diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d8561622e662310109d17276197f6b49ed819661
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,588 @@
+defaults: &defaults
+  working_directory: /home/circleci/metabase/metabase/
+  docker:
+    - image: circleci/clojure:lein-2.8.1-node-browsers
+
+restore-be-deps-cache: &restore-be-deps-cache
+  keys:
+    - be-deps-{{ checksum "project.clj" }}
+    - be-deps-
+
+restore-fe-deps-cache: &restore-fe-deps-cache
+  keys:
+    - fe-deps-{{ checksum "yarn.lock" }}
+    - fe-deps-
+
+version: 2.1
+jobs:
+
+########################################################################################################################
+#                                                       CHECKOUT                                                       #
+########################################################################################################################
+
+  checkout:
+    <<: *defaults
+    steps:
+      - restore_cache:
+          keys:
+            - source-{{ .Branch }}-{{ .Revision }}
+            - source-{{ .Branch }}
+            - source-
+      - checkout
+      - save_cache:
+          key: source-{{ .Branch }}-{{ .Revision }}
+          paths:
+            - .git
+      # The basic idea here is to generate a file with checksums for all the backend source files, and save it as
+      # `./backend-checksums.txt`. Then we'll use the checksum of that files for uberjar caching; thus we can reuse
+      # the same uberjar for integration tests across any build where the backend files are the same
+      - run:
+          name: Generate checksums of all backend source files to use as Uberjar cache key
+          command: >
+            for file in `find ./src -type f -name '*.clj' | sort`;
+              do echo `md5sum $file` >> backend-checksums.txt;
+            done
+      - persist_to_workspace:
+          root: /home/circleci/
+          paths:
+            - metabase/metabase
+
+########################################################################################################################
+#                                                       BACKEND                                                        #
+########################################################################################################################
+
+  be-deps:
+    <<: *defaults
+    steps:
+      - attach_workspace:
+          at: /home/circleci/
+      - restore_cache:
+          <<: *restore-be-deps-cache
+      - run: lein deps
+      - save_cache:
+          key: be-deps-{{ checksum "project.clj" }}
+          paths:
+            - /home/circleci/.m2
+
+  be-tests:
+    <<: *defaults
+    steps:
+      - attach_workspace:
+          at: /home/circleci/
+      - restore_cache:
+          <<: *restore-be-deps-cache
+      - run:
+          name: Run backend unit tests
+          command: lein with-profile +ci test
+          no_output_timeout: 5m
+
+  be-linter-eastwood:
+    <<: *defaults
+    steps:
+      - attach_workspace:
+          at: /home/circleci/
+      - restore_cache:
+          <<: *restore-be-deps-cache
+      - run:
+          name: Run Eastwood linter
+          command: lein with-profile +ci eastwood
+          no_output_timeout: 5m
+
+  be-linter-docstring-checker:
+    <<: *defaults
+    steps:
+      - attach_workspace:
+          at: /home/circleci/
+      - restore_cache:
+          <<: *restore-be-deps-cache
+      - run:
+          name: Run dockstring-checker
+          command: lein with-profile +ci docstring-checker
+          no_output_timeout: 5m
+
+  be-linter-bikeshed:
+    <<: *defaults
+    steps:
+      - attach_workspace:
+          at: /home/circleci/
+      - restore_cache:
+          <<: *restore-be-deps-cache
+      - run:
+          name: Run dockstring-checker
+          command: lein with-profile +ci bikeshed
+          no_output_timeout: 5m
+
+  be-linter-reflection-warnings:
+    <<: *defaults
+    steps:
+      - attach_workspace:
+          at: /home/circleci/
+      - restore_cache:
+          <<: *restore-be-deps-cache
+      - run:
+          name: Run dockstring-checker
+          command: ./bin/reflection-linter
+          no_output_timeout: 5m
+
+  be-tests-mysql:
+    working_directory: /home/circleci/metabase/metabase/
+    docker:
+      - image: circleci/clojure:lein-2.8.1-node-browsers
+      - image: circleci/mysql:5.7.23
+    steps:
+      - attach_workspace:
+          at: /home/circleci/
+      - restore_cache:
+          <<: *restore-be-deps-cache
+      - run:
+          name: Run backend unit tests (MySQL)
+          environment:
+            ENGINES: h2,mysql
+            MB_ENCRYPTION_SECRET_KEY: Orw0AAyzkO/kPTLJRxiyKoBHXa/d6ZcO+p+gpZO/wSQ=
+            MB_DB_TYPE: mysql
+            MB_DB_HOST: localhost
+            MB_DB_PORT: 3306
+            MB_DB_DBNAME: circle_test
+            MB_DB_USER: root
+            MB_MYSQL_TEST_USER: root
+          command: >
+            /home/circleci/metabase/metabase/.circleci/skip-driver-tests.sh mysql ||
+            lein with-profile +ci test
+          no_output_timeout: 5m
+
+  be-tests-postgres:
+    working_directory: /home/circleci/metabase/metabase/
+    docker:
+      - image: circleci/clojure:lein-2.8.1-node-browsers
+      - image: circleci/postgres:9.6-alpine
+        environment:
+          POSTGRES_USER: circle_test
+          POSTGRES_DB: circle_test
+    steps:
+      - attach_workspace:
+          at: /home/circleci/
+      - restore_cache:
+          <<: *restore-be-deps-cache
+      - run:
+          name: Run backend unit tests (Postgres)
+          environment:
+            ENGINES: h2,postgres
+            MB_DB_TYPE: postgres
+            MB_DB_PORT: 5432
+            MB_DB_HOST: localhost
+            MB_DB_DBNAME: circle_test
+            MB_DB_USER: circle_test
+            MB_POSTGRESQL_TEST_USER: circle_test
+          command: >
+            /home/circleci/metabase/metabase/.circleci/skip-driver-tests.sh postgres ||
+            lein with-profile +ci test
+          no_output_timeout: 5m
+
+  be-tests-sparksql:
+    working_directory: /home/circleci/metabase/metabase/
+    docker:
+      - image: circleci/clojure:lein-2.8.1-node-browsers
+      - image: metabase/spark:2.1.1
+    steps:
+      - attach_workspace:
+          at: /home/circleci/
+      - restore_cache:
+          <<: *restore-be-deps-cache
+      - run:
+          name: Make plugins dir
+          command: mkdir /home/circleci/metabase/metabase/plugins
+      - run:
+          name: Download SparkSQL deps JAR
+          command: wget --output-document=plugins/spark-deps.jar https://s3.amazonaws.com/sparksql-deps/metabase-sparksql-deps-1.2.1.spark2-standalone.jar
+      - run:
+          name: Wait for SparkSQL to be ready
+          command: >
+            /home/circleci/metabase/metabase/.circleci/skip-driver-tests.sh sparksql ||
+            while ! nc -z localhost 10000; do sleep 0.1; done
+          no_output_timeout: 5m
+      - run:
+          name: Run backend unit tests (SparkSQL)
+          environment:
+            ENGINES: h2,sparksql
+          command: >
+            /home/circleci/metabase/metabase/.circleci/skip-driver-tests.sh sparksql ||
+            lein with-profile +ci test
+          no_output_timeout: 5m
+
+  be-tests-mongo:
+    working_directory: /home/circleci/metabase/metabase/
+    docker:
+      - image: circleci/clojure:lein-2.8.1-node-browsers
+      - image: circleci/mongo:3.2
+    steps:
+      - attach_workspace:
+          at: /home/circleci/
+      - restore_cache:
+          <<: *restore-be-deps-cache
+      - run:
+          name: Run backend unit tests (MongoDB 3.2)
+          command: >
+            /home/circleci/metabase/metabase/.circleci/skip-driver-tests.sh mongo ||
+            lein with-profile +ci test
+          no_output_timeout: 5m
+
+  be-tests-vertica:
+    working_directory: /home/circleci/metabase/metabase/
+    docker:
+      - image: circleci/clojure:lein-2.8.1-node-browsers
+      - image: sumitchawla/vertica
+    steps:
+      - attach_workspace:
+          at: /home/circleci/
+      - restore_cache:
+          <<: *restore-be-deps-cache
+      - run:
+          name: Make plugins dir
+          command: mkdir /home/circleci/metabase/metabase/plugins
+      - run:
+          name: Download Vertica JAR
+          command: >
+            /home/circleci/metabase/metabase/.circleci/skip-driver-tests.sh vertica ||
+            wget --output-document=plugins/vertica-jdbc-7.1.2-0.jar $VERTICA_JDBC_JAR
+          no_output_timeout: 5m
+      - run:
+          name: Run backend unit tests (Vertica)
+          command: >
+            /home/circleci/metabase/metabase/.circleci/skip-driver-tests.sh vertica ||
+            lein with-profile +ci test
+          no_output_timeout: 5m
+
+  be-tests-sqlserver:
+    <<: *defaults
+    steps:
+      - attach_workspace:
+          at: /home/circleci/
+      - restore_cache:
+          <<: *restore-be-deps-cache
+      - run:
+          name: Run backend unit tests (SQL Server)
+          command: >
+            /home/circleci/metabase/metabase/.circleci/skip-driver-tests.sh sqlserver ||
+            lein with-profile +ci test
+          no_output_timeout: 5m
+
+  be-tests-bigquery:
+    <<: *defaults
+    steps:
+      - attach_workspace:
+          at: /home/circleci/
+      - restore_cache:
+          <<: *restore-be-deps-cache
+      - run:
+          name: Run backend unit tests (BigQuery)
+          environment:
+            ENGINES: h2,bigquery
+          command: >
+            /home/circleci/metabase/metabase/.circleci/skip-driver-tests.sh bigquery ||
+            lein with-profile +ci test
+          no_output_timeout: 5m
+
+  be-tests-sqlite:
+    <<: *defaults
+    steps:
+      - attach_workspace:
+          at: /home/circleci/
+      - restore_cache:
+          <<: *restore-be-deps-cache
+      - run:
+          name: Run backend unit tests (SQLite)
+          environment:
+            ENGINES: h2,sqlite
+          command: >
+            /home/circleci/metabase/metabase/.circleci/skip-driver-tests.sh sqlite ||
+            lein with-profile +ci test
+
+  be-tests-druid:
+    <<: *defaults
+    steps:
+      - attach_workspace:
+          at: /home/circleci/
+      - restore_cache:
+          <<: *restore-be-deps-cache
+      - run:
+          name: Run backend unit tests (Druid)
+          environment:
+            ENGINES: h2,druid
+          command: >
+            /home/circleci/metabase/metabase/.circleci/skip-driver-tests.sh druid ||
+            lein with-profile +ci test
+          no_output_timeout: 5m
+
+  be-tests-redshift:
+    <<: *defaults
+    steps:
+      - attach_workspace:
+          at: /home/circleci/
+      - restore_cache:
+          <<: *restore-be-deps-cache
+      - run:
+          name: Run backend unit tests (Redshift)
+          environment:
+            ENGINES: h2,redshift
+          command: >
+            /home/circleci/metabase/metabase/.circleci/skip-driver-tests.sh redshift ||
+            lein with-profile +ci test
+          no_output_timeout: 5m
+
+
+  be-tests-presto:
+    working_directory: /home/circleci/metabase/metabase/
+    docker:
+      - image: circleci/clojure:lein-2.8.1-node-browsers
+      - image: metabase/presto-mb-ci
+    steps:
+      - attach_workspace:
+          at: /home/circleci/
+      - restore_cache:
+          <<: *restore-be-deps-cache
+      - run:
+          name: Run backend unit tests (Presto)
+          environment:
+            ENGINES: h2,presto
+            MB_PRESTO_TEST_HOST: localhost
+            MB_PRESTO_TEST_PORT: 8080
+          command: >
+            /home/circleci/metabase/metabase/.circleci/skip-driver-tests.sh presto ||
+            lein with-profile +ci test
+          no_output_timeout: 5m
+
+
+  be-tests-oracle:
+    <<: *defaults
+    steps:
+      - attach_workspace:
+          at: /home/circleci/
+      - restore_cache:
+          <<: *restore-be-deps-cache
+      - run:
+          name: Make plugins dir
+          command: mkdir /home/circleci/metabase/metabase/plugins
+      - run:
+          name: Download Oracle JAR
+          command: >
+            /home/circleci/metabase/metabase/.circleci/skip-driver-tests.sh oracle ||
+            wget --output-document=/home/circleci/metabase/metabase/plugins/ojdbc7.jar $ORACLE_JDBC_JAR
+      - run:
+          name: Run backend unit tests (Oracle)
+          environment:
+            ENGINES: h2,oracle
+          command: >
+            /home/circleci/metabase/metabase/.circleci/skip-driver-tests.sh oracle ||
+            lein with-profile +ci test
+          no_output_timeout: 5m
+
+
+########################################################################################################################
+#                                                       FRONTEND                                                       #
+########################################################################################################################
+
+  fe-deps:
+    <<: *defaults
+    steps:
+      - attach_workspace:
+          at: /home/circleci/
+      - restore_cache:
+          <<: *restore-fe-deps-cache
+      - run:
+          name: Run yarn
+          command: SAUCE_CONNECT_DOWNLOAD_ON_INSTALL=true yarn
+          no_output_timeout: 5m
+      - save_cache:
+          key: fe-deps-{{ checksum "yarn.lock" }}
+          paths:
+            - /home/circleci/.yarn
+            - /home/circleci/.yarn-cache
+            - /home/circleci/metabase/metabase/node_modules
+
+  fe-linter-eslint:
+    <<: *defaults
+    steps:
+      - attach_workspace:
+          at: /home/circleci/
+      - restore_cache:
+          <<: *restore-fe-deps-cache
+      - run:
+          name: Run ESLint linter
+          command: yarn lint-eslint
+          no_output_timeout: 5m
+
+  fe-linter-prettier:
+    <<: *defaults
+    steps:
+      - attach_workspace:
+          at: /home/circleci/
+      - restore_cache:
+          <<: *restore-fe-deps-cache
+      - run:
+          name: Run Prettier formatting linter
+          command: yarn lint-prettier
+          no_output_timeout: 5m
+
+  fe-linter-flow:
+    <<: *defaults
+    steps:
+      - attach_workspace:
+          at: /home/circleci/
+      - restore_cache:
+          <<: *restore-fe-deps-cache
+      - run:
+          name: Run Flow type checker
+          command: yarn flow
+          no_output_timeout: 5m
+
+  fe-tests-karma:
+    <<: *defaults
+    steps:
+      - attach_workspace:
+          at: /home/circleci/
+      - restore_cache:
+          <<: *restore-fe-deps-cache
+      - run:
+          name: Run frontend tests (karma)
+          command: yarn run test-karma
+          no_output_timeout: 5m
+
+  fe-tests-unit:
+    <<: *defaults
+    steps:
+      - attach_workspace:
+          at: /home/circleci/
+      - restore_cache:
+          <<: *restore-fe-deps-cache
+      - run:
+          name: Run frontend unit tests
+          command: yarn run test-unit
+          no_output_timeout: 5m
+
+  build-uberjar:
+    <<: *defaults
+    steps:
+      - attach_workspace:
+          at: /home/circleci/
+      - restore_cache:
+          <<: *restore-be-deps-cache
+      - restore_cache:
+          keys:
+            - uberjar-{{ checksum "./backend-checksums.txt" }}
+      - run:
+          name: Build uberjar if needed
+          command: >
+            if [ ! -f './target/uberjar/metabase.jar' ]; then ./bin/build version uberjar; fi
+          no_output_timeout: 5m
+      - save_cache:
+          key: uberjar-{{ checksum "./backend-checksums.txt" }}
+          paths:
+            - /home/circleci/metabase/metabase/target/uberjar/metabase.jar
+
+  fe-tests-integrated:
+    <<: *defaults
+    steps:
+      - attach_workspace:
+          at: /home/circleci/
+      - restore_cache:
+          <<: *restore-fe-deps-cache
+      - restore_cache:
+          keys:
+            - uberjar-{{ checksum "./backend-checksums.txt" }}
+      - run:
+          name: Generate version file
+          command: ./bin/build version
+      - run:
+          name: Run frontend integrated tests
+          command: yarn run test-integrated-no-build
+          no_output_timeout: 5m
+
+
+########################################################################################################################
+#                                                      WORKFLOWS                                                       #
+########################################################################################################################
+
+workflows:
+  version: 2
+  build:
+    jobs:
+      - checkout
+      - be-deps:
+          requires:
+            - checkout
+      - be-tests:
+          requires:
+            - be-deps
+      - be-linter-eastwood:
+          requires:
+            - be-deps
+      - be-linter-docstring-checker:
+          requires:
+            - be-deps
+      - be-linter-bikeshed:
+          requires:
+            - be-deps
+      - be-linter-reflection-warnings:
+          requires:
+            - be-deps
+      - be-tests-mysql:
+          requires:
+            - be-tests
+      - be-tests-postgres:
+          requires:
+            - be-tests
+      - be-tests-sparksql:
+          requires:
+            - be-tests
+      - be-tests-mongo:
+          requires:
+            - be-tests
+      - be-tests-sqlserver:
+          requires:
+            - be-tests
+      - be-tests-bigquery:
+          requires:
+            - be-tests
+      - be-tests-sqlite:
+          requires:
+            - be-tests
+      - be-tests-presto:
+          requires:
+            - be-tests
+      - be-tests-oracle:
+          requires:
+            - be-tests
+      - be-tests-druid:
+          requires:
+            - be-tests
+      - be-tests-redshift:
+          requires:
+            - be-tests
+      - be-tests-vertica:
+          requires:
+            - be-tests
+      - fe-deps:
+          requires:
+            - checkout
+      - fe-linter-eslint:
+          requires:
+            - fe-deps
+      - fe-linter-prettier:
+          requires:
+            - fe-deps
+      - fe-linter-flow:
+          requires:
+            - fe-deps
+      - fe-tests-karma:
+          requires:
+            - fe-deps
+      - fe-tests-unit:
+          requires:
+            - fe-deps
+      - build-uberjar:
+          requires:
+            - be-deps
+      - fe-tests-integrated:
+          requires:
+            - build-uberjar
+            - fe-deps
diff --git a/.circleci/skip-driver-tests.sh b/.circleci/skip-driver-tests.sh
new file mode 100755
index 0000000000000000000000000000000000000000..d0ee1ff9fddf999f2304bc4f310275687e8d568a
--- /dev/null
+++ b/.circleci/skip-driver-tests.sh
@@ -0,0 +1,18 @@
+#!/usr/bin/env bash
+
+# Determines whether we should skip tests for a driver, usage:
+#
+#    ./.circleci/skip-driver-tests.sh oracle
+#
+# Returns false if the commit message contains [ci all], [ci drivers], or [ci <driver-name>],
+# or if the current branch is master or a release branch.
+
+set -eu
+
+COMMIT_MESSAGE=`git log -1 --oneline`
+
+! [[ "$CIRCLE_BRANCH" =~ ^master|release-.+$ ]] &&
+    ! [[ "$COMMIT_MESSAGE" == *"[ci all]"* ]] &&
+    ! [[ "$COMMIT_MESSAGE" == *"[ci drivers]"* ]] &&
+    ! [[ "$COMMIT_MESSAGE" == *"[ci $1]"* ]] &&
+    echo "Skipping driver tests: $1"
diff --git a/.eslintrc b/.eslintrc
index d0472ad6208f4201b061130ffec27547d36241f6..07a483fb6b804386c7b0985572d6e33765e2544c 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -16,7 +16,8 @@
         "react/no-did-update-set-state": 0,
         "react/no-find-dom-node": 0,
         "flowtype/define-flow-type": 1,
-        "flowtype/use-flow-type": 1
+        "flowtype/use-flow-type": 1,
+        "no-color-literals": 1
     },
     "globals": {
         "pending": false
diff --git a/.gitignore b/.gitignore
index f82f949b1705dec2e57dcb4152c14fdeaaf0eb2a..70e402a9dfdffcacc9f431c115d3071a325569af 100644
--- a/.gitignore
+++ b/.gitignore
@@ -54,3 +54,4 @@ coverage-summary.json
 bin/node_modules/
 *.log
 *.trace.db
+/backend-checksums.txt
diff --git a/Dockerfile b/Dockerfile
index ef867480cfbb1c321733d839458eb3fa9324e269..a8751a36229dab35a6ff7afe408e65c2bec1e7fb 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -2,7 +2,7 @@
 # STAGE 1: builder
 ###################
 
-FROM java:openjdk-8-jre-alpine as builder
+FROM openjdk:8-jdk-alpine as builder
 
 WORKDIR /app/source
 
@@ -14,7 +14,8 @@ ENV LC_CTYPE en_US.UTF-8
 # git:     ./bin/version
 # nodejs:  frontend building
 # make:    backend building
-RUN apk add --update bash nodejs git wget make
+# gettext: translations
+RUN apk add --update bash nodejs nodejs-npm git wget make gettext
 
 # yarn:    frontend dependencies
 RUN npm install -g yarn
diff --git a/README.md b/README.md
index 576e5786a6a9a3afc78f6a9d3cc56e99a618a3dc..292677ef4e6b1d8e71ac05ae64d6c6206aaefcdc 100644
--- a/README.md
+++ b/README.md
@@ -96,7 +96,10 @@ To get started with a development installation of the Metabase, follow the instr
 
 Then take a look at our [Contribution Guide](docs/contributing.md) for information about our process and where you can fit in!
 
-Talk to other contributors [in our Gitter room](https://gitter.im/metabase/metabase). 
+Talk to other contributors [in our Gitter room](https://gitter.im/metabase/metabase).
+
+# Internationalization
+We want Metabase to be avaliable in as many languages as possible. See what translations are avaliable and help contribute to internationalization using our project [over at POEditor](https://poeditor.com/join/project/ynjQmwSsGh)
 
 # Extending and Deep Integrations
 
diff --git a/bin/build b/bin/build
index aa4388776d2609975bd21f58c3c752f712c1fc6f..b16fc136b90ba3cac4042d8d6265126be96c2f4a 100755
--- a/bin/build
+++ b/bin/build
@@ -20,15 +20,25 @@ frontend-deps() {
 }
 
 frontend() {
+    frontend-deps
     echo "Running 'webpack' with NODE_ENV=production assemble and minify frontend assets..." &&
     NODE_ENV=production ./node_modules/.bin/webpack --bail
 }
 
 frontend-fast() {
+    frontend-deps
     echo "Running 'webpack' with NODE_ENV=development to assemble frontend assets..." &&
     NODE_ENV=development ./node_modules/.bin/webpack --bail --devtool eval
 }
 
+translations() {
+    echo "Running './bin/i18n/build-translation-resources' to build translation resources..."
+    if ! ./bin/i18n/build-translation-resources; then
+      echo "Building translation resources failed, please install 'gettext', or build without translations by running './bin/build no-translations'."
+      exit 1
+    fi
+}
+
 sample-dataset() {
     if [ -f resources/sample-dataset.db.mv.db ]; then
         echo "Sample Dataset already generated."
@@ -44,7 +54,11 @@ uberjar() {
 }
 
 all() {
-    version && frontend-deps && frontend && sample-dataset && uberjar
+    version && translations && frontend && sample-dataset && uberjar
+}
+
+no-translations() {
+    version && frontend && sample-dataset && uberjar
 }
 
 # Default to running all but let someone specify one or more sub-tasks to run instead if desired
diff --git a/bin/ci b/bin/ci
deleted file mode 100755
index 91d024cecdea64862947a663097f2970b5201146..0000000000000000000000000000000000000000
--- a/bin/ci
+++ /dev/null
@@ -1,282 +0,0 @@
-#!/usr/bin/env bash
-
-# this ensures any failures along the way result in a CI failure
-set -eu
-
-node-0() {
-    is_enabled "drivers" && export ENGINES="h2,mongo,mysql,bigquery,sparksql" || export ENGINES="h2"
-    if is_engine_enabled "mongo"; then
-        run_step install-mongodb
-    fi
-    if is_engine_enabled "sparksql"; then
-        run_step install-sparksql
-    fi
-    MB_MYSQL_TEST_USER=ubuntu run_step lein-test
-}
-node-1() {
-    run_step lein with-profile +ci docstring-checker
-
-    is_enabled "drivers" && export ENGINES="h2,sqlserver,oracle" || export ENGINES="h2"
-    if is_engine_enabled "oracle"; then
-        run_step install-oracle
-    fi
-    MB_DB_TYPE=postgres MB_DB_DBNAME=circle_test MB_DB_PORT=5432 MB_DB_USER=ubuntu MB_DB_HOST=localhost \
-        run_step lein-test
-}
-node-2() {
-    run_step lein with-profile +ci bikeshed
-
-    is_enabled "drivers" && export ENGINES="h2,postgres,sqlite,presto" || export ENGINES="h2"
-    if is_engine_enabled "crate"; then
-        run_step install-crate
-    fi
-    if is_engine_enabled "presto"; then
-        run_step install-presto
-    fi
-    MB_ENCRYPTION_SECRET_KEY='Orw0AAyzkO/kPTLJRxiyKoBHXa/d6ZcO+p+gpZO/wSQ=' MB_DB_TYPE=mysql MB_DB_DBNAME=circle_test MB_DB_PORT=3306 MB_DB_USER=ubuntu MB_DB_HOST=localhost \
-        MB_PRESTO_TEST_HOST=localhost MB_PRESTO_TEST_PORT=8080 MB_POSTGRESQL_TEST_USER=ubuntu \
-        run_step lein-test
-}
-node-3() {
-    run_step yarn run lint
-    run_step yarn run flow
-
-    is_enabled "drivers" && export ENGINES="h2,redshift,druid,vertica" || export ENGINES="h2"
-    if is_engine_enabled "vertica"; then
-        run_step install-vertica
-    fi
-    # this is redundant with node 0 unless one of the non-H2 driver tests is enabled
-    if [ ENGINES != "h2" ]; then
-        run_step lein-test
-    fi
-}
-node-4() {
-    run_step ./bin/reflection-linter
-
-    run_step ./bin/build version frontend sample-dataset uberjar
-    report-frontend-size
-    report-uberjar-size
-}
-node-5() {
-    run_step lein with-profile +ci eastwood
-
-    run_step yarn run test-karma
-    run_step yarn run test-unit --coverage
-    report-frontend-coverage
-}
-node-6() {
-    run_step ./bin/build-for-test
-    run_step check-uberjar-file-count
-    run_step yarn run test-integrated-no-build
-}
-
-report() {
-  timestamp="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
-  name="$1"
-  value="$2"
-  if ! [ -z ${STATS_DB+x} ]; then
-    psql "$STATS_DB" -c "INSERT INTO build_stats (timestamp, name, value, build_number, node_index, branch, hash) VALUES ('$timestamp', '$name', $value, $CIRCLE_BUILD_NUM, $CIRCLE_NODE_INDEX, '$CIRCLE_BRANCH', '$CIRCLE_SHA1');" > /dev/null
-  fi
-}
-
-report-frontend-coverage() {
-  report "frontend-coverage-lines" $(node -e "console.log(require('./coverage-summary.json').total.lines.pct)")
-  report "frontend-coverage-functions" $(node -e "console.log(require('./coverage-summary.json').total.functions.pct)")
-  report "frontend-coverage-branches" $(node -e "console.log(require('./coverage-summary.json').total.branches.pct)")
-  report "frontend-coverage-statements" $(node -e "console.log(require('./coverage-summary.json').total.statements.pct)")
-
-  report "frontend-loc" $(node -e "console.log(require('./coverage-summary.json').total.lines.total)")
-}
-report-frontend-size() {
-  report "frontend-size" "$(wc -c < resources/frontend_client/app/dist/app-main.bundle.js)"
-}
-report-uberjar-size() {
-  report "uberjar-size" "$(wc -c < target/uberjar/metabase.jar)"
-}
-
-install-crate() {
-    sudo add-apt-repository ppa:crate/stable -y
-    sudo apt-get update
-    sudo apt-get install -y crate
-    # ulimit setting refused Crate service to start on CircleCI container - so comment it
-    sudo sed -i '/MAX_LOCKED_MEMORY/s/^/#/' /etc/init/crate.conf
-    echo "psql.port: 5200" | sudo tee -a /etc/crate/crate.yml
-    sudo service crate restart
-}
-
-install-mongodb() {
-    sudo apt-get purge mongodb-org*
-    sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10
-    echo "deb http://repo.mongodb.org/apt/ubuntu precise/mongodb-org/3.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.0.list
-    sudo apt-get update
-    sudo apt-get install -y mongodb-org
-    sudo service mongod restart
-}
-
-install-oracle() {
-    wget --output-document=plugins/ojdbc7.jar $ORACLE_JDBC_JAR
-}
-
-install-vertica() {
-    wget --output-document=plugins/vertica-jdbc-7.1.2-0.jar $VERTICA_JDBC_JAR
-    docker run --detach --publish 5433:5433 sumitchawla/vertica
-    sleep 60
-}
-
-install-presto() {
-    docker run --detach --publish 8080:8080 metabase/presto-mb-ci
-    sleep 10
-}
-
-install-sparksql() {
-    # first, download the Spark Deps JAR and put it in the plugins/ dir
-    wget --output-document=plugins/spark-deps.jar https://s3.amazonaws.com/sparksql-deps/metabase-sparksql-deps-1.2.1.spark2-standalone.jar
-
-    # next, download Spark and run it
-    spark_version='2.1.1' # Java 7 support was removed in Spark 2.2 so don't upgrade until we upgrade CI
-    hadoop_version='2.7'
-
-    spark_archive="spark-${spark_version}-bin-hadoop${hadoop_version}.tgz"
-    wget --progress dot -e dotbytes=250K "https://archive.apache.org/dist/spark/spark-${spark_version}/${spark_archive}"
-    tar -xf $spark_archive
-    rm $spark_archive
-
-    spark_dir="$(pwd)/spark-${spark_version}-bin-hadoop${hadoop_version}"
-    java -Duser.timezone=Etc/UTC \
-         -Xmx512m \
-         -cp "${spark_dir}/conf:${spark_dir}/jars/*" \
-         org.apache.spark.deploy.SparkSubmit \
-         --master local[8] \
-         --conf spark.executor.extraJavaOptions=-Duser.timezone=Etc/UTC \
-         --conf spark.cores.max=1 \
-         --class org.apache.spark.sql.hive.thriftserver.HiveThriftServer2 \
-         --name "Thrift JDBC/ODBC Server" \
-         --executor-memory 512m \
-         spark-internal &>/dev/null &
-}
-
-lein-test() {
-    lein with-profile +ci test
-}
-
-if [ -z ${CIRCLE_BRANCH_REGEX+x} ]; then
-    CIRCLE_BRANCH_REGEX='^master|release-.+$'
-fi
-
-is_enabled() {
-    (echo "$CIRCLE_BRANCH" | grep -qE "$CIRCLE_BRANCH_REGEX") ||
-    [[ "$CIRCLE_COMMIT_MESSAGE" == *"[ci $1]"* ]] ||
-    [[ "$CIRCLE_COMMIT_MESSAGE" == *"[ci all]"* ]]
-}
-
-is_engine_enabled() {
-    [[ "$ENGINES" == *"$1"* ]]
-}
-
-# Make sure uberjar has less than 64k files because that is the Java 7 LIMIT
-check-uberjar-file-count() {
-    if [ ! -f ./target/uberjar/metabase.jar ]; then
-        echo "Missing uberjar."
-        exit  1
-    fi
-
-    file_count=$(unzip -l target/uberjar/metabase.jar | wc -l)
-    echo "Uberjar has ${file_count} files."
-
-    if [ $file_count -gt 65535 ]; then
-        echo "Uberjar exceeds the 64k Java 7 file limit! We can't allow this. ¡Lo siento!"
-        exit 1
-    fi
-}
-
-# print a summary on exit
-status=0
-summary=""
-
-# records the time and exit code of each step
-run_step() {
-    status=0
-    start=$(date +%s)
-    # run in the background then `wait` so fail_fast can interrupt it
-    "$@" &
-    wait $! || status=$?
-    elapsed=$(expr $(date +%s) - $start || true)
-    summary="${summary}status=$status time=$elapsed command=$@\n"
-    report "run-status \"$*\"" "$status"
-    report "run-time \"$*\"" "$elapsed"
-    return $status
-}
-
-summary() {
-    # if last status was failure then fail the rest of the nodes
-    if [ $status != 0 ]; then
-      fail_fast
-    fi
-    echo -e "========================================"
-    echo -en "$summary"
-    echo -e "========================================"
-}
-
-trap summary EXIT
-
-fail_fast() {
-  if [ -z ${CIRCLE_NODE_TOTAL+x} ]; then
-    return 0
-  fi
-
-  echo -e "========================================"
-  echo -e "Failing fast! Stopping other nodes..."
-  # Touch a file to differentiate between a local failure and a
-  # failure triggered by another node
-  touch '/tmp/local-fail'
-  # ssh to the other CircleCI nodes and send SIGUSR1 to tell them to exit early
-  for (( i = 0; i < $CIRCLE_NODE_TOTAL; i++ )); do
-    if [ $i != $CIRCLE_NODE_INDEX ]; then
-      ssh node$i 'touch /tmp/fail; pkill -SIGUSR1 -f "bash ./bin/ci"' 2> /dev/null || true
-    fi
-  done
-}
-
-exit_early() {
-  echo -e "========================================"
-  echo -e "Exited early! Did not necesssarily pass!"
-  pkill -TERM -P $$ || true
-  exit 0
-}
-
-trap exit_early SIGUSR1
-
-if [ -z ${CIRCLE_BUILD_NUM+x} ]; then
-    export CIRCLE_BUILD_NUM="-1"
-fi
-
-if [ -z ${CIRCLE_SHA1+x} ]; then
-    export CIRCLE_SHA1="$(git rev-parse HEAD)"
-fi
-
-if [ -z ${CIRCLE_BRANCH+x} ]; then
-    export CIRCLE_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
-fi
-
-export CIRCLE_COMMIT_MESSAGE="$(git log --format=oneline -n 1 $CIRCLE_SHA1)"
-
-# This local-fail check is to guard against two nodes failing at the
-# same time. Both nodes ssh to each node and drop /tmp/fail. Those
-# failing nodes then get here and see and the other node has told it
-# to exit early. This results in both nodes exiting early, and thus
-# not failing, causing the build to succeed
-if [[ -f "/tmp/fail" && ! -f "/tmp/local-fail" ]]; then
-  exit_early
-fi
-
-if [ -z ${CIRCLE_NODE_INDEX+x} ]; then
-    # If CIRCLE_NODE_INDEX isn't set, read node numbers from the args
-    # Useful for testing locally.
-    for i in "$@"; do
-      export CIRCLE_NODE_INDEX="$i"
-      node-$i
-    done
-else
-    # Normal mode on CircleCI
-    node-$CIRCLE_NODE_INDEX
-fi
diff --git a/bin/colopocalypse b/bin/colopocalypse
new file mode 100755
index 0000000000000000000000000000000000000000..a9517c8c99f8a8b6902c1f5f3ecb19029143c840
--- /dev/null
+++ b/bin/colopocalypse
@@ -0,0 +1,398 @@
+#!./node_modules/.bin/babel-node
+
+const glob = require("glob");
+const fs = require("fs");
+const path = require("path");
+const Color = require("color");
+const colorDiff = require("color-diff");
+const _ = require("underscore");
+const j = require("jscodeshift");
+
+const { replaceStrings } = require("./lib/codemod");
+
+const POSTCSS_CONFIG = require("../postcss.config.js");
+const cssVariables =
+  POSTCSS_CONFIG.plugins["postcss-cssnext"].features.customProperties.variables;
+// console.log(cssVariables);
+
+// these are a bit liberal regexes but that's probably ok
+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}\))/g;
+const COLOR_REGEX_WITH_LINE = /(?:#[a-fA-F0-9]{3}(?:[a-fA-F0-9]{3})?\b|(?:rgb|hsl)a?\(\s*\d+\s*(?:,\s*\d+(?:\.\d+)?%?\s*){2,3}\)).*/g;
+
+const CSS_SIMPLE_VAR_REGEX = /^var\(([^)]+)\)$/;
+const CSS_COLOR_VAR_REGEX = /^color\(var\(([^)]+)\) shade\(([^)]+)\)\)$/;
+const CSS_VAR_REGEX = /var\([^)]+\)|color\(var\([^)]+\) shade\([^)]+\)\)/g;
+
+const FILE_GLOB = "frontend/src/**/*.{css,js,jsx}";
+const FILE_GLOB_IGNORE = [
+  "**/metabase/lib/colors.js",
+  "**/metabase/css/core/colors.css",
+  "**/metabase/auth/components/AuthScene.jsx",
+  "**/metabase/icon_paths.js",
+  // // recast messes up these file and they don't have any colors so just ignore them:
+  // "**/metabase/query_builder/components/FieldList.jsx",
+  // "**/metabase/query_builder/components/filters/FilterPopover.jsx",
+  // "**/metabase/visualizations/components/TableInteractive.jsx",
+];
+
+const COLORS_CSS_PATH = "frontend/src/metabase/css/core/colors.css";
+const COLORS_JS_PATH = "frontend/src/metabase/lib/colors.js";
+
+const varForName = name => `--color-${name}`;
+
+const colors = {
+  // themeable colors
+
+  brand: "#509EE3",
+
+  accent1: "#9CC177",
+  accent2: "#A989C5",
+  accent3: "#EF8C8C",
+  accent4: "#F9D45C",
+
+  accent5: "#F1B556",
+  accent6: "#A6E7F3",
+  accent7: "#7172AD",
+
+  // general purpose
+
+  white: "#FFFFFF",
+  black: "#2E353B",
+
+  // semantic colors
+
+  success: "#84BB4C",
+  error: "#ED6E6E",
+  warning: "#F9CF48",
+
+  "text-dark": "#2E353B", // "black"
+  "text-medium": "#93A1AB",
+  "text-light": "#DCE1E4",
+  "text-white": "#FFFFFF", // "white"
+
+  "bg-black": "#2E353B", // "black"
+  "bg-dark": "#93A1AB",
+  "bg-medium": "#EDF2F5",
+  "bg-light": "#F9FBFC",
+  "bg-white": "#FFFFFF", // "white"
+
+  shadow: "#F4F5F6",
+  border: "#D7DBDE",
+};
+
+function paletteForColors(colors) {
+  return Object.entries(colors).map(([name, colorValue]) => {
+    const color = Color(colorValue);
+    return {
+      name,
+      color,
+      R: color.red(),
+      G: color.green(),
+      B: color.blue(),
+    };
+  });
+}
+
+const PRIMARY_AND_SECONDARY_NAMES = [
+  "brand",
+  "accent1",
+  "accent2",
+  "accent3",
+  "accent4",
+];
+const TEXT_COLOR_NAMES = [
+  "text-dark",
+  "text-medium",
+  "text-light",
+  "text-white",
+];
+const BACKGROUND_COLOR_NAMES = [
+  "bg-black",
+  "bg-dark",
+  "bg-medium",
+  "bg-light",
+  "bg-white",
+];
+const SEMANTIC_NAMES = ["success", "error", "warning"];
+
+const PALETTE_FOREGROUND = paletteForColors(
+  _.pick(
+    colors,
+    ...TEXT_COLOR_NAMES,
+    ...PRIMARY_AND_SECONDARY_NAMES,
+    ...SEMANTIC_NAMES,
+  ),
+);
+const PALETTE_BACKGROUND = paletteForColors(
+  _.pick(
+    colors,
+    ...BACKGROUND_COLOR_NAMES,
+    ...PRIMARY_AND_SECONDARY_NAMES,
+    ...SEMANTIC_NAMES,
+  ),
+);
+const PALETTE_BORDER = paletteForColors(
+  _.pick(colors, "border", ...PRIMARY_AND_SECONDARY_NAMES),
+);
+const PALETTE_SHADOW = paletteForColors(_.pick(colors, "shadow"));
+
+// basically everything except border/shadow
+const PALETTE_OTHER = paletteForColors(
+  _.pick(
+    colors,
+    ...TEXT_COLOR_NAMES,
+    ...BACKGROUND_COLOR_NAMES,
+    ...PRIMARY_AND_SECONDARY_NAMES,
+    ...SEMANTIC_NAMES,
+  ),
+);
+
+function paletteForCSSProperty(property) {
+  if (property) {
+    if (property === "color" || /text|font/i.test(property)) {
+      return PALETTE_FOREGROUND;
+    } else if (/bg|background/i.test(property)) {
+      return PALETTE_BACKGROUND;
+    } else if (/border/i.test(property)) {
+      return PALETTE_BORDER;
+    } else if (/shadow/i.test(property)) {
+      return PALETTE_SHADOW;
+    }
+  }
+  if (property != undefined) {
+    console.log("unknown pallet for property", property);
+  }
+  return PALETTE_OTHER;
+}
+
+function getBestCandidate(color, palette) {
+  const closest = colorDiff.closest(
+    { R: color.red(), G: color.green(), B: color.blue() },
+    palette,
+  );
+  let bestName = closest.name;
+  let bestColor = closest.color;
+  if (color.alpha() < 1) {
+    bestColor = bestColor.alpha(color.alpha());
+  }
+  return [bestName, bestColor];
+}
+
+function toJSValue(newColorName, newColor) {
+  if (newColor.alpha() < 1) {
+    return newColor.string();
+  } else {
+    return newColor.hex();
+  }
+}
+
+function toCSSValue(newColorName, newColor) {
+  if (newColor.alpha() < 1) {
+    return `color(var(${varForName(newColorName)}) alpha(-${Math.round(
+      100 * (1 - newColor.alpha()),
+    )}%))`;
+  } else {
+    return `var(${varForName(newColorName)})`;
+  }
+}
+
+function lineAtIndex(lines, index) {
+  let charIndex = 0;
+  for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
+    charIndex += lines[lineIndex].length + 1;
+    if (charIndex >= index) {
+      return lines[lineIndex];
+    }
+  }
+}
+
+function lineUpToIndex(lines, index) {
+  let charIndex = 0;
+  for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
+    const lineStart = charIndex;
+    charIndex += lines[lineIndex].length + 1;
+    if (charIndex >= index) {
+      return lines[lineIndex].slice(0, index - lineStart);
+    }
+  }
+}
+
+function cssPropertyAtIndex(lines, index) {
+  const line = lineAtIndex(lines, index);
+  const prefix = lineUpToIndex(lines, index);
+  if (line) {
+    const match =
+      // matches property names at the beginning of the line
+      line.match(/^\s*([a-zA-Z0-9-]+):/) ||
+      // matches property names leading up to the rule value
+      prefix.match(/(^|[^a-zA-Z0-9-])([a-zA-Z0-9-]+)\s*:\s*"?$/);
+    if (match) {
+      return match[1].trim();
+    } else {
+      console.warn("no property", line);
+    }
+  } else {
+    console.warn("no line at that index! this should not happen");
+  }
+}
+
+function replaceCSSColorValues(content) {
+  const lines = content.split("\n");
+  return content.replace(COLOR_REGEX, (color, index) => {
+    const palette = paletteForCSSProperty(cssPropertyAtIndex(lines, index));
+    const [newColorName, newColor] = getBestCandidate(Color(color), palette);
+    return toCSSValue(newColorName, newColor);
+  });
+}
+
+function replaceJSColorValues(content) {
+  if (COLOR_REGEX.test(content)) {
+    // console.log("processing");
+    return replaceStrings(content, COLOR_REGEX, (value, propertyName) => {
+      const palette = paletteForCSSProperty(propertyName);
+      const [newColorName, newColor] = getBestCandidate(Color(value), palette);
+      // console.log(value, propertyName, "=>", newColorName);
+      // return j.identifier(newColorName.replace(/\W/g, "_"));
+      // return j.stringLiteral(toJSValue(newColorName, newColor));
+      return j.memberExpression(
+        j.identifier("colors"),
+        /\W/.test(newColorName)
+          ? j.literal(newColorName)
+          : j.identifier(newColorName),
+      );
+    });
+  } else {
+    // console.log("skipping");
+    return content;
+  }
+}
+
+function replaceCSSColorVariables(content) {
+  const lines = content.split("\n");
+  return content.replace(CSS_VAR_REGEX, (variable, index) => {
+    const match = variable.match(/^var\(--color-(.*)\)$/);
+    if (match && colors[match[1]]) {
+      // already references a color, don't change it
+      return variable;
+    }
+    const color = resolveCSSVariableColor(variable);
+    if (color) {
+      const palette = paletteForCSSProperty(cssPropertyAtIndex(lines, index));
+      const [newColorName, newColor] = getBestCandidate(Color(color), palette);
+      return toCSSValue(newColorName, newColor);
+    } else {
+      return variable;
+    }
+  });
+}
+
+function resolveCSSVariableColor(value) {
+  try {
+    if (value) {
+      if (COLOR_REGEX.test(value)) {
+        return Color(value);
+      }
+      const colorVarMatch = value.match(CSS_COLOR_VAR_REGEX);
+      if (colorVarMatch) {
+        const color = resolveCSSVariableColor(cssVariables[colorVarMatch[1]]);
+        if (color) {
+          const shade = parseFloat(colorVarMatch[2]) / 100;
+          return Color(color).mix(Color("black"), shade);
+        }
+      }
+      const varMatch = value.match(CSS_SIMPLE_VAR_REGEX);
+      if (varMatch) {
+        const color = resolveCSSVariableColor(cssVariables[varMatch[1]]);
+        if (color) {
+          return color;
+        }
+      }
+    }
+  } catch (e) {
+    console.warn(e);
+  }
+  return null;
+}
+
+function processFiles(files) {
+  for (const file of files) {
+    let content = fs.readFileSync(file, "utf-8");
+    try {
+      if (/\.css/.test(file)) {
+        content = replaceCSSColorVariables(replaceCSSColorValues(content));
+      } else if (/\.jsx?/.test(file)) {
+        let newContent = replaceJSColorValues(content);
+        if (newContent !== content && !/\/colors.js/.test(file)) {
+          newContent = ensureHasColorsImport(newContent);
+        }
+        content = newContent;
+      } else {
+        console.warn("unknown file type", file);
+      }
+      fs.writeFileSync(file, content);
+    } catch (e) {
+      console.log("failed to process", file, e);
+    }
+  }
+
+  // do this last so we don't replace them
+  prependCSSVariablesBlock();
+  prependJSVariablesBlock();
+}
+
+function ensureHasColorsImport(content) {
+  // TODO: implement
+  return content;
+}
+
+function prependCSSVariablesBlock() {
+  const colorsVarsBlock = `
+/* NOTE: DO NOT ADD COLORS WITHOUT EXTREMELY GOOD REASON AND DESIGN REVIEW
+ * NOTE: KEEP SYNCRONIZED WITH COLORS.JS
+ */
+:root {
+${Object.entries(colors)
+    .map(([name, color]) => `  ${varForName(name)}: ${color};`)
+    .join("\n")}
+}\n\n`;
+
+  const content = fs.readFileSync(COLORS_CSS_PATH, "utf-8");
+  if (content.indexOf("NOTE: DO NOT ADD COLORS") < 0) {
+    fs.writeFileSync(COLORS_CSS_PATH, colorsVarsBlock + content);
+  }
+}
+
+function prependJSVariablesBlock() {
+  // TODO: remove window.colors and inject `import colors from "metabase/lib/colors";` in each file where it's required
+  const colorsVarsBlock = `
+// NOTE: DO NOT ADD COLORS WITHOUT EXTREMELY GOOD REASON AND DESIGN REVIEW
+// NOTE: KEEP SYNCRONIZED WITH COLORS.CSS
+const colors = window.colors = ${JSON.stringify(colors, null, 2)};
+export default colors;\n\n`;
+
+  const content = fs.readFileSync(COLORS_JS_PATH, "utf-8");
+  if (content.indexOf("NOTE: DO NOT ADD COLORS") < 0) {
+    const anchor = "export const brand = ";
+    fs.writeFileSync(
+      COLORS_JS_PATH,
+      content.replace(anchor, colorsVarsBlock + anchor),
+    );
+  }
+}
+
+function run() {
+  const fileGlob = process.argv[2] || FILE_GLOB;
+  glob(
+    path.join(__dirname, "..", fileGlob),
+    { ignore: FILE_GLOB_IGNORE },
+    (err, files) => {
+      if (err) {
+        console.error(err);
+      } else {
+        processFiles(files);
+      }
+    },
+  );
+}
+
+run();
diff --git a/bin/lib/codemod.js b/bin/lib/codemod.js
new file mode 100644
index 0000000000000000000000000000000000000000..7ef4bfe676fd7181cf2f41478938fcc0fd5d0d5c
--- /dev/null
+++ b/bin/lib/codemod.js
@@ -0,0 +1,140 @@
+const j = require("jscodeshift");
+
+function getPropertyName(path) {
+  const parent = path.parentPath.value;
+  if (parent.type === "Property") {
+    if (parent.key.type === "Identifier") {
+      return parent.key.name;
+    } else if (parent.key.type === "Literal") {
+      return parent.key.value;
+    }
+  }
+  if (parent.type === "JSXAttribute") {
+    return parent.name.name;
+  }
+}
+
+function splitMatches(string, regex) {
+  const results = [];
+  let current = 0;
+  string.replace(regex, (match, index) => {
+    results.push(string.slice(current, index));
+    results.push(match);
+    current = index + match.length;
+  });
+  results.push(string.slice(current));
+  return results;
+}
+
+function extractMatches(
+  string,
+  regex,
+  replacer = str => j.stringLiteral(str),
+  quasis = [],
+  expressions = [],
+) {
+  const components = splitMatches(string, regex);
+  for (let cIndex = 0; cIndex < components.length; cIndex++) {
+    if (cIndex % 2) {
+      expressions.push(replacer(components[cIndex]));
+    } else {
+      const quasi = j.templateElement(
+        { cooked: components[cIndex], raw: components[cIndex] },
+        false,
+      );
+      quasis.push(quasi);
+    }
+  }
+  return components.length > 1;
+}
+
+function makeTemplate(quasis, expressions) {
+  if (
+    quasis.length === 2 &&
+    quasis[0].value.raw === "" &&
+    quasis[1].value.raw === ""
+  ) {
+    return expressions[0];
+  } else {
+    return j.templateLiteral(quasis, expressions);
+  }
+}
+
+exports.replaceStrings = function replaceStrings(source, regex, replacer) {
+  const root = j(source, { parser: require("flow-parser") });
+  root
+    .find(j.Literal)
+    .filter(
+      path =>
+        // match only string literals
+        typeof path.value.value === "string" &&
+        // don't match strings that are property keys
+        !(
+          path.parentPath.value.type && path.parentPath.value.key == path.value
+        ),
+    )
+    .replaceWith(path => {
+      const stringValue = path.value.value;
+      const propertyName = getPropertyName(path);
+
+      const quasis = [];
+      const expressions = [];
+      if (
+        extractMatches(
+          stringValue,
+          regex,
+          str => replacer(str, propertyName),
+          quasis,
+          expressions,
+        )
+      ) {
+        const value = makeTemplate(quasis, expressions);
+        // wrap non string literals in JSXExpressionContainer
+        if (
+          path.parentPath.value.type === "JSXAttribute" &&
+          (value.type !== "Literal" || typeof value.value !== "string")
+        ) {
+          return j.jsxExpressionContainer(value);
+        } else {
+          return value;
+        }
+      } else {
+        return path.value;
+      }
+    });
+  root
+    .find(j.TemplateLiteral)
+    // .filter(path => typeof path.value.value.raw === "string")
+    .replaceWith(path => {
+      const propertyName = getPropertyName(path);
+
+      let modified = false;
+      const quasis = [];
+      const expressions = [];
+
+      for (let qIndex = 0; qIndex < path.value.quasis.length; qIndex++) {
+        const quasiValue = path.value.quasis[qIndex].value.raw;
+        if (
+          extractMatches(
+            quasiValue,
+            regex,
+            str => replacer(str, propertyName),
+            quasis,
+            expressions,
+          )
+        ) {
+          modified = true;
+        }
+        if (qIndex < path.value.expressions.length) {
+          expressions.push(path.value.expressions[qIndex]);
+        }
+      }
+
+      if (modified) {
+        return makeTemplate(quasis, expressions);
+      } else {
+        return path.value;
+      }
+    });
+  return root.toSource();
+};
diff --git a/bin/version b/bin/version
index 38d1d635c90a4e2cdf905b3c89affeaa58d547e0..d23c5f3eb2923538fdc7c0fb4486532c8df8f81a 100755
--- a/bin/version
+++ b/bin/version
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 
-VERSION="v0.30.0-snapshot"
+VERSION="v0.31.0-snapshot"
 
 # 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
diff --git a/circle.yml b/circle.yml
deleted file mode 100644
index 07a4df17fb09b6e0d17ee759e0043d9f7996bb3e..0000000000000000000000000000000000000000
--- a/circle.yml
+++ /dev/null
@@ -1,44 +0,0 @@
-machine:
-  timezone:
-    America/Los_Angeles
-  java:
-    version:
-      openjdk8
-  node:
-    version: 8.9.0
-  services:
-    - docker
-dependencies:
-  override:
-    - lein deps
-    - npm install -g 'yarn@>=0.16.0'
-    # Forces the Sauce Connect binary to be downloaded during dependencies phase so it's cached
-    - SAUCE_CONNECT_DOWNLOAD_ON_INSTALL=true yarn
-    - mkdir plugins
-  cache_directories:
-    - "~/.yarn"
-    - "~/.yarn-cache"
-database:
-  post:
-    # MySQL doesn't load named timezone information automatically, you have to run this command to load it
-    # TODO - we only really need to do this step if we're testing against MySQL
-    - mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u ubuntu mysql
-test:
-  override:
-    - ./bin/ci:
-        parallel: true
-deployment:
-  master:
-    branch: master
-    commands:
-      - ./bin/deploy-webhook $DEPLOY_WEBHOOK
-general:
-  artifacts:
-    - target/uberjar/metabase.jar
-    - screenshots
-experimental:
-  notify:
-    branches:
-      only:
-        - master
-        - /release-.*/
diff --git a/docs/administration-guide/01-managing-databases.md b/docs/administration-guide/01-managing-databases.md
index 34a25b2035e0fd39da0aba6d9c33d0efafe0180c..acea451c050a978256fb74898dede4bf81415883 100644
--- a/docs/administration-guide/01-managing-databases.md
+++ b/docs/administration-guide/01-managing-databases.md
@@ -1,10 +1,10 @@
 
 ## Managing Databases
-If you already connected your database during the installation, you’ve probably a covered a lot of this info. But if you need to add another database or manage the settings of the one you already have connected, just click the circle with your initials in the top right of Metabase and select the **Admin Panel**.
+If you already connected your database during the installation, you’ve probably a covered a lot of this territory. But if you need to add another database or manage the settings of one you've already have connected, just click the settings icon in the top right of Metabase and select **Admin**.
 
 ![profiledropdown](images/ProfileDropdown.png)
 
-Cool, now you’re in the admin panel. Next, select **Databases** from the menu bar at the top of the screen to see your databases.
+Cool, now you’re in the administration section of Metabase. Next, select **Databases** from the menu bar at the top of the screen to see your databases.
 
 ### Adding a Database Connection
 
diff --git a/docs/administration-guide/04-managing-users.md b/docs/administration-guide/04-managing-users.md
index 877b8573fe73a14f2dbb8cdc1da5455418cfedf3..ee0f2e8e0fbcb9a7e89168566d853d0bc100a3a0 100644
--- a/docs/administration-guide/04-managing-users.md
+++ b/docs/administration-guide/04-managing-users.md
@@ -1,6 +1,6 @@
 ## Managing User Accounts
 
-To start managing users, first go to the **Admin Panel** by clicking on the dropdown menu in the top right of Metabase and selecting Admin Panel.
+To start managing users, first go to the **Metabase Admin** section by clicking on the dropdown menu in the top right of Metabase and selecting Admin.
 
 ![Profile dropdown](images/ProfileDropdown.png)
 
@@ -13,11 +13,13 @@ To add a new user account, click **Add person** in the upper right corner. You
 
 If you’ve already [configured Metabase to use email](02-setting-up-email.md), Metabase will send the new user an invite email. Otherwise, it’ll give you a temporary password that you’ll have to send to the person you’re inviting by hand.
 
-### Removing a user
-To delete a user's account, click on the three dots icon on the right of a user’s row and select **Remove** from the dropdown. Deleting an account will mark it as inactive and prevent it from being used in the future - but it *won’t* delete that user's saved questions or dashboards.
+### Deactivating a user
+To deactivate a user's account, click on the three dots icon on the right of a user’s row and select **Deactivate** from the dropdown. Deactivating an account will mark it as inactive and prevent the user from logging in - but it *won’t* delete that user's saved questions or dashboards.
 
 ![Remove a user](images/RemoveUser.png)
 
+To reactivate a deactivated user, click on the Deactivated tab at the top of the list of your users to see the list of inactive users. Click on the icon on the far right to reactivate that user, allowing them to log in to Metabase again.
+
 ### Editing a user
 You can edit a user’s name and email address by clicking the three dots icon and choosing **Edit Details**. Note: be careful when changing a user’s email address, because *this will change the address they’ll use to log in to Metabase*.
 
@@ -25,9 +27,14 @@ You can edit a user’s name and email address by clicking the three dots icon a
 A user can always reset their password using the forgot password link on the login screen, but if you want to do this for them, just click the three dots icon and choose Reset Password. If you haven’t configured your email settings yet, you’ll be given a temporary password that you’ll have to share with that user. Otherwise, they’ll receive a password reset email.
 
 ### Changing a user’s role
-Right now, the only role a user can have is either User or Admin. The only difference is that Admins can access the Admin Panel and make changes there.
+Right now, the only special role a user can have is Admin. The only difference is that Admins can access the Admin Panel and make changes there, and can set [permissions on collections](06-collections.md).
+
+To make a user an admin, click on the Groups dropdown and click the check mark next to the Administrators group.
+
+### Adding users to Groups
+Adding users to groups allow you to assign [data access](05-setting-permissions.md) and [collection permissions](06-collections.md) to them. The next two articles in this guide will teach you how to set those permissions, but to add users to one or more groups, just click the Groups dropdown and click the checkboxes next to the group(s) you want to add the user to.
 
-To change a user’s role, just click on it to open a dropdown and make your selection.
+Check out this article for more on [creating and managing user groups](05-setting-permissions.md).
 
 ---
 
diff --git a/docs/administration-guide/05-setting-permissions.md b/docs/administration-guide/05-setting-permissions.md
index d62420add539ef26acb48a5e5f44a85828f2aa2d..ad54d52344015337171f499373308019d25265f7 100644
--- a/docs/administration-guide/05-setting-permissions.md
+++ b/docs/administration-guide/05-setting-permissions.md
@@ -8,6 +8,8 @@ Metabase uses a group-based approach to set permissions and restrictions on your
 
 A user can be a member of multiple groups, and if one of the groups they’re in has access to a particular database or table, but another group they’re a member of does not, then they **will** have access to that database.
 
+In addition to setting permissions on your databases and tables, you can also [set access permissions on the collections](06-collections.md) where your dashboards, questions, and pulses are saved. Collection permissions are not set from the Admin section; set them by clicking on the edit/pencil icon in the top-right of the screen when viewing a collection.
+
 ### Groups
 
 To view and manage your groups, go to the Admin Panel, click on the People section, and then click on Groups from the side menu.
@@ -20,7 +22,10 @@ You’ll notice that you already have two default groups: Administrators and All
 
 You’ll also see that you’re a member of the **Administrators** group — that’s why you were able to go to the Admin Panel in the first place. So, to make someone an admin of Metabase you just need to add them to this group. Metabase admins can log into the Admin Panel and make changes there, and they always have unrestricted access to all data that you have in your Metabase instance. So be careful who you add to the Administrator group!
 
-The **All Users** group is another special one. Every Metabase user is always a  member of this group, though they can also be a member of as many other groups as you want. We recommend using the All Users group as a way to set default access levels for new Metabase users. If you have [Google single sign-on](10-single-sign-on.md) enabled, new users who join that way will be automatically added to the All Users group. (**Important note:** as we mentioned above, a user is given the *most permissive* setting she has for a given database/schema/table across *all* groups she is in. Because of that, it is important that your All Users group should never have *greater* access for an item than a group for which you're trying to restrict access — otherwise the more permissive setting will win out.)
+The **All Users** group is another special one. Every Metabase user is always a  member of this group, though they can also be a member of as many other groups as you want. We recommend using the All Users group as a way to set default access levels for new Metabase users. If you have [Google single sign-on](10-single-sign-on.md) enabled, new users who join that way will be automatically added to the All Users group.
+
+#### An important note on the All Users group
+As we mentioned above, a user is given the *most permissive* setting she has for a given database/schema/table across *all* groups she is in. Because of that, it is important that your All Users group should never have *greater* access for an item than a group for which you're trying to restrict access — otherwise the more permissive setting will win out. This goes for both data access as well as [collection permission](06-collections.md) settings.
 
 If you’ve set up the [Slack integration](09-setting-up-slack.md) and enabled [Metabot](../users-guide/11-metabot.md), you’ll also see a special **Metabot** group, which will allow you to restrict which questions your users will be able to access in Slack via Metabot.
 
diff --git a/docs/administration-guide/06-collections.md b/docs/administration-guide/06-collections.md
index aa807a3c9b373aa1a814f94bcefe4a5bd692b9f6..f9f92c831902068d4517fe6607b2a5c3272a4a39 100644
--- a/docs/administration-guide/06-collections.md
+++ b/docs/administration-guide/06-collections.md
@@ -1,35 +1,61 @@
 ## Creating Collections for Your Saved Questions
 ---
 
-Collections are a great way to organize your saved questions and decide who gets to see and edit things. Collections could be things like, "Important Metrics," "Marketing KPIs," or "Questions about users." Multiple [user groups](05-setting-permissions.md) can be given access to the same collections, so we don't necessarily recommend naming collections after user groups.
+![Collection detail](images/collections/collection-detail.png)
+
+Collections are a great way to organize your dashboards, saved questions, and pulses, and to decide who gets to see and edit things. Collections could be things like, "Important Metrics," "Product Team," "Marketing KPIs," or "Questions about users." Collections can even contain other collections, allowing you to create an organizational structure that fits your team. You can also choose which user groups should have what level of access to your collections (more on that below).
+
+Metabase starts out with a default top-level collection which is called "Our analytics," which every other collection is saved inside of.
 
 This page will teach you how to create and manage your collections. For more information on organizing saved questions and using collections, [check out this section of the User's Guide](../users-guide/06-sharing-answers.md).
 
 ### Creating and editing collections
-Only administrators of Metabase can create and edit collections. From the Questions section of Metabase, click on the `Create a collection` button. Give your collection a name, choose a color for it, and give it a description if you'd like.
+If a user has Curate access for a collection, they can create new sub-collections inside it and edit the contents of the collection. From the detail view of any collection, click on the `Create a collection` button to make a new one. Give your collection a name, choose where it should live, and give it a description if you'd like.
+
+![Create collection](images/collections/create-collection.png)
+
+By default, new collections will have the same permissions settings as the collection it was created in (its "parent" collection), but you can change those settings from the Edit menu.
+
+### Pinning things in collections
+![Pins](images/collections/pinned-items.png)
+
+One great feature in Metabase is that you can pin the most important couple of items in each of your collections to the top. Pinning an item in a collection turns it into a big, eye-catching card that will help make sure that folks who are browsing your Metabase instance will always know what's most important.
 
-![Permissions empty state](images/collections/collections-empty-state.png)
+Any user with curate permissions for a collection can pin items in it, making it easy to delegate curation responsibilities to other members of your team. To pin something, you can either click and drag it to the top of the page, or click on its menu and choose the pin action. (Note that collections themselves can't be pinned.)
 
 ### Setting permissions for collections
-Collection permissions are similar to data permissions. Rather than going to the Admin Panel, you set permissions on collections by clicking on the lock icon in the top-right of the Questions screen or the top-right of a collection screen.
+Collection permissions are similar to [data access permissions](05-setting-permissions.md). Rather than going to the Admin Panel, you set permissions on collections by clicking on the sharing icon in the top-right of the screen while viewing the collection and clicking on `Edit permissions`. Only Administrators can edit collection permissions. Each [user group](05-setting-permissions.md) can have either View, Curate, or No access to a collection:
 
-![Permissions grid](images/collections/permission-grid.png)
+- **View access:** the user can see all the questions, dashboards, and pulses in the collection. If the user does not have permission to view some or all of the questions included in a given dashboard or pulse then those questions will not be visible to them; but any questions that are saved in this collection *will* be visible to them, *even if the user doesn't have access to the underlying data used to in the question.**
+- **Curate access:** the user can edit, move, and archive items saved in this collection, and can save or move new items into it. They can also create new sub-collections within this collection. In order to archive a sub-collection within this collection, they'll need to have Curate access for it and any and all collections within it.
+- **No access:** the user won't see this collection listed, and doesn't have access to any of the items saved within it.
 
-You'll see a table with your user groups along the top and all your collections down along the left. A user group can have View access, Curate access, or no access to a given collection.
+![Permissions](images/collections/collection-permissions.png)
 
-- View access: can see all the questions in the collection, **even if the user doesn't have access to the underlying data used to create the question.**
-- Curate access: can additionally move questions in or out of the collection, and edit the questions in the collection.
-- No access: won't see the collection listed on the Questions page, and can't see questions from this collection in dashboards or when creating a Pulse.
+If you want to see the bigger picture of what permissions your user groups have for all your collections, just click the link that says `See all collection permissions`. You'll see a table with your user groups along the top and all your collections down along the left. Click the `View collections` link under any collection that contains more collections to zoom in and see its contents:
 
-Just like with data access permissions, collection permissions are *additive*, meaning that if a user belongs to more than one group, if one of their groups has a more restrictive setting for a collection than another one of their groups, they'll be given the *more permissive* setting. This is especially important to remember when dealing with the All Users group: since all users are members of this group, if you give the All Users group Curate access to a collection, then *all* users will be given that access for that collection, even if they also belong to a group with *less* access than that.
+![Full permissions grid](images/collections/permission-grid.png)
 
-### The "Everything Else" section
-If a question isn't saved within a collection, it will be placed in the Everything Else section of the main Questions page. **All your Metabase users can see questions in this section**, provided they have data access permission.
+Just like with data access permissions, collection permissions are *additive*, meaning that if a user belongs to more than one group, if one of their groups has a more restrictive setting for a collection than another one of their groups, they'll be given the *more permissive* setting. This is especially important to remember when dealing with the All Users group: since all users are members of this group, if you give the All Users group Curate access to a collection, then *all* users will be given Curate access for that collection, even if they also belong to a group with *less* access than that.
+
+### Permissions and sub-collections
+One nuance with how collections permissions work has to do with sub-collections. A user group can be given access to a collection located somewhere within one more more sub-collections *without* having to have access to every collection "above" it. E.g., if a user group had access to the "Super Secret Collection" that's saved several layers deep within a "Marketing" collection that the group does *not* have access to, the "Super Secret Collection" would show up at the top-most level that the group *does* have access to.
+
+### Personal collections
+![Personal collections](images/collections/personal-collections.png)
+
+Each user has a personal collection where they're always allowed to save things, even if they don't have Curate permissions for any other collections. Administrators can see and edit the contents of every user's personal collection (even those belonging to other Administrators) by clicking on the "All personal collections" link from the "Our analytics" collection.
+
+A personal collection works just like any other collection except that its permissions can't be changed. If a sub-collection within a personal collection is moved to a different collection, it will inherit the permissions of that collection.
+
+![Personal collection detail](images/collections/personal-collection-detail.png)
 
 ### Archiving collections
-You can archive collections similarly to how you can archive questions. Click the archive icon in the top-right of the collection screen to archive it. This will also archive all questions in the collection, and importantly it will also remove all of those questions from all dashboards and Pulses that use those questions. So be careful!
+Users with curate permission for a collection can archive collections. Click the edit icon in the top-right of the collection screen and select `Archive this collection` to archive it. This will also archive all questions, dashboards, pulses, and all other sub-collections and their contents. Importantly, this will also remove any archived questions from all dashboards and Pulses that use them.
+
+**Note:** the "Our analytics" collection and personal collections can't be archived.
 
-To restore a collection and its contents, click the `View Archive` icon in the top-right of the main Questions screen to see the archive, then hover over an item to reveal the `Unarchive` icon on the far right of the item. Questions within archived collections are not individually listed in the archive, so if you want to unarchive a specific question from an archived collection, you have to unarchive that whole collection.
+You can always *unarchive* things by clicking on the More menu from a collection and selecting `View the archive`, then clicking the un-archive button next to an archived item. Questions within archived collections are not individually listed in the archive, so if you want to unarchive a specific question from an archived collection, you have to unarchive that whole collection.
 
 ---
 
diff --git a/docs/administration-guide/10-single-sign-on.md b/docs/administration-guide/10-single-sign-on.md
index b235e7247a615a8b6ad9914c8d542c857d9c57fd..43f3920a78fe00ca129d74f0dae18d4d591b0bea 100644
--- a/docs/administration-guide/10-single-sign-on.md
+++ b/docs/administration-guide/10-single-sign-on.md
@@ -48,4 +48,4 @@ If you have user groups in Metabase you are using to control access, it is often
 ---
 
 ## Next: Authenticating with SAML
-If you use a SAML-based identity provider for SSO, [learn how to connect it to Metabase](16-authenticating-with-saml.md).
+If you use a SAML-based identity provider for SSO, [learn how to connect it to Metabase](16-authenticating-with-saml.md).
\ No newline at end of file
diff --git a/docs/administration-guide/11-getting-started-guide.md b/docs/administration-guide/11-getting-started-guide.md
index 1ed5c5fd879a1ee3eec37a0f53b517e09e2ff6d4..e4cbfcf74d2a269ba09bd74dbb85bb1b89fc54e7 100644
--- a/docs/administration-guide/11-getting-started-guide.md
+++ b/docs/administration-guide/11-getting-started-guide.md
@@ -1,23 +1,3 @@
-## Creating a Getting Started Guide
+## Getting Started Guide
 
-In most places we’ve worked, there’s typically an email or a Google doc that that gets forwarded around to new hires that describes how to use the analytics systems available. Some more sophisticated setups use an internal wiki or other website that has an inventory of what’s available. But we believe that the best way to keep information like this current is to have it be documented in the application itself. Metabase now lets you create a cheatsheet to help new users know which dashboards, metrics, and reports are the most important. It also provides a place to document caveats for use, advice on who to contact for help, and more.
-
-To get started, click on the `Guide` link in the main nav. You'll see a screen like this:
-
-![Blank guide](images/gsg/blank-guide.png)
-
-Before you've even created your guide, this page gives you some links that you can use to explore the data you have in Metabase. But for now, click the button to begin making your guide. Now you'll see a list of sections that you can include in your guide:
-
-![Sections](images/gsg/sections.png)
-
-You can highlight your company's most important dashboard, [metrics](07-segments-and-metrics.md) that you commonly refer to (and the dimensions by which they're most often grouped), and tables and [segments](07-segments-and-metrics.md) that are useful or interesting. There's also a place to write a little bit more about "gotchas" or caveats with your data that your users should know about before they start exploring things and drawing conclusions. Lastly, you can optionally include an email address for your users to contact in case they're still confused about things.
-
-If you click on a section, it'll expand and let you select the items that you want to include in that section:
-
-![Picking items](images/gsg/pick-items.png)
-
-To remove an item you've added, just click the X icon in the top-right of the item. When you're all done adding things to your guide, click the `Save` button in the blue bar at the top of the screen. To make edits to your guide, simply click the `Edit` link in the top-right of the guide. Once you click `Save`, you'll see your brand new Getting Started Guide!
-
-![Finished Guide](images/gsg/finished-guide.png)
-
-Clicking on the title of an item you've included in your guide will take you to the item itself. Clicking on `Learn more` will take you to a more detailed entry about that item so you can explore it and related items in more detail.
+The Getting Started Guide was removed from Metabase in version 0.30. Instead, a great way of helping your teammates find their way around Metabase is by pinning the most important dashboards or questions in each of your collections to make it clear what's most important.
diff --git a/docs/administration-guide/images/ProfileDropdown.png b/docs/administration-guide/images/ProfileDropdown.png
index fff0346f64e1494bff894a91bbe4dd08a022c24d..0ee4f72d9c5c5e43ab6660cf3dcf174af0d9dc7b 100644
Binary files a/docs/administration-guide/images/ProfileDropdown.png and b/docs/administration-guide/images/ProfileDropdown.png differ
diff --git a/docs/administration-guide/images/RemoveUser.png b/docs/administration-guide/images/RemoveUser.png
index c453c5138d22a138a1bcdfeee91d5742c5371c4b..89eafb83b49c496f29464cd03512f88c7b6ce786 100644
Binary files a/docs/administration-guide/images/RemoveUser.png and b/docs/administration-guide/images/RemoveUser.png differ
diff --git a/docs/administration-guide/images/collections/collection-detail.png b/docs/administration-guide/images/collections/collection-detail.png
new file mode 100644
index 0000000000000000000000000000000000000000..5dd7fe9277762c16d3f42b6ee64c0a576450f582
Binary files /dev/null and b/docs/administration-guide/images/collections/collection-detail.png differ
diff --git a/docs/administration-guide/images/collections/collection-permissions.png b/docs/administration-guide/images/collections/collection-permissions.png
new file mode 100644
index 0000000000000000000000000000000000000000..f03d4b6c657825ee58fa43d50bf96fcece2929b3
Binary files /dev/null and b/docs/administration-guide/images/collections/collection-permissions.png differ
diff --git a/docs/administration-guide/images/collections/collections-empty-state.png b/docs/administration-guide/images/collections/collections-empty-state.png
deleted file mode 100644
index 6fb4d376bdd11cfb577c1d1749f05328a1f11384..0000000000000000000000000000000000000000
Binary files a/docs/administration-guide/images/collections/collections-empty-state.png and /dev/null differ
diff --git a/docs/administration-guide/images/collections/create-collection.png b/docs/administration-guide/images/collections/create-collection.png
new file mode 100644
index 0000000000000000000000000000000000000000..390c573dad3099930c690339d510542a923d8f9a
Binary files /dev/null and b/docs/administration-guide/images/collections/create-collection.png differ
diff --git a/docs/administration-guide/images/collections/permission-grid.png b/docs/administration-guide/images/collections/permission-grid.png
index f8190d72b12f4bf51429852b28ac61173b7de6e7..673084902d24b20f129ac32c1118bae5f0f45d2f 100644
Binary files a/docs/administration-guide/images/collections/permission-grid.png and b/docs/administration-guide/images/collections/permission-grid.png differ
diff --git a/docs/administration-guide/images/collections/personal-collection-detail.png b/docs/administration-guide/images/collections/personal-collection-detail.png
new file mode 100644
index 0000000000000000000000000000000000000000..a167776a850a4b814a772f7d315ff758879ff295
Binary files /dev/null and b/docs/administration-guide/images/collections/personal-collection-detail.png differ
diff --git a/docs/administration-guide/images/collections/personal-collections.png b/docs/administration-guide/images/collections/personal-collections.png
new file mode 100644
index 0000000000000000000000000000000000000000..bd557deaa6a1ff9c2e4e142c5f6a4e93d8785b16
Binary files /dev/null and b/docs/administration-guide/images/collections/personal-collections.png differ
diff --git a/docs/administration-guide/images/collections/pinned-items.png b/docs/administration-guide/images/collections/pinned-items.png
new file mode 100644
index 0000000000000000000000000000000000000000..0453c64cb496530ad7ade11c241f4d49e772f5a8
Binary files /dev/null and b/docs/administration-guide/images/collections/pinned-items.png differ
diff --git a/docs/api-documentation.md b/docs/api-documentation.md
index b1a42b61809a4d63560777ab9dfe3fc788ca5a77..d9adda78e32a89e2df74f973b56ad2521dda8938 100644
--- a/docs/api-documentation.md
+++ b/docs/api-documentation.md
@@ -1,4 +1,4 @@
-# API Documentation for Metabase v0.27.0-snapshot
+# API Documentation for Metabase v0.30.0-snapshot
 
 ## `GET /api/activity/`
 
@@ -12,7 +12,7 @@ Get the list of 10 things the current user has been viewing most recently.
 
 ## `DELETE /api/alert/:id`
 
-Remove an alert
+Delete an Alert. (DEPRECATED -- don't delete a Alert anymore -- archive it instead.)
 
 ##### PARAMS:
 
@@ -23,6 +23,10 @@ Remove an alert
 
 Fetch all alerts
 
+##### PARAMS:
+
+*  **`archived`** value may be nil, or if non-nil, value must be a valid boolean string ('true' or 'false').
+
 
 ## `GET /api/alert/question/:id`
 
@@ -35,13 +39,13 @@ Fetch all questions for the given question (`Card`) id
 
 ## `POST /api/alert/`
 
-Create a new alert (`Pulse`)
+Create a new Alert.
 
 ##### PARAMS:
 
 *  **`alert_condition`** value must be one of: `goal`, `rows`.
 
-*  **`card`** value must be a map.
+*  **`card`** value must be a map with the keys `id`, `include_csv`, and `include_xls`.
 
 *  **`channels`** value must be an array. Each value must be a map. The array cannot be empty.
 
@@ -49,7 +53,7 @@ Create a new alert (`Pulse`)
 
 *  **`alert_above_goal`** value may be nil, or if non-nil, value must be a boolean.
 
-*  **`req`** 
+*  **`new-alert-request-body`** 
 
 
 ## `PUT /api/alert/:id`
@@ -60,17 +64,19 @@ Update a `Alert` with ID.
 
 *  **`id`** 
 
-*  **`alert_condition`** value must be one of: `goal`, `rows`.
+*  **`alert_condition`** value may be nil, or if non-nil, value must be one of: `goal`, `rows`.
 
-*  **`card`** value must be a map.
+*  **`card`** value may be nil, or if non-nil, value must be a map with the keys `id`, `include_csv`, and `include_xls`.
 
-*  **`channels`** value must be an array. Each value must be a map. The array cannot be empty.
+*  **`channels`** value may be nil, or if non-nil, value must be an array. Each value must be a map. The array cannot be empty.
 
-*  **`alert_first_only`** value must be a boolean.
+*  **`alert_first_only`** value may be nil, or if non-nil, value must be a boolean.
 
 *  **`alert_above_goal`** value may be nil, or if non-nil, value must be a boolean.
 
-*  **`req`** 
+*  **`archived`** value may be nil, or if non-nil, value must be a boolean.
+
+*  **`alert-updates`** 
 
 
 ## `PUT /api/alert/:id/unsubscribe`
@@ -82,18 +88,166 @@ Unsubscribes a user from the given alert
 *  **`id`** 
 
 
-## `GET /api/async/:id`
+## `GET /api/automagic-dashboards/:entity/:entity-id-or-query`
 
-Get result of async computation job with ID.
+Return an automagic dashboard for entity `entity` with id `ìd`.
 
 ##### PARAMS:
 
-*  **`id`** 
+*  **`entity`** Invalid entity type
+
+*  **`entity-id-or-query`** 
+
+*  **`show`** invalid show value
+
+
+## `GET /api/automagic-dashboards/:entity/:entity-id-or-query/cell/:cell-query`
+
+Return an automagic dashboard analyzing cell in  automagic dashboard for entity `entity`
+   defined by
+   query `cell-querry`.
+
+##### PARAMS:
+
+*  **`entity`** Invalid entity type
+
+*  **`entity-id-or-query`** 
+
+*  **`cell-query`** value couldn't be parsed as base64 encoded JSON
+
+*  **`show`** invalid show value
+
+
+## `GET /api/automagic-dashboards/:entity/:entity-id-or-query/cell/:cell-query/compare/:comparison-entity/:comparison-entity-id-or-query`
+
+Return an automagic comparison dashboard for cell in automagic dashboard for entity `entity`
+   with id `ìd` defined by query `cell-querry`; compared with entity `comparison-entity` with id
+   `comparison-entity-id-or-query.`.
+
+##### PARAMS:
+
+*  **`entity`** Invalid entity type
+
+*  **`entity-id-or-query`** 
+
+*  **`cell-query`** value couldn't be parsed as base64 encoded JSON
+
+*  **`show`** invalid show value
+
+*  **`comparison-entity`** Invalid comparison entity type. Can only be one of "table", "segment", or "adhoc"
+
+*  **`comparison-entity-id-or-query`** 
+
+
+## `GET /api/automagic-dashboards/:entity/:entity-id-or-query/cell/:cell-query/rule/:prefix/:rule`
+
+Return an automagic dashboard analyzing cell in question  with id `id` defined by
+   query `cell-querry` using rule `rule`.
+
+##### PARAMS:
+
+*  **`entity`** Invalid entity type
+
+*  **`entity-id-or-query`** 
+
+*  **`cell-query`** value couldn't be parsed as base64 encoded JSON
+
+*  **`prefix`** invalid value for prefix
+
+*  **`rule`** invalid value for rule name
+
+*  **`show`** invalid show value
+
+
+## `GET /api/automagic-dashboards/:entity/:entity-id-or-query/cell/:cell-query/rule/:prefix/:rule/compare/:comparison-entity/:comparison-entity-id-or-query`
+
+Return an automagic comparison dashboard for cell in automagic dashboard for entity `entity`
+   with id `ìd` defined by query `cell-querry` using rule `rule`; compared with entity
+   `comparison-entity` with id `comparison-entity-id-or-query.`.
+
+##### PARAMS:
+
+*  **`entity`** Invalid entity type
+
+*  **`entity-id-or-query`** 
+
+*  **`cell-query`** value couldn't be parsed as base64 encoded JSON
+
+*  **`prefix`** invalid value for prefix
+
+*  **`rule`** invalid value for rule name
+
+*  **`show`** invalid show value
+
+*  **`comparison-entity`** Invalid comparison entity type. Can only be one of "table", "segment", or "adhoc"
+
+*  **`comparison-entity-id-or-query`** 
+
+
+## `GET /api/automagic-dashboards/:entity/:entity-id-or-query/compare/:comparison-entity/:comparison-entity-id-or-query`
+
+Return an automagic comparison dashboard for entity `entity` with id `ìd` compared with entity
+   `comparison-entity` with id `comparison-entity-id-or-query.`
+
+##### PARAMS:
+
+*  **`entity`** Invalid entity type
+
+*  **`entity-id-or-query`** 
+
+*  **`show`** invalid show value
 
+*  **`comparison-entity`** Invalid comparison entity type. Can only be one of "table", "segment", or "adhoc"
 
-## `GET /api/async/running-jobs`
+*  **`comparison-entity-id-or-query`** 
 
-Get all running jobs belonging to the current user.
+
+## `GET /api/automagic-dashboards/:entity/:entity-id-or-query/rule/:prefix/:rule`
+
+Return an automagic dashboard for entity `entity` with id `ìd` using rule `rule`.
+
+##### PARAMS:
+
+*  **`entity`** Invalid entity type
+
+*  **`entity-id-or-query`** 
+
+*  **`prefix`** invalid value for prefix
+
+*  **`rule`** invalid value for rule name
+
+*  **`show`** invalid show value
+
+
+## `GET /api/automagic-dashboards/:entity/:entity-id-or-query/rule/:prefix/:rule/compare/:comparison-entity/:comparison-entity-id-or-query`
+
+Return an automagic comparison dashboard for entity `entity` with id `ìd` using rule `rule`;
+   compared with entity `comparison-entity` with id `comparison-entity-id-or-query.`.
+
+##### PARAMS:
+
+*  **`entity`** Invalid entity type
+
+*  **`entity-id-or-query`** 
+
+*  **`prefix`** invalid value for prefix
+
+*  **`rule`** invalid value for rule name
+
+*  **`show`** invalid show value
+
+*  **`comparison-entity`** Invalid comparison entity type. Can only be one of "table", "segment", or "adhoc"
+
+*  **`comparison-entity-id-or-query`** 
+
+
+## `GET /api/automagic-dashboards/database/:id/candidates`
+
+Return a list of candidates for automagic dashboards orderd by interestingness.
+
+##### PARAMS:
+
+*  **`id`** 
 
 
 ## `DELETE /api/card/:card-id/favorite`
@@ -107,7 +261,7 @@ Unfavorite a Card.
 
 ## `DELETE /api/card/:card-id/public_link`
 
-Delete the publically-accessible link to this Card.
+Delete the publicly-accessible link to this Card.
 
 You must be a superuser to do this.
 
@@ -118,7 +272,7 @@ You must be a superuser to do this.
 
 ## `DELETE /api/card/:id`
 
-Delete a `Card`.
+Delete a Card. (DEPRECATED -- don't delete a Card anymore -- archive it instead.)
 
 ##### PARAMS:
 
@@ -127,31 +281,16 @@ Delete a `Card`.
 
 ## `GET /api/card/`
 
-Get all the `Cards`. Option filter param `f` can be used to change the set of Cards that are returned; default is
+Get all the Cards. Option filter param `f` can be used to change the set of Cards that are returned; default is
   `all`, but other options include `mine`, `fav`, `database`, `table`, `recent`, `popular`, and `archived`. See
   corresponding implementation functions above for the specific behavior of each filter option. :card_index:
 
-  Optionally filter cards by LABEL or COLLECTION slug. (COLLECTION can be a blank string, to signify cards with *no
-  collection* should be returned.)
-
-  NOTES:
-
-  *  Filtering by LABEL is considered *deprecated*, as `Labels` will be removed from an upcoming version of Metabase
-     in favor of `Collections`.
-  *  LABEL and COLLECTION params are mutually exclusive; if both are specified, LABEL will be ignored and Cards will
-     only be filtered by their `Collection`.
-  *  If no `Collection` exists with the slug COLLECTION, this endpoint will return a 404.
-
 ##### PARAMS:
 
 *  **`f`** value may be nil, or if non-nil, value must be one of: `all`, `archived`, `database`, `fav`, `mine`, `popular`, `recent`, `table`.
 
 *  **`model_id`** value may be nil, or if non-nil, value must be an integer greater than zero.
 
-*  **`label`** value may be nil, or if non-nil, value must be a non-blank string.
-
-*  **`collection`** value may be nil, or if non-nil, value must be a string.
-
 
 ## `GET /api/card/:id`
 
@@ -162,6 +301,15 @@ Get `Card` with ID.
 *  **`id`** 
 
 
+## `GET /api/card/:id/related`
+
+Return related entities.
+
+##### PARAMS:
+
+*  **`id`** 
+
+
 ## `GET /api/card/embeddable`
 
 Fetch a list of Cards where `enable_embedding` is `true`. The cards can be embedded using the embedding endpoints
@@ -172,7 +320,7 @@ You must be a superuser to do this.
 
 ## `GET /api/card/public`
 
-Fetch a list of Cards with public UUIDs. These cards are publically-accessible *if* public sharing is enabled.
+Fetch a list of Cards with public UUIDs. These cards are publicly-accessible *if* public sharing is enabled.
 
 You must be a superuser to do this.
 
@@ -183,47 +331,37 @@ Create a new `Card`.
 
 ##### PARAMS:
 
-*  **`dataset_query`** 
+*  **`visualization_settings`** value must be a map.
 
 *  **`description`** value may be nil, or if non-nil, value must be a non-blank string.
 
-*  **`display`** value must be a non-blank string.
-
-*  **`name`** value must be a non-blank string.
-
-*  **`visualization_settings`** value must be a map.
-
-*  **`collection_id`** value may be nil, or if non-nil, value must be an integer greater than zero.
+*  **`collection_position`** value may be nil, or if non-nil, value must be an integer greater than zero.
 
 *  **`result_metadata`** value may be nil, or if non-nil, value must be an array of valid results column metadata maps.
 
 *  **`metadata_checksum`** value may be nil, or if non-nil, value must be a non-blank string.
 
+*  **`collection_id`** value may be nil, or if non-nil, value must be an integer greater than zero.
 
-## `POST /api/card/:card-id/favorite`
-
-Favorite a Card.
+*  **`name`** value must be a non-blank string.
 
-##### PARAMS:
+*  **`dataset_query`** 
 
-*  **`card-id`** 
+*  **`display`** value must be a non-blank string.
 
 
-## `POST /api/card/:card-id/labels`
+## `POST /api/card/:card-id/favorite`
 
-Update the set of `Labels` that apply to a `Card`.
-   (This endpoint is considered DEPRECATED as Labels will be removed in a future version of Metabase.)
+Favorite a Card.
 
 ##### PARAMS:
 
 *  **`card-id`** 
 
-*  **`label_ids`** value must be an array. Each value must be an integer greater than zero.
-
 
 ## `POST /api/card/:card-id/public_link`
 
-Generate publically-accessible links for this Card. Returns UUID to be used in public links. (If this Card has
+Generate publicly-accessible links for this Card. Returns UUID to be used in public links. (If this Card has
   already been shared, it will return the existing public link rather than creating a new one.)  Public sharing must
   be enabled.
 
@@ -273,6 +411,15 @@ Bulk update endpoint for Card Collections. Move a set of `Cards` with CARD_IDS i
 *  **`collection_id`** value may be nil, or if non-nil, value must be an integer greater than zero.
 
 
+## `POST /api/card/related`
+
+Return related entities for an ad-hoc query.
+
+##### PARAMS:
+
+*  **`query`** 
+
+
 ## `PUT /api/card/:id`
 
 Update a `Card`.
@@ -285,6 +432,8 @@ Update a `Card`.
 
 *  **`archived`** value may be nil, or if non-nil, value must be a boolean.
 
+*  **`collection_position`** value may be nil, or if non-nil, value must be an integer greater than zero.
+
 *  **`result_metadata`** value may be nil, or if non-nil, value must be an array of valid results column metadata maps.
 
 *  **`metadata_checksum`** value may be nil, or if non-nil, value must be a non-blank string.
@@ -293,6 +442,8 @@ Update a `Card`.
 
 *  **`collection_id`** value may be nil, or if non-nil, value must be an integer greater than zero.
 
+*  **`card-updates`** 
+
 *  **`name`** value may be nil, or if non-nil, value must be a non-blank string.
 
 *  **`embedding_params`** value may be nil, or if non-nil, value must be a valid embedding params map.
@@ -306,11 +457,11 @@ Update a `Card`.
 
 ## `GET /api/collection/`
 
-Fetch a list of all Collections that the current user has read permissions for.
-   This includes `:can_write`, which means whether the current user is allowed to add or remove Cards to this Collection; keep in mind
-   that regardless of this status you must be a superuser to modify properties of Collections themselves.
+Fetch a list of all Collections that the current user has read permissions for (`:can_write` is returned as an
+  additional property of each Collection so you can tell which of these you have write permissions for.)
 
-   By default, this returns non-archived Collections, but instead you can show archived ones by passing `?archived=true`.
+  By default, this returns non-archived Collections, but instead you can show archived ones by passing
+  `?archived=true`.
 
 ##### PARAMS:
 
@@ -319,12 +470,28 @@ Fetch a list of all Collections that the current user has read permissions for.
 
 ## `GET /api/collection/:id`
 
-Fetch a specific (non-archived) Collection, including cards that belong to it.
+Fetch a specific Collection with standard details added
+
+##### PARAMS:
+
+*  **`id`** 
+
+
+## `GET /api/collection/:id/items`
+
+Fetch a specific Collection's items with the following options:
+
+  *  `model` - only include objects of a specific `model`. If unspecified, returns objects of all models
+  *  `archived` - when `true`, return archived objects *instead* of unarchived ones. Defaults to `false`.
 
 ##### PARAMS:
 
 *  **`id`** 
 
+*  **`model`** value may be nil, or if non-nil, value must be one of: `card`, `collection`, `dashboard`, `pulse`.
+
+*  **`archived`** value may be nil, or if non-nil, value must be a valid boolean string ('true' or 'false').
+
 
 ## `GET /api/collection/graph`
 
@@ -333,12 +500,35 @@ Fetch a graph of all Collection Permissions.
 You must be a superuser to do this.
 
 
+## `GET /api/collection/root`
+
+Return the 'Root' Collection object with standard details added
+
+
+## `GET /api/collection/root/items`
+
+Fetch objects that the current user should see at their root level. As mentioned elsewhere, the 'Root' Collection
+  doesn't actually exist as a row in the application DB: it's simply a virtual Collection where things with no
+  `collection_id` exist. It does, however, have its own set of Permissions.
+
+  This endpoint will actually show objects with no `collection_id` for Users that have Root Collection
+  permissions, but for people without Root Collection perms, we'll just show the objects that have an effective
+  location of `/`.
+
+  This endpoint is intended to power a 'Root Folder View' for the Current User, so regardless you'll see all the
+  top-level objects you're allowed to access.
+
+##### PARAMS:
+
+*  **`model`** value may be nil, or if non-nil, value must be one of: `card`, `collection`, `dashboard`, `pulse`.
+
+*  **`archived`** value may be nil, or if non-nil, value must be a valid boolean string ('true' or 'false').
+
+
 ## `POST /api/collection/`
 
 Create a new Collection.
 
-You must be a superuser to do this.
-
 ##### PARAMS:
 
 *  **`name`** value must be a non-blank string.
@@ -347,25 +537,29 @@ You must be a superuser to do this.
 
 *  **`description`** value may be nil, or if non-nil, value must be a non-blank string.
 
+*  **`parent_id`** value may be nil, or if non-nil, value must be an integer greater than zero.
 
-## `PUT /api/collection/:id`
 
-Modify an existing Collection, including archiving or unarchiving it.
+## `PUT /api/collection/:id`
 
-You must be a superuser to do this.
+Modify an existing Collection, including archiving or unarchiving it, or moving it.
 
 ##### PARAMS:
 
 *  **`id`** 
 
-*  **`name`** value must be a non-blank string.
+*  **`name`** value may be nil, or if non-nil, value must be a non-blank string.
 
-*  **`color`** value must be a string that matches the regex `^#[0-9A-Fa-f]{6}$`.
+*  **`color`** value may be nil, or if non-nil, value must be a string that matches the regex `^#[0-9A-Fa-f]{6}$`.
 
 *  **`description`** value may be nil, or if non-nil, value must be a non-blank string.
 
 *  **`archived`** value may be nil, or if non-nil, value must be a boolean.
 
+*  **`parent_id`** value may be nil, or if non-nil, value must be an integer greater than zero.
+
+*  **`collection-updates`** 
+
 
 ## `PUT /api/collection/graph`
 
@@ -380,7 +574,7 @@ You must be a superuser to do this.
 
 ## `DELETE /api/dashboard/:dashboard-id/public_link`
 
-Delete the publically-accessible link to this Dashboard.
+Delete the publicly-accessible link to this Dashboard.
 
 You must be a superuser to do this.
 
@@ -440,6 +634,15 @@ Get `Dashboard` with ID.
 *  **`id`** 
 
 
+## `GET /api/dashboard/:id/related`
+
+Return related entities.
+
+##### PARAMS:
+
+*  **`id`** 
+
+
 ## `GET /api/dashboard/:id/revisions`
 
 Fetch `Revisions` for `Dashboard` with ID.
@@ -451,14 +654,16 @@ Fetch `Revisions` for `Dashboard` with ID.
 
 ## `GET /api/dashboard/embeddable`
 
-Fetch a list of Dashboards where `enable_embedding` is `true`. The dashboards can be embedded using the embedding endpoints and a signed JWT.
+Fetch a list of Dashboards where `enable_embedding` is `true`. The dashboards can be embedded using the embedding
+  endpoints and a signed JWT.
 
 You must be a superuser to do this.
 
 
 ## `GET /api/dashboard/public`
 
-Fetch a list of Dashboards with public UUIDs. These dashboards are publically-accessible *if* public sharing is enabled.
+Fetch a list of Dashboards with public UUIDs. These dashboards are publicly-accessible *if* public sharing is
+  enabled.
 
 You must be a superuser to do this.
 
@@ -475,14 +680,18 @@ Create a new `Dashboard`.
 
 *  **`parameters`** value must be an array. Each value must be a map.
 
+*  **`collection_id`** value may be nil, or if non-nil, value must be an integer greater than zero.
+
+*  **`collection_position`** value may be nil, or if non-nil, value must be an integer greater than zero.
+
 *  **`dashboard`** 
 
 
 ## `POST /api/dashboard/:dashboard-id/public_link`
 
-Generate publically-accessible links for this Dashboard. Returns UUID to be used in public links.
-   (If this Dashboard has already been shared, it will return the existing public link rather than creating a new one.)
-   Public sharing must be enabled.
+Generate publicly-accessible links for this Dashboard. Returns UUID to be used in public links. (If this
+  Dashboard has already been shared, it will return the existing public link rather than creating a new one.) Public
+  sharing must be enabled.
 
 You must be a superuser to do this.
 
@@ -499,7 +708,7 @@ Add a `Card` to a `Dashboard`.
 
 *  **`id`** 
 
-*  **`cardId`** value must be an integer greater than zero.
+*  **`cardId`** value may be nil, or if non-nil, value must be an integer greater than zero.
 
 *  **`parameter_mappings`** value must be an array. Each value must be a map.
 
@@ -528,12 +737,33 @@ Revert a `Dashboard` to a prior `Revision`.
 *  **`revision_id`** value must be an integer greater than zero.
 
 
+## `POST /api/dashboard/save`
+
+Save a denormalized description of dashboard.
+
+##### PARAMS:
+
+*  **`dashboard`** 
+
+
+## `POST /api/dashboard/save/collection/:parent-collection-id`
+
+Save a denormalized description of dashboard into collection with ID `:parent-collection-id`.
+
+##### PARAMS:
+
+*  **`parent-collection-id`** 
+
+*  **`dashboard`** 
+
+
 ## `PUT /api/dashboard/:id`
 
 Update a `Dashboard`.
 
-   Usually, you just need write permissions for this Dashboard to do this (which means you have appropriate permissions for the Cards belonging to this Dashboard),
-   but to change the value of `enable_embedding` you must be a superuser.
+  Usually, you just need write permissions for this Dashboard to do this (which means you have appropriate
+  permissions for the Cards belonging to this Dashboard), but to change the value of `enable_embedding` you must be a
+  superuser.
 
 ##### PARAMS:
 
@@ -545,16 +775,20 @@ Update a `Dashboard`.
 
 *  **`archived`** value may be nil, or if non-nil, value must be a boolean.
 
+*  **`collection_position`** value may be nil, or if non-nil, value must be an integer greater than zero.
+
 *  **`show_in_getting_started`** value may be nil, or if non-nil, value must be a boolean.
 
 *  **`enable_embedding`** value may be nil, or if non-nil, value must be a boolean.
 
+*  **`collection_id`** value may be nil, or if non-nil, value must be an integer greater than zero.
+
+*  **`dash-updates`** 
+
 *  **`name`** value may be nil, or if non-nil, value must be a non-blank string.
 
 *  **`caveats`** value may be nil, or if non-nil, value must be a string.
 
-*  **`dashboard`** 
-
 *  **`embedding_params`** value may be nil, or if non-nil, value must be a valid embedding params map.
 
 *  **`id`** 
@@ -592,7 +826,9 @@ Delete a `Database`.
 
 ## `GET /api/database/`
 
-Fetch all `Databases`.
+Fetch all `Databases`. `include_tables` means we should hydrate the Tables belonging to each DB. `include_cards` here
+  means we should also include virtual Table entries for saved Questions, e.g. so we can easily use them as source
+  Tables in queries. Default for both is `false`.
 
 ##### PARAMS:
 
@@ -654,6 +890,26 @@ Get metadata about a `Database`, including all of its `Tables` and `Fields`.
 *  **`id`** 
 
 
+## `GET /api/database/:id/schema/:schema`
+
+Returns a list of tables for the given database `id` and `schema`
+
+##### PARAMS:
+
+*  **`id`** 
+
+*  **`schema`** 
+
+
+## `GET /api/database/:id/schemas`
+
+Returns a list of all the schemas found for the database `id`
+
+##### PARAMS:
+
+*  **`id`** 
+
+
 ## `GET /api/database/:virtual-db/metadata`
 
 Endpoint that provides metadata for the Saved Questions 'virtual' database. Used for fooling the frontend
@@ -805,6 +1061,13 @@ Get historical query execution duration.
 *  **`query`** 
 
 
+## `DELETE /api/email/`
+
+Clear all email related settings. You must be a superuser to ddo this
+
+You must be a superuser to do this.
+
+
 ## `POST /api/email/test`
 
 Send a test email. You must be a superuser to do this.
@@ -836,6 +1099,50 @@ Fetch a Card via a JSON Web Token signed with the `embedding-secret-key`.
 *  **`token`** 
 
 
+## `GET /api/embed/card/:token/field/:field-id/remapping/:remapped-id`
+
+Fetch remapped Field values. This is the same as `GET /api/field/:id/remapping/:remapped-id`, but for use with
+  embedded Cards.
+
+##### PARAMS:
+
+*  **`token`** 
+
+*  **`field-id`** 
+
+*  **`remapped-id`** 
+
+*  **`value`** value must be a non-blank string.
+
+
+## `GET /api/embed/card/:token/field/:field-id/search/:search-field-id`
+
+Search for values of a Field that is referenced by an embedded Card.
+
+##### PARAMS:
+
+*  **`token`** 
+
+*  **`field-id`** 
+
+*  **`search-field-id`** 
+
+*  **`value`** value must be a non-blank string.
+
+*  **`limit`** value may be nil, or if non-nil, value must be a valid integer greater than zero.
+
+
+## `GET /api/embed/card/:token/field/:field-id/values`
+
+Fetch FieldValues for a Field that is referenced by an embedded Card.
+
+##### PARAMS:
+
+*  **`token`** 
+
+*  **`field-id`** 
+
+
 ## `GET /api/embed/card/:token/query`
 
 Fetch the results of running a Card using a JSON Web Token signed with the `embedding-secret-key`.
@@ -884,14 +1191,7 @@ Fetch a Dashboard via a JSON Web Token signed with the `embedding-secret-key`.
 
 ## `GET /api/embed/dashboard/:token/dashcard/:dashcard-id/card/:card-id`
 
-Fetch the results of running a Card belonging to a Dashboard using a JSON Web Token signed with the `embedding-secret-key`.
-
-   Token should have the following format:
-
-     {:resource {:dashboard <dashboard-id>}
-      :params   <parameters>}
-
-   Additional dashboard parameters can be provided in the query string, but params in the JWT token take precedence.
+Fetch the results of running a Card belonging to a Dashboard using a JSON Web Token signed with the `embedding-secret-key`
 
 ##### PARAMS:
 
@@ -906,13 +1206,77 @@ Fetch the results of running a Card belonging to a Dashboard using a JSON Web To
 *  **`query-params`** 
 
 
-## `DELETE /api/field/:id/dimension`
+## `GET /api/embed/dashboard/:token/dashcard/:dashcard-id/card/:card-id/:export-format`
 
-Remove the dimension associated to field at ID
+Fetch the results of running a Card belonging to a Dashboard using a JSON Web Token signed with the `embedding-secret-key`
+   return the data in one of the export formats
 
 ##### PARAMS:
 
-*  **`id`** 
+*  **`token`** 
+
+*  **`export-format`** value must be one of: `csv`, `json`, `xlsx`.
+
+*  **`dashcard-id`** 
+
+*  **`card-id`** 
+
+*  **`&`** 
+
+*  **`query-params`** 
+
+
+## `GET /api/embed/dashboard/:token/field/:field-id/remapping/:remapped-id`
+
+Fetch remapped Field values. This is the same as `GET /api/field/:id/remapping/:remapped-id`, but for use with
+  embedded Dashboards.
+
+##### PARAMS:
+
+*  **`token`** 
+
+*  **`field-id`** 
+
+*  **`remapped-id`** 
+
+*  **`value`** value must be a non-blank string.
+
+
+## `GET /api/embed/dashboard/:token/field/:field-id/search/:search-field-id`
+
+Search for values of a Field that is referenced by a Card in an embedded Dashboard.
+
+##### PARAMS:
+
+*  **`token`** 
+
+*  **`field-id`** 
+
+*  **`search-field-id`** 
+
+*  **`value`** value must be a non-blank string.
+
+*  **`limit`** value may be nil, or if non-nil, value must be a valid integer greater than zero.
+
+
+## `GET /api/embed/dashboard/:token/field/:field-id/values`
+
+Fetch FieldValues for a Field that is used as a param in an embedded Dashboard.
+
+##### PARAMS:
+
+*  **`token`** 
+
+*  **`field-id`** 
+
+
+## `DELETE /api/field/:id/dimension`
+
+Remove the dimension associated to field at ID
+
+##### PARAMS:
+
+*  **`id`** 
 
 
 ## `GET /api/field/:id`
@@ -924,6 +1288,43 @@ Get `Field` with ID.
 *  **`id`** 
 
 
+## `GET /api/field/:id/related`
+
+Return related entities.
+
+##### PARAMS:
+
+*  **`id`** 
+
+
+## `GET /api/field/:id/remapping/:remapped-id`
+
+Fetch remapped Field values.
+
+##### PARAMS:
+
+*  **`id`** 
+
+*  **`remapped-id`** 
+
+*  **`value`** 
+
+
+## `GET /api/field/:id/search/:search-id`
+
+Search for values of a Field that match values of another Field when breaking out by the 
+
+##### PARAMS:
+
+*  **`id`** 
+
+*  **`search-id`** 
+
+*  **`value`** value must be a non-blank string.
+
+*  **`limit`** value may be nil, or if non-nil, value must be a valid integer greater than zero.
+
+
 ## `GET /api/field/:id/summary`
 
 Get the count and distinct count of `Field` with ID.
@@ -935,8 +1336,8 @@ Get the count and distinct count of `Field` with ID.
 
 ## `GET /api/field/:id/values`
 
-If `Field`'s special type derives from `type/Category`, or its base type is `type/Boolean`, return
-   all distinct values of the field, and a map of human-readable values defined by the user.
+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.
 
 ##### PARAMS:
 
@@ -945,9 +1346,8 @@ If `Field`'s special type derives from `type/Category`, or its base type is `typ
 
 ## `GET /api/field/field-literal%2C:field-name%2Ctype%2F:field-type/values`
 
-Implementation of the field values endpoint for fields in the Saved Questions 'virtual' DB.
-   This endpoint is just a convenience to simplify the frontend code. It just returns the standard
-   'empty' field values response.
+Implementation of the field values endpoint for fields in the Saved Questions 'virtual' DB. This endpoint is just a
+  convenience to simplify the frontend code. It just returns the standard 'empty' field values response.
 
 ##### PARAMS:
 
@@ -995,14 +1395,14 @@ You must be a superuser to do this.
 
 ## `POST /api/field/:id/values`
 
-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.
+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.
 
 ##### PARAMS:
 
 *  **`id`** 
 
-*  **`value-pairs`** value must be an array.
+*  **`value-pairs`** value must be an array. Each value must be an array.
 
 
 ## `PUT /api/field/:id`
@@ -1011,73 +1411,33 @@ Update `Field` with ID.
 
 ##### PARAMS:
 
-*  **`id`** 
-
-*  **`caveats`** value may be nil, or if non-nil, value must be a non-blank string.
-
-*  **`description`** value may be nil, or if non-nil, value must be a non-blank string.
+*  **`visibility_type`** value may be nil, or if non-nil, value must be one of: `details-only`, `hidden`, `normal`, `retired`, `sensitive`.
 
 *  **`display_name`** value may be nil, or if non-nil, value must be a non-blank string.
 
-*  **`fk_target_field_id`** value may be nil, or if non-nil, value must be an integer greater than zero.
-
 *  **`points_of_interest`** value may be nil, or if non-nil, value must be a non-blank string.
 
-*  **`special_type`** value may be nil, or if non-nil, value must be a valid field type.
-
-*  **`visibility_type`** value may be nil, or if non-nil, value must be one of: `details-only`, `hidden`, `normal`, `retired`, `sensitive`.
-
-
-## `GET /api/geojson/:key`
-
-Fetch a custom GeoJSON file as defined in the `custom-geojson` setting. (This just acts as a simple proxy for the file specified for KEY).
-
-##### PARAMS:
-
-*  **`key`** value must be a non-blank string.
-
-
-## `GET /api/getting-started/`
-
-Fetch basic info for the Getting Started guide.
+*  **`description`** value may be nil, or if non-nil, value must be a non-blank string.
 
+*  **`special_type`** value may be nil, or if non-nil, value must be a valid field type.
 
-## `DELETE /api/label/:id`
+*  **`has_field_values`** value may be nil, or if non-nil, value must be one of: `auto-list`, `list`, `none`, `search`.
 
-[DEPRECATED] Delete a `Label`. :label:
+*  **`caveats`** value may be nil, or if non-nil, value must be a non-blank string.
 
-##### PARAMS:
+*  **`fk_target_field_id`** value may be nil, or if non-nil, value must be an integer greater than zero.
 
 *  **`id`** 
 
 
-## `GET /api/label/`
-
-[DEPRECATED] List all `Labels`. :label:
-
-
-## `POST /api/label/`
-
-[DEPRECATED] Create a new `Label`. :label:
-
-##### PARAMS:
-
-*  **`name`** value must be a non-blank string.
-
-*  **`icon`** value may be nil, or if non-nil, value must be a non-blank string.
-
-
-## `PUT /api/label/:id`
+## `GET /api/geojson/:key`
 
-[DEPRECATED] Update a `Label`. :label:
+Fetch a custom GeoJSON file as defined in the `custom-geojson` setting. (This just acts as a simple proxy for the
+  file specified for KEY).
 
 ##### PARAMS:
 
-*  **`id`** 
-
-*  **`name`** value may be nil, or if non-nil, value must be a non-blank string.
-
-*  **`icon`** value may be nil, or if non-nil, value must be a non-blank string.
+*  **`key`** value must be a non-blank string.
 
 
 ## `PUT /api/ldap/settings`
@@ -1124,6 +1484,15 @@ You must be a superuser to do this.
 *  **`id`** 
 
 
+## `GET /api/metric/:id/related`
+
+Return related entities.
+
+##### PARAMS:
+
+*  **`id`** 
+
+
 ## `GET /api/metric/:id/revisions`
 
 Fetch `Revisions` for `Metric` with ID.
@@ -1294,14 +1663,14 @@ You must be a superuser to do this.
 
 ## `PUT /api/permissions/graph`
 
-Do a batch update of Permissions by passing in a modified graph. This should return the same graph,
-   in the same format, that you got from `GET /api/permissions/graph`, with any changes made in the wherever neccesary.
-   This modified graph must correspond to the `PermissionsGraph` schema.
-   If successful, this endpoint returns the updated permissions graph; use this as a base for any further modifications.
+Do a batch update of Permissions by passing in a modified graph. This should return the same graph, in the same
+  format, that you got from `GET /api/permissions/graph`, with any changes made in the wherever neccesary. This
+  modified graph must correspond to the `PermissionsGraph` schema. If successful, this endpoint returns the updated
+  permissions graph; use this as a base for any further modifications.
 
-   Revisions to the permissions graph are tracked. If you fetch the permissions graph and some other third-party modifies it before you can submit
-   you revisions, the endpoint will instead make no changes andr eturn a 409 (Conflict) response. In this case, you should fetch the updated graph
-   and make desired changes to that.
+  Revisions to the permissions graph are tracked. If you fetch the permissions graph and some other third-party
+  modifies it before you can submit you revisions, the endpoint will instead make no changes and return a
+  409 (Conflict) response. In this case, you should fetch the updated graph and make desired changes to that.
 
 You must be a superuser to do this.
 
@@ -1373,7 +1742,7 @@ Fetch the results of running a Card belonging to a Dashboard you're considering
 
 ## `GET /api/public/card/:uuid`
 
-Fetch a publically-accessible Card an return query results as well as `:card` information. Does not require auth
+Fetch a publicly-accessible Card an return query results as well as `:card` information. Does not require auth
    credentials. Public sharing must be enabled.
 
 ##### PARAMS:
@@ -1381,9 +1750,53 @@ Fetch a publically-accessible Card an return query results as well as `:card` in
 *  **`uuid`** 
 
 
+## `GET /api/public/card/:uuid/field/:field-id/remapping/:remapped-id`
+
+Fetch remapped Field values. This is the same as `GET /api/field/:id/remapping/:remapped-id`, but for use with public
+  Cards.
+
+##### PARAMS:
+
+*  **`uuid`** 
+
+*  **`field-id`** 
+
+*  **`remapped-id`** 
+
+*  **`value`** value must be a non-blank string.
+
+
+## `GET /api/public/card/:uuid/field/:field-id/search/:search-field-id`
+
+Search for values of a Field that is referenced by a public Card.
+
+##### PARAMS:
+
+*  **`uuid`** 
+
+*  **`field-id`** 
+
+*  **`search-field-id`** 
+
+*  **`value`** value must be a non-blank string.
+
+*  **`limit`** value may be nil, or if non-nil, value must be a valid integer greater than zero.
+
+
+## `GET /api/public/card/:uuid/field/:field-id/values`
+
+Fetch FieldValues for a Field that is referenced by a public Card.
+
+##### PARAMS:
+
+*  **`uuid`** 
+
+*  **`field-id`** 
+
+
 ## `GET /api/public/card/:uuid/query`
 
-Fetch a publically-accessible Card an return query results as well as `:card` information. Does not require auth
+Fetch a publicly-accessible Card an return query results as well as `:card` information. Does not require auth
    credentials. Public sharing must be enabled.
 
 ##### PARAMS:
@@ -1395,7 +1808,7 @@ Fetch a publically-accessible Card an return query results as well as `:card` in
 
 ## `GET /api/public/card/:uuid/query/:export-format`
 
-Fetch a publically-accessible Card and return query results in the specified format. Does not require auth
+Fetch a publicly-accessible Card and return query results in the specified format. Does not require auth
    credentials. Public sharing must be enabled.
 
 ##### PARAMS:
@@ -1409,7 +1822,7 @@ Fetch a publically-accessible Card and return query results in the specified for
 
 ## `GET /api/public/dashboard/:uuid`
 
-Fetch a publically-accessible Dashboard. Does not require auth credentials. Public sharing must be enabled.
+Fetch a publicly-accessible Dashboard. Does not require auth credentials. Public sharing must be enabled.
 
 ##### PARAMS:
 
@@ -1418,7 +1831,7 @@ Fetch a publically-accessible Dashboard. Does not require auth credentials. Publ
 
 ## `GET /api/public/dashboard/:uuid/card/:card-id`
 
-Fetch the results for a Card in a publically-accessible Dashboard. Does not require auth credentials. Public
+Fetch the results for a Card in a publicly-accessible Dashboard. Does not require auth credentials. Public
    sharing must be enabled.
 
 ##### PARAMS:
@@ -1430,6 +1843,50 @@ Fetch the results for a Card in a publically-accessible Dashboard. Does not requ
 *  **`parameters`** value may be nil, or if non-nil, value must be a valid JSON string.
 
 
+## `GET /api/public/dashboard/:uuid/field/:field-id/remapping/:remapped-id`
+
+Fetch remapped Field values. This is the same as `GET /api/field/:id/remapping/:remapped-id`, but for use with public
+  Dashboards.
+
+##### PARAMS:
+
+*  **`uuid`** 
+
+*  **`field-id`** 
+
+*  **`remapped-id`** 
+
+*  **`value`** value must be a non-blank string.
+
+
+## `GET /api/public/dashboard/:uuid/field/:field-id/search/:search-field-id`
+
+Search for values of a Field that is referenced by a Card in a public Dashboard.
+
+##### PARAMS:
+
+*  **`uuid`** 
+
+*  **`field-id`** 
+
+*  **`search-field-id`** 
+
+*  **`value`** value must be a non-blank string.
+
+*  **`limit`** value may be nil, or if non-nil, value must be a valid integer greater than zero.
+
+
+## `GET /api/public/dashboard/:uuid/field/:field-id/values`
+
+Fetch FieldValues for a Field that is referenced by a Card in a public Dashboard.
+
+##### PARAMS:
+
+*  **`uuid`** 
+
+*  **`field-id`** 
+
+
 ## `GET /api/public/oembed`
 
 oEmbed endpoint used to retreive embed code and metadata for a (public) Metabase URL.
@@ -1447,7 +1904,7 @@ oEmbed endpoint used to retreive embed code and metadata for a (public) Metabase
 
 ## `DELETE /api/pulse/:id`
 
-Delete a `Pulse`.
+Delete a Pulse. (DEPRECATED -- don't delete a Pulse anymore -- archive it instead.)
 
 ##### PARAMS:
 
@@ -1456,7 +1913,11 @@ Delete a `Pulse`.
 
 ## `GET /api/pulse/`
 
-Fetch all `Pulses`
+Fetch all Pulses
+
+##### PARAMS:
+
+*  **`archived`** value may be nil, or if non-nil, value must be a valid boolean string ('true' or 'false').
 
 
 ## `GET /api/pulse/:id`
@@ -1470,12 +1931,12 @@ Fetch `Pulse` with ID.
 
 ## `GET /api/pulse/form_input`
 
-Provides relevant configuration information and user choices for creating/updating `Pulses`.
+Provides relevant configuration information and user choices for creating/updating Pulses.
 
 
 ## `GET /api/pulse/preview_card/:id`
 
-Get HTML rendering of a `Card` with ID.
+Get HTML rendering of a Card with `id`.
 
 ##### PARAMS:
 
@@ -1484,7 +1945,7 @@ Get HTML rendering of a `Card` with ID.
 
 ## `GET /api/pulse/preview_card_info/:id`
 
-Get JSON object containing HTML rendering of a `Card` with ID and other information.
+Get JSON object containing HTML rendering of a Card with `id` and other information.
 
 ##### PARAMS:
 
@@ -1493,7 +1954,7 @@ Get JSON object containing HTML rendering of a `Card` with ID and other informat
 
 ## `GET /api/pulse/preview_card_png/:id`
 
-Get PNG rendering of a `Card` with ID.
+Get PNG rendering of a Card with `id`.
 
 ##### PARAMS:
 
@@ -1508,11 +1969,15 @@ Create a new `Pulse`.
 
 *  **`name`** value must be a non-blank string.
 
-*  **`cards`** value must be an array. Each value must be a map. The array cannot be empty.
+*  **`cards`** value must be an array. Each value must satisfy one of the following requirements: 1) value must be a map with the following keys `(collection_id, description, display, id, include_csv, include_xls, name)` 2) value must be a map with the keys `id`, `include_csv`, and `include_xls`. The array cannot be empty.
 
 *  **`channels`** value must be an array. Each value must be a map. The array cannot be empty.
 
-*  **`skip_if_empty`** value must be a boolean.
+*  **`skip_if_empty`** value may be nil, or if non-nil, value must be a boolean.
+
+*  **`collection_id`** value may be nil, or if non-nil, value must be an integer greater than zero.
+
+*  **`collection_position`** value may be nil, or if non-nil, value must be an integer greater than zero.
 
 
 ## `POST /api/pulse/test`
@@ -1523,28 +1988,38 @@ Test send an unsaved pulse.
 
 *  **`name`** value must be a non-blank string.
 
-*  **`cards`** value must be an array. Each value must be a map. The array cannot be empty.
+*  **`cards`** value must be an array. Each value must satisfy one of the following requirements: 1) value must be a map with the following keys `(collection_id, description, display, id, include_csv, include_xls, name)` 2) value must be a map with the keys `id`, `include_csv`, and `include_xls`. The array cannot be empty.
 
 *  **`channels`** value must be an array. Each value must be a map. The array cannot be empty.
 
-*  **`skip_if_empty`** value must be a boolean.
+*  **`skip_if_empty`** value may be nil, or if non-nil, value must be a boolean.
+
+*  **`collection_id`** value may be nil, or if non-nil, value must be an integer greater than zero.
+
+*  **`collection_position`** value may be nil, or if non-nil, value must be an integer greater than zero.
 
 
 ## `PUT /api/pulse/:id`
 
-Update a `Pulse` with ID.
+Update a Pulse with `id`.
 
 ##### PARAMS:
 
 *  **`id`** 
 
-*  **`name`** value must be a non-blank string.
+*  **`name`** value may be nil, or if non-nil, value must be a non-blank string.
 
-*  **`cards`** value must be an array. Each value must be a map. The array cannot be empty.
+*  **`cards`** value may be nil, or if non-nil, value must be an array. Each value must satisfy one of the following requirements: 1) value must be a map with the following keys `(collection_id, description, display, id, include_csv, include_xls, name)` 2) value must be a map with the keys `id`, `include_csv`, and `include_xls`. The array cannot be empty.
 
-*  **`channels`** value must be an array. Each value must be a map. The array cannot be empty.
+*  **`channels`** value may be nil, or if non-nil, value must be an array. Each value must be a map. The array cannot be empty.
+
+*  **`skip_if_empty`** value may be nil, or if non-nil, value must be a boolean.
+
+*  **`collection_id`** value may be nil, or if non-nil, value must be an integer greater than zero.
+
+*  **`archived`** value may be nil, or if non-nil, value must be a boolean.
 
-*  **`skip_if_empty`** value must be a boolean.
+*  **`pulse-updates`** 
 
 
 ## `GET /api/revision/`
@@ -1571,6 +2046,17 @@ Revert an object to a prior revision.
 *  **`revision_id`** value must be an integer.
 
 
+## `GET /api/search/`
+
+Search Cards, Dashboards, Collections and Pulses for the substring `q`.
+
+##### PARAMS:
+
+*  **`q`** value may be nil, or if non-nil, value must be a non-blank string.
+
+*  **`archived`** value may be nil, or if non-nil, value must be a valid boolean string ('true' or 'false').
+
+
 ## `DELETE /api/segment/:id`
 
 Delete a `Segment`.
@@ -1600,6 +2086,15 @@ You must be a superuser to do this.
 *  **`id`** 
 
 
+## `GET /api/segment/:id/related`
+
+Return related entities.
+
+##### PARAMS:
+
+*  **`id`** 
+
+
 ## `GET /api/segment/:id/revisions`
 
 Fetch `Revisions` for `Segment` with ID.
@@ -1747,6 +2242,17 @@ You must be a superuser to do this.
 *  **`key`** value must be a non-blank string.
 
 
+## `PUT /api/setting/`
+
+Update multiple `Settings` values.  You must be a superuser to do this.
+
+You must be a superuser to do this.
+
+##### PARAMS:
+
+*  **`settings`** 
+
+
 ## `PUT /api/setting/:key`
 
 Create/update a `Setting`. You must be a superuser to do this.
@@ -1810,10 +2316,6 @@ Validate that we can connect to a database given a set of details.
 
 *  **`engine`** value must be a valid database engine.
 
-*  **`host`** 
-
-*  **`port`** 
-
 *  **`details`** 
 
 *  **`token`** Token does not match the setup token.
@@ -1862,8 +2364,8 @@ Get all foreign keys whose destination is a `Field` that belongs to this `Table`
 Get metadata about a `Table` useful for running queries.
    Returns DB, fields, field FKs, and field values.
 
-  By passing `include_sensitive_fields=true`, information *about* sensitive `Fields` will be returned; in no case
-  will any of its corresponding values be returned. (This option is provided for use in the Admin Edit Metadata page).
+  By passing `include_sensitive_fields=true`, information *about* sensitive `Fields` will be returned; in no case will
+  any of its corresponding values be returned. (This option is provided for use in the Admin Edit Metadata page).
 
 ##### PARAMS:
 
@@ -1872,6 +2374,15 @@ Get metadata about a `Table` useful for running queries.
 *  **`include_sensitive_fields`** value may be nil, or if non-nil, value must be a valid boolean string ('true' or 'false').
 
 
+## `GET /api/table/:id/related`
+
+Return related entities.
+
+##### PARAMS:
+
+*  **`id`** 
+
+
 ## `GET /api/table/card__:id/fks`
 
 Return FK info for the 'virtual' table for a Card. This is always empty, so this endpoint
@@ -1921,7 +2432,7 @@ Update `Table` with ID.
 
 *  **`display_name`** value may be nil, or if non-nil, value must be a non-blank string.
 
-*  **`entity_type`** value may be nil, or if non-nil, value must be one of: `event`, `person`, `photo`, `place`.
+*  **`entity_type`** value may be nil, or if non-nil, value must be a valid entity type (keyword or string).
 
 *  **`visibility_type`** value may be nil, or if non-nil, value must be one of: `cruft`, `hidden`, `technical`.
 
@@ -1936,9 +2447,10 @@ Update `Table` with ID.
 
 ## `GET /api/tiles/:zoom/:x/:y/:lat-field-id/:lon-field-id/:lat-col-idx/:lon-col-idx/`
 
-This endpoints provides an image with the appropriate pins rendered given a MBQL QUERY (passed as a GET query string param).
-   We evaluate the query and find the set of lat/lon pairs which are relevant and then render the appropriate ones.
-   It's expected that to render a full map view several calls will be made to this endpoint in parallel.
+This endpoints provides an image with the appropriate pins rendered given a MBQL QUERY (passed as a GET query
+  string param). We evaluate the query and find the set of lat/lon pairs which are relevant and then render the
+  appropriate ones. It's expected that to render a full map view several calls will be made to this endpoint in
+  parallel.
 
 ##### PARAMS:
 
@@ -1972,7 +2484,13 @@ You must be a superuser to do this.
 
 ## `GET /api/user/`
 
-Fetch a list of all active `Users` for the admin People page.
+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.)
+
+##### PARAMS:
+
+*  **`include_deactivated`** value may be nil, or if non-nil, value must be a valid boolean string ('true' or 'false').
 
 
 ## `GET /api/user/:id`
@@ -1991,7 +2509,7 @@ Fetch the current `User`.
 
 ## `POST /api/user/`
 
-Create a new `User`, or reactivate an existing one.
+Create a new `User`, return a 400 if the email address is already taken
 
 You must be a superuser to do this.
 
@@ -2005,6 +2523,8 @@ You must be a superuser to do this.
 
 *  **`password`** 
 
+*  **`login_attributes`** value may be nil, or if non-nil, value must be a map with each value either a string or number.
+
 
 ## `POST /api/user/:id/send_invite`
 
@@ -2019,13 +2539,13 @@ You must be a superuser to do this.
 
 ## `PUT /api/user/:id`
 
-Update a `User`.
+Update an existing, active `User`.
 
 ##### PARAMS:
 
 *  **`id`** 
 
-*  **`email`** value must be a valid email address.
+*  **`email`** value may be nil, or if non-nil, value must be a valid email address.
 
 *  **`first_name`** value may be nil, or if non-nil, value must be a non-blank string.
 
@@ -2033,6 +2553,8 @@ Update a `User`.
 
 *  **`is_superuser`** 
 
+*  **`login_attributes`** value may be nil, or if non-nil, value must be a map with each value either a string or number.
+
 
 ## `PUT /api/user/:id/password`
 
@@ -2056,6 +2578,17 @@ Indicate that a user has been informed about the vast intricacies of 'the' Query
 *  **`id`** 
 
 
+## `PUT /api/user/:id/reactivate`
+
+Reactivate user at `:id`
+
+You must be a superuser to do this.
+
+##### PARAMS:
+
+*  **`id`** 
+
+
 ## `GET /api/util/logs`
 
 Logs.
@@ -2083,277 +2616,4 @@ Endpoint that checks if the supplied password meets the currently configured pas
 
 ##### PARAMS:
 
-*  **`password`** Insufficient password strength
-
-
-## `GET /api/x-ray/card/:id`
-
-X-ray a card.
-
-##### PARAMS:
-
-*  **`id`** 
-
-*  **`max_query_cost`** value may be nil, or if non-nil, value must be one of: `cache`, `full-scan`, `joins`, `sample`.
-
-*  **`max_computation_cost`** value may be nil, or if non-nil, value must be one of: `linear`, `unbounded`, `yolo`.
-
-
-## `GET /api/x-ray/compare/card/:card-id/segment/:segment-id`
-
-Get comparison x-ray of a card and a segment.
-
-##### PARAMS:
-
-*  **`card-id`** 
-
-*  **`segment-id`** 
-
-*  **`max_query_cost`** value may be nil, or if non-nil, value must be one of: `cache`, `full-scan`, `joins`, `sample`.
-
-*  **`max_computation_cost`** value may be nil, or if non-nil, value must be one of: `linear`, `unbounded`, `yolo`.
-
-
-## `GET /api/x-ray/compare/card/:card-id/table/:table-id`
-
-Get comparison x-ray of a table and a card.
-
-##### PARAMS:
-
-*  **`card-id`** 
-
-*  **`table-id`** 
-
-*  **`max_query_cost`** value may be nil, or if non-nil, value must be one of: `cache`, `full-scan`, `joins`, `sample`.
-
-*  **`max_computation_cost`** value may be nil, or if non-nil, value must be one of: `linear`, `unbounded`, `yolo`.
-
-
-## `GET /api/x-ray/compare/cards/:card1-id/:card2-id`
-
-Get comparison x-ray of two cards.
-
-##### PARAMS:
-
-*  **`card1-id`** 
-
-*  **`card2-id`** 
-
-*  **`max_query_cost`** value may be nil, or if non-nil, value must be one of: `cache`, `full-scan`, `joins`, `sample`.
-
-*  **`max_computation_cost`** value may be nil, or if non-nil, value must be one of: `linear`, `unbounded`, `yolo`.
-
-
-## `GET /api/x-ray/compare/fields/:field1-id/:field2-id`
-
-Get comparison x-ray of two fields.
-
-##### PARAMS:
-
-*  **`field1-id`** 
-
-*  **`field2-id`** 
-
-*  **`max_query_cost`** value may be nil, or if non-nil, value must be one of: `cache`, `full-scan`, `joins`, `sample`.
-
-*  **`max_computation_cost`** value may be nil, or if non-nil, value must be one of: `linear`, `unbounded`, `yolo`.
-
-
-## `GET /api/x-ray/compare/segment/:segment-id/card/:card-id`
-
-Get comparison x-ray of a card and a segment.
-
-##### PARAMS:
-
-*  **`segment-id`** 
-
-*  **`card-id`** 
-
-*  **`max_query_cost`** value may be nil, or if non-nil, value must be one of: `cache`, `full-scan`, `joins`, `sample`.
-
-*  **`max_computation_cost`** value may be nil, or if non-nil, value must be one of: `linear`, `unbounded`, `yolo`.
-
-
-## `GET /api/x-ray/compare/segment/:segment-id/table/:table-id`
-
-Get comparison x-ray of a table and a segment.
-
-##### PARAMS:
-
-*  **`segment-id`** 
-
-*  **`table-id`** 
-
-*  **`max_query_cost`** value may be nil, or if non-nil, value must be one of: `cache`, `full-scan`, `joins`, `sample`.
-
-*  **`max_computation_cost`** value may be nil, or if non-nil, value must be one of: `linear`, `unbounded`, `yolo`.
-
-
-## `GET /api/x-ray/compare/segments/:segment1-id/:segment2-id`
-
-Get comparison x-ray of two segments.
-
-##### PARAMS:
-
-*  **`segment1-id`** 
-
-*  **`segment2-id`** 
-
-*  **`max_query_cost`** value may be nil, or if non-nil, value must be one of: `cache`, `full-scan`, `joins`, `sample`.
-
-*  **`max_computation_cost`** value may be nil, or if non-nil, value must be one of: `linear`, `unbounded`, `yolo`.
-
-
-## `GET /api/x-ray/compare/table/:table-id/card/:card-id`
-
-Get comparison x-ray of a table and a card.
-
-##### PARAMS:
-
-*  **`table-id`** 
-
-*  **`card-id`** 
-
-*  **`max_query_cost`** value may be nil, or if non-nil, value must be one of: `cache`, `full-scan`, `joins`, `sample`.
-
-*  **`max_computation_cost`** value may be nil, or if non-nil, value must be one of: `linear`, `unbounded`, `yolo`.
-
-
-## `GET /api/x-ray/compare/table/:table-id/segment/:segment-id`
-
-Get comparison x-ray of a table and a segment.
-
-##### PARAMS:
-
-*  **`table-id`** 
-
-*  **`segment-id`** 
-
-*  **`max_query_cost`** value may be nil, or if non-nil, value must be one of: `cache`, `full-scan`, `joins`, `sample`.
-
-*  **`max_computation_cost`** value may be nil, or if non-nil, value must be one of: `linear`, `unbounded`, `yolo`.
-
-
-## `GET /api/x-ray/compare/tables/:table1-id/:table2-id`
-
-Get comparison x-ray of two tables.
-
-##### PARAMS:
-
-*  **`table1-id`** 
-
-*  **`table2-id`** 
-
-*  **`max_query_cost`** value may be nil, or if non-nil, value must be one of: `cache`, `full-scan`, `joins`, `sample`.
-
-*  **`max_computation_cost`** value may be nil, or if non-nil, value must be one of: `linear`, `unbounded`, `yolo`.
-
-
-## `GET /api/x-ray/field/:id`
-
-X-ray a field.
-
-##### PARAMS:
-
-*  **`id`** 
-
-*  **`max_query_cost`** value may be nil, or if non-nil, value must be one of: `cache`, `full-scan`, `joins`, `sample`.
-
-*  **`max_computation_cost`** value may be nil, or if non-nil, value must be one of: `linear`, `unbounded`, `yolo`.
-
-
-## `GET /api/x-ray/metric/:id`
-
-X-ray a metric.
-
-##### PARAMS:
-
-*  **`id`** 
-
-*  **`max_query_cost`** value may be nil, or if non-nil, value must be one of: `cache`, `full-scan`, `joins`, `sample`.
-
-*  **`max_computation_cost`** value may be nil, or if non-nil, value must be one of: `linear`, `unbounded`, `yolo`.
-
-
-## `GET /api/x-ray/segment/:id`
-
-X-ray a segment.
-
-##### PARAMS:
-
-*  **`id`** 
-
-*  **`max_query_cost`** value may be nil, or if non-nil, value must be one of: `cache`, `full-scan`, `joins`, `sample`.
-
-*  **`max_computation_cost`** value may be nil, or if non-nil, value must be one of: `linear`, `unbounded`, `yolo`.
-
-
-## `GET /api/x-ray/table/:id`
-
-X-ray a table.
-
-##### PARAMS:
-
-*  **`id`** 
-
-*  **`max_query_cost`** value may be nil, or if non-nil, value must be one of: `cache`, `full-scan`, `joins`, `sample`.
-
-*  **`max_computation_cost`** value may be nil, or if non-nil, value must be one of: `linear`, `unbounded`, `yolo`.
-
-
-## `POST /api/x-ray/compare/card/:id/query`
-
-Get comparison x-ray of card and ad-hoc query.
-
-##### PARAMS:
-
-*  **`id`** 
-
-*  **`max_query_cost`** value may be nil, or if non-nil, value must be one of: `cache`, `full-scan`, `joins`, `sample`.
-
-*  **`max_computation_cost`** value may be nil, or if non-nil, value must be one of: `linear`, `unbounded`, `yolo`.
-
-*  **`query`** 
-
-
-## `POST /api/x-ray/compare/segment/:id/query`
-
-Get comparison x-ray of segment and ad-hoc query.
-
-##### PARAMS:
-
-*  **`id`** 
-
-*  **`max_query_cost`** value may be nil, or if non-nil, value must be one of: `cache`, `full-scan`, `joins`, `sample`.
-
-*  **`max_computation_cost`** value may be nil, or if non-nil, value must be one of: `linear`, `unbounded`, `yolo`.
-
-*  **`query`** 
-
-
-## `POST /api/x-ray/compare/table/:id/query`
-
-Get comparison x-ray of table and ad-hoc query.
-
-##### PARAMS:
-
-*  **`id`** 
-
-*  **`max_query_cost`** value may be nil, or if non-nil, value must be one of: `cache`, `full-scan`, `joins`, `sample`.
-
-*  **`max_computation_cost`** value may be nil, or if non-nil, value must be one of: `linear`, `unbounded`, `yolo`.
-
-*  **`query`** 
-
-
-## `POST /api/x-ray/query`
-
-X-ray a query.
-
-##### PARAMS:
-
-*  **`max_query_cost`** value may be nil, or if non-nil, value must be one of: `cache`, `full-scan`, `joins`, `sample`.
-
-*  **`max_computation_cost`** value may be nil, or if non-nil, value must be one of: `linear`, `unbounded`, `yolo`.
-
-*  **`query`** 
\ No newline at end of file
+*  **`password`** Insufficient password strength
\ No newline at end of file
diff --git a/docs/developers-guide.md b/docs/developers-guide.md
index 115ee34fa09b2845364382d83462335db678333d..c42a3bda79ec91fec71e3cf5287d3d4b1cae9a96 100644
--- a/docs/developers-guide.md
+++ b/docs/developers-guide.md
@@ -111,7 +111,7 @@ $ yarn run build-watch
 All frontend tests are located in `frontend/test` directory. Run all frontend tests with
 
 ```
-./bin/build version uberjar && yarn run test
+yarn run test
 ```
 
 which will first build the backend JAR and then run integration, unit and Karma browser tests in sequence.
@@ -123,10 +123,9 @@ Integration tests use an enforced file naming convention `<test-suite-name>.inte
 
 Useful commands:
 ```bash
-./bin/build version uberjar # Builds the JAR without frontend assets; run this every time you need to update the backend
 lein run refresh-integration-test-db-metadata # Scan the sample dataset and re-run sync/classification/field values caching
 yarn run test-integrated-watch # Watches for file changes and runs the tests that have changed
-yarn run test-integrated-watch -- TestFileName # Watches the files in paths that match the given (regex) string
+yarn run test-integrated-watch TestFileName # Watches the files in paths that match the given (regex) string
 ```
 
 The way integration tests are written is a little unconventional so here is an example that hopefully helps in getting up to speed:
@@ -266,7 +265,7 @@ Start up an instant cheatsheet for the project + dependencies by running
     lein instant-cheatsheet
 
 ## Internationalization
-We are an application with lots of users all over the world. To help them use Metabase in their own language, we mark all of our strings as i18n. 
+We are an application with lots of users all over the world. To help them use Metabase in their own language, we mark all of our strings as i18n.
 ### The general workflow for developers is:
 
 1. Tag strings in the frontend using `t` and `jt` ES6 template literals (see more details in https://c-3po.js.org/):
diff --git a/docs/information-collection.md b/docs/information-collection.md
index 5b33220c58131527a8ab18d00b27244735f269ab..5856fcfdedfbbc4e9e47d7802e9280f98817f809 100644
--- a/docs/information-collection.md
+++ b/docs/information-collection.md
@@ -59,7 +59,7 @@ NOTE: we never capture any specific details in any of our tracking methodology s
 
 ### Server-side Analytics
 | Metric |  An example of why we collect this |
-|---------|--------|--------------------|
+|---------|--------|
 | Number of Users/Admins and whether SSO is enabled | To understand which auth methods are being used, and whether to prioritize features that scale with # of users. |
 | Number of user groups | Action | To understand how complicated a permissions model most of our users have, and to make sure that we don't over-simplify our designs. |
 | Number of Dashboards | Action | Whether we need to provide ways to organize dashboards. |
diff --git a/docs/operations-guide/running-metabase-on-docker.md b/docs/operations-guide/running-metabase-on-docker.md
index 3eb7e77ec8830eaf7f8a876649b5bcb540a72a00..f9f3f8bd5db081db36b86cc7e51c632dfd334c8c 100644
--- a/docs/operations-guide/running-metabase-on-docker.md
+++ b/docs/operations-guide/running-metabase-on-docker.md
@@ -141,4 +141,4 @@ On some hosts Metabase can fail to start with an error message like:
     java.lang.OutOfMemoryError: Java heap space
 
 If that happens, you'll need to set a JVM option to manually configure the maximum amount of memory the JVM uses for the heap. Refer
-to [these instructions](./start.html#metabase-fails-to-start-due-to-heap-space-outofmemoryerrors) for details on how to do that.
+to [these instructions](../troubleshooting-guide/running.md) for details on how to do that.
diff --git a/docs/operations-guide/running-the-metabase-jar-file.md b/docs/operations-guide/running-the-metabase-jar-file.md
index 6ddbc7a8f6a7f49dbc1ce6700c3122f96a8c6b03..3269d2e9571829303aeccd9d6659cd1695e7e6c5 100644
--- a/docs/operations-guide/running-the-metabase-jar-file.md
+++ b/docs/operations-guide/running-the-metabase-jar-file.md
@@ -19,7 +19,7 @@ You should see output such as:
     Java(TM) SE Runtime Environment (build 1.8.0_31-b13)
     Java HotSpot(TM) 64-Bit Server VM (build 25.31-b07, mixed mode)
 
-If you did not see the output above and instead saw either an error or your Java version is less than 1.7, then you need to install the Java Runtime.
+If you did not see the output above and instead saw either an error or your Java version is less than 1.8, then you need to install the Java Runtime.
 
 [OpenJDK Downloads](http://openjdk.java.net/install/)
 [Oracle's Java Downloads](http://www.oracle.com/technetwork/java/javase/downloads/index.html)
diff --git a/docs/troubleshooting-guide/email.md b/docs/troubleshooting-guide/email.md
index 4c798c64162f9b9368247f0799850d863d721245..aeac1772bbefaa83ac47228016f49eca4bceb994 100644
--- a/docs/troubleshooting-guide/email.md
+++ b/docs/troubleshooting-guide/email.md
@@ -19,6 +19,7 @@
 
 4. For user accounts specifically, did you previously create an account under this email and then delete it? This occasionally results in that email address being "claimed".
 
+5. Make sure that the HOSTNAME is being set correctly. EC2 instances in particular have those set to the local ip, and some email delivery services such as GMail will error out in this situation.
 
 ## Specific Problems:
 
@@ -27,4 +28,4 @@
 ###  Metabase can't send email via Office365
 
 We see users report issues with sending email via Office365. We recommend using a different email delivery service if you can. 
-https://github.com/metabase/metabase/issues/4272
\ No newline at end of file
+https://github.com/metabase/metabase/issues/4272
diff --git a/docs/users-guide/01-what-is-metabase.md b/docs/users-guide/01-what-is-metabase.md
index 3ba08779f728debc9032303b9caaa07940166c0f..1ed4cc455ccc0e06349abd3771c3ab02793a716c 100644
--- a/docs/users-guide/01-what-is-metabase.md
+++ b/docs/users-guide/01-what-is-metabase.md
@@ -1,6 +1,54 @@
 ## What is Metabase?
-Metabase is an open source business intelligence tool. It lets you ask questions about your data and displays answers in formats that make sense, whether that's a bar graph or a detailed table.  
 
-Your questions can be saved for later, making it easy to come back to them, or you can group questions into great looking dashboards. Metabase also makes it easy to share questions and dashboards with the rest of your team. 
+Metabase is an open source business intelligence tool. It lets you ask questions about your data, and displays answers in formats that make sense, whether that's a bar graph or a detailed table.
+
+Your questions can be saved for later, making it easy to come back to them, or you can group questions into great looking dashboards. Metabase also makes it easy to share questions and dashboards with the rest of your team.
+
+## Finding your way around
+
+So, you've [gotten Metabase up and running](../operations-guide/start.md) and [connected it to your data](../administration-guide/01-managing-databases.md). It's time to give you the lay of the land.
+
+### The home page
+
+![The home page](./images/metabase-homepage.png)
+
+Fresh out of the box, Metabase will show you a few things on the home page:
+* Some [automatic explorations](14-x-rays.md) of your tables that you can look at and save as a dashboard if you like any of them.
+* An area where things you or your teammates create will show up, along with a link to see all the dashboards, questions, and pulses you have.
+* A list of the databases you've connected to Metabase.
+
+![Our data](./images/our-data.png)
+
+Once you've created some [dashboards](07-dashboards.md), any of them that you pin will show up on the homepage for all of your teammates, so that when they log in to Metabase they'll know right where to go.
+
+### Browse your data
+
+![Browse data](./images/browse-data.png)
+
+If you connected your database to Metabase during setup, you'll see it listed at the bottom of the homepage along with the sample dataset that Metabase comes with. Click on a database to see its contents. You can click on a table to see its rows, or you can also click on the bolt icon to x-ray a table and see an automatic exploration of it, or click on the book icon to go to the data reference view for that table to learn more about it.
+
+### Explore your analytics
+
+As you and your team create dashboards and collections, they'll start to show up on the homepage. Click on a collection in the "Our analytics" section to see its contents, or click "browse all items" to see everything you and your team have made. [More about exploring](03-basic-exploration.md)
+
+### Ask a question or write a query
+
+Click the `Ask a question button` in the top-right of Metabase to start a new custom exploration of one of your tables, or to write a new SQL or native query if you want to really dig in.
+
+### Make a new dashboard or pulse
+
+In Metabase, dashboards are made up of saved questions that you can arrange and resize as you please. They're a great way to track important metrics and stats that you care about. Pulses are what regularly scheduled reports are called in Metabase. They can be sent out either via email, Slack, or both.
+
+To make a dashboard or pulse, click the plus (+) icon in the top-right of the main navigation bar.
+
+![Create menu](./images/create-menu.png)
+
+### Use search to quickly find things
+
+![Search results](./images/search-results.png)
+
+The search bar that's always present at the top of the screen lets you search through your dashboards, collections, saved questions, and pulses in an instant. Just type part of the title of the thing you're looking for and hit enter.
+
+## A primer on databases
 
 To fully understand how to use Metabase, it’s useful to have at least a high-level understanding of databases, so we'll discuss [the basics of databases](02-database-basics.md) next.
diff --git a/docs/users-guide/03-basic-exploration.md b/docs/users-guide/03-basic-exploration.md
index 47da7d701aed58a3e6b61fe0a91a6c66a70f343d..b2df4bb89ded93acb94569e40f6cf0ccd4c87508 100644
--- a/docs/users-guide/03-basic-exploration.md
+++ b/docs/users-guide/03-basic-exploration.md
@@ -1,30 +1,44 @@
 ### Exploring in Metabase
-As long as you're not the very first user in your team's Metabase, the easiest way to start exploring your data is by looking at dashboards, charts, and lists that your teammates have already created.
+
+#### See what your teammates have made
+As long as you're not the very first user in your team's Metabase, the easiest way to start exploring your data is by looking at dashboards, charts, and lists that your teammates have already created. The best place to start is by checking out any dashboards that might be pinned on your home page or in the collections you have access to.
+
+#### Browse your data
+Alternatively, you can dive right in to exploring the data in Metabase by clicking on one of the databases at the bottom of the home page, and then clicking on a table to see it. You can also click on the bolt icon on any table to see an automatic exploration of its data. Give it a try!
+
+![Browse data](./images/browse-data.png)
+
+#### Exploring collections
+Collections in Metabase are a lot like folders. They're where all your team's dashboards and charts are kept. To explore a collection just click on one in the "Our analytics" section of the home page, or click on `Browse all items` to see everything.
+
+![A collection](./images/collection-detail.png)
+
+If your teammates are cool, they'll have pinned some important dashboards or questions within your collections; if so, those important or useful items will show up nice and large at the top of a collection. Collections also have a list of any other items that are saved within them, as well as a list of other collections that are saved inside the current one.
 
 #### Exploring dashboards
-Click on the `Dashboards` nav item to see all the dashboards your teammates have created. Dashboards are simply collections of charts and numbers that you want to be able to refer back to regularly. (You can learn more about dashboards [here](07-dashboards.md))
+Dashboards are simply collections of charts and numbers that you want to be able to refer back to regularly. You can learn more about dashboards [here](07-dashboards.md).
 
-If you click on a part of a chart, such as a bar in a bar chart, or a dot on a line chart, you'll see a menu with actions you can take to dive deeper into that result, or to branch off from it in a different direction.
+If you click on a part of a chart, such as a bar in a bar chart, or a dot on a line chart, you'll see a menu with actions you can take to dive deeper into that result, to branch off from it in a different direction, or to [x-ray](14-x-rays.md) it to see an automatic exploration the thing you clicked on.
 
 ![Drill through](images/drill-through/drill-through.png)
 
 In this example of pie orders by type over time, clicking on a dot on this line chart gives us the ability to:
-- Zoom in — i.e., see just the banana cream pie orders in June 2017 over time
-- View these Orders — which lets us see a list of banana cream pie orders in June 2017
-- Break out by a category — this lets us do things like see the banana cream pie orders in June 2017 broken out by the status of the customer (e.g., `new` or `VIP`, etc.) or other different aspects of the order. Different charts will have different break out options, such as Location and Time.
+- **Zoom in** — i.e., see just the banana cream pie orders in June 2017 over time
+- **View these Orders** — which lets us see a list of banana cream pie orders in June 2017
+- **Break out by a category** — this lets us do things like see the banana cream pie orders in June 2017 broken out by the status of the customer (e.g., `new` or `VIP`, etc.) or other different aspects of the order. Different charts will have different break out options, such as Location and Time.
+
+**Note that charts created with SQL don't currently have these action options.**
 
 Other charts as well as table cells will often also allow you to go to a filtered view of that chart or table. You can click on one of the inequality symbols to see that chart where, for example, the value of the Subtotal column is less than $100, or where the Purchased-at timestamp is greater than (i.e., after) April 1, 2017.
 
 ![Inequality filters](images/drill-through/inequality-filters.png)
 
-Lastly, clicking on the ID of an item in table gives you the option to go to a detail view for that single record. (E.g., you can click on a customer's ID to see the profile view for that one customer.)
-
-**Note that charts created with SQL don't currently have these action options.**
+Lastly, clicking on the ID of an item in a table gives you the option to go to a detail view for that single record. (E.g., you can click on a customer's ID to see the profile view for that one customer.)
 
 #### Exploring saved questions
-In Metabase parlance, every chart or number on a dashboard is called a "question." Clicking on the title of a question on a dashboard will take you to a detail view of that question. You'll also end up at this detail view if you use one of the actions mentioned above. You can also browse all the questions your teammates have saved by clicking the `Questions` link in the main navigation.
+In Metabase parlance, every chart or number on a dashboard is called a "question." Clicking on the title of a question on a dashboard will take you to a detail view of that question. You'll also end up at this detail view if you use one of the actions mentioned above.
 
-When you're viewing the detail view of a question, you can use all the same actions mentioned above. You can also click on the headings of tables to see more options, like viewing the sum of the values in a column, or finding the minimum or maximum value in it.
+When you're looking at the detail view of a question, you can use all the same actions mentioned above. You can also click on the headings of tables to see more options, like viewing the sum of the values in a column, or finding the minimum or maximum value in it.
 
 ![Heading actions](images/drill-through/heading-actions.png)
 
@@ -33,15 +47,17 @@ Additionally, the question detail page has an Explore button in the bottom-right
 ![Action menu](images/drill-through/actions.png)
 
 Here's a list of all the actions:
-* Table actions
+* **Table actions**
+  - `X-ray this` will show you an automatic exploration and summary of the data in this table. [Learn more about x-rays](14-x-rays.md)
   - `Count of rows by time` lets you see how many rows there were in this table over time.
   - `Summarize this segment` gives you options of various summarization functions (sum, average, maximum, etc.) you can use on this table to arrive at a number.
-* Chart and pivot table actions
+* **Chart and pivot table actions**
   - `Break outs` will be listed depending on the question, and include the option to break out by a category, location, or time. For example, if you're looking at the count of total orders over time, you might be able to further break that out by customer gender, if that information is present.
   - `View this as a table` does what it says. Every chart has a table behind it that is providing the data for the chart, and this action lets you see that table.
   - `View the underlying records` shows you the un-summarized list of records underlying the chart or number you're currently viewing.
+  - `X-ray this question` will show you an automatic [x-ray]((14-x-rays.md)) exploration of this question's results.
 
 ---
 
-## Next: Asking new questions
+## Next: Asking custom questions
 So what do you do if you can't find an existing dashboard or question that's exactly what you're looking for? Let's learn about [asking our own new questions](04-asking-questions.md)
diff --git a/docs/users-guide/04-asking-questions.md b/docs/users-guide/04-asking-questions.md
index 509b4c846b8fc1eef926a7f41d24ee9a45eeeda5..1989b06a1f67896a3fd28b606ddde3babd8ab53d 100644
--- a/docs/users-guide/04-asking-questions.md
+++ b/docs/users-guide/04-asking-questions.md
@@ -1,21 +1,20 @@
 
-## Asking questions
+## Asking custom questions
 ---
 Metabase's two core concepts are questions and their corresponding answers. Everything else is based around questions and answers. To ask Metabase a question, click the New Question button at the top of the screen.
 
 ### Ways to start a new question
 
-If an administrator has [defined some metrics or segments](../administration-guide/07-segments-and-metrics.md), when you click on the New Question button, you'll see a screen like this one:
+If an administrator has [defined some metrics](../administration-guide/07-segments-and-metrics.md), when you click on the `Ask a question` button in the top bar you'll see a screen like this one:
 
 ![New question options](images/new-question-all-options.png)
 
 You can start your new question:
 - from an existing metric
-- from an existing segment
 - from scratch with the Question Builder interface
-- or using the SQL / native query editor
+- using the SQL / native query editor
 
-Asking a new question about a **metric** or a **segment** is often a great place to start.
+Asking a new question about a **metric** is often a great place to start.
 
 #### Asking a new question about a metric
 
@@ -31,25 +30,23 @@ You can also use the Action Menu in the bottom-right of the screen to choose a b
 
 ![Metric action menu](images/metric-action-menu.png)
 
-#### Asking a new question about a segment
+#### Asking a new question about a table
 
-A **segment** is any kind of list or table of things that your company cares about: returning users, orders that used a certain promo code, or sales leads that need to be followed up with are all examples of possible segments.
+Another quick way to start a new question is by clicking on one of your connected databases at the bottom of the homepage, and picking a table that you have a question about. You'll immediately see the table and the graphical question builder so that you can keep exploring.
 
-Selecting the Segment option from the new question menu will show you a list of your company's segments. When you click on one, you'll see a list, like this one:
+![Browse data](./images/browse-data.png)
 
-![Californians segment](images/segment-californians.png)
-
-When viewing a segment or a table, you can click on the headings of columns to see options for ways to explore more, like seeing the distribution of the values a column has, or the number of distinct values:
+When viewing a table you can also click on the headings of columns to see options for ways to explore more, like clicking on the Age column of your Users table to see how many Users you have per age group (that's called a "distribution"):
 
 ![Table heading actions](images/table-heading-actions.png)
 
-You can also use the Action Menu when viewing a segment or table to see any metrics that are related, or to summarize the table.
+You can also use the Action Menu when viewing a table to see any metrics in it, or to summarize the table.
 
 ![Table action menu](images/segment-actions.png)
 
 #### Asking a new custom question
 
-If your team hasn't set up any metrics or segments, or if you have a question that isn't covered by an existing question or segment, you can create a custom question using the Question Builder interface by clicking "Custom." Or, if you're an advanced user, you can click "SQL" to go straight to the SQL/native query editor.
+If you have a question that isn't covered by an existing question, you can create a new custom question using the Question Builder interface by clicking "Custom." Or, if you're an advanced user, you can click "SQL" to go straight to the SQL/native query editor.
 
 
 ### Using the Question Builder interface
@@ -61,7 +58,7 @@ Metabase has a simple graphical question builder that looks like this:
 The question builder is made up of four distinct sections, from left to right:
 - **Data**, where you pick the source data you want to ask a question about
 - **Filters**, where you can optionally add one or more filters to narrow down your source data
-- **View**, where you choose what you want to see — raw table data, a basic metric, or a saved metric
+- **View**, where you choose what you want to see — raw table data, a basic metric, or a "common" metric that an administrator has defined
 - **Groupings**, where you can group or break out your metric by time, location, or other categories
 
 #### Source data
@@ -86,7 +83,7 @@ You can use most saved questions as source data, provided you have [permission](
 
 #### Filters
 ---
-Filtering your data lets you exclude information that you don’t want. You can filter by any field in the table you're working with, or by any tables that are connected through a foreign key. Filters narrow down the source data to an interesting subset, like "active users" or "bookings after June 15th, 2015."  
+Filtering your data lets you exclude information that you don’t want. You can filter by any field in the table you're working with, or by any tables that are connected through a foreign key. Filters narrow down the source data to an interesting subset, like "active users" or "bookings after June 15th, 2015."
 
 Different fields will have different filter options based on what kind of data type they are. There are four universal filter options, or “operators,” that can be applied to any field. These operators are:
 
@@ -105,27 +102,25 @@ Fields that are comparable, like numbers or dates, can also be filtered using th
 
 ##### Filtering by dates
 
-If filtering by dates, a date picker will appear to allow you to select dates easily. You have two main options for picking your date: relative or specific.
+If filtering by dates, a date picker will appear to allow you to select dates easily, and will default to the previous 30 days.
 
-**Specific Dates**
-This is the most basic way to select dates. You just click on the date you want from the calendar. If you click on a second date, the picker will select all the dates in between the two you clicked on, creating a range. Clicking on any date while you have a range selected will clear the range. You can also use the **All before** and **All after** buttons to quickly select all dates before or after the one you’ve selected.
+Click on the first dropdown to change the kind of date filter you're using. The rest of the popup menu will change depending on this first selection.
 
-**Relative Dates**
-Relative dates are how we more commonly talk about time: “how many customers did we have **last month**?” We talk about time relative to today.
+One important thing to understand when filtering by time or dates like this is the difference between specific and relative dates:
 
-In practice, if you select **Past 30 days** from the Relative Date calendar picker, this would be the same as selecting those same dates from the Specific Date picker — *unless* you save your question and look at it again tomorrow.
+**Specific dates** are things like November 1, 2010, or June 3 – July 12, 2017; they always refer to the same date(s).
 
-Now the relative date will be referencing the past 30 days from *today*, *not* from the day you saved the question. This is a really useful way of creating and saving questions that stay up-to-date: you can always know what your total sales were in the past 7 days, for example.
+**Relative dates** are things like "the past 30 days," or "the current week;" as time passes, the dates these refer to change. Relative dates are a useful way to set up a filter on a question so that it stays up to date by showing you for example how many users visited your website in the last 7 days.
 
 ##### Using segments
-If your Metabase admins have created special named filters, called segments, for the table you’re viewing, they’ll appear at the top of the filter dropdown in purple text with a star next to them. These are shortcuts to sets of filters that are commonly used in your organization. They might be something like “Active Users,” or “Most Popular Products.”
+If your Metabase administrators have created special named filters for the table you're viewing they’ll appear at the top of the filter dropdown in purple text with a star next to them. These are called "segments," and they're shortcuts to filters that are commonly used in your organization. They might be called things like “Active Users,” or “Most Popular Products.”
 
 #### Selecting answer output in the View section
 ---
 The next section of the question builder is where you select what you want the output of your answer to be, under the View dropdown. You’re basically telling Metabase, “I want to view…” Metabase can output the answer to your question in four different ways:
 
 ##### 1. Raw data
-Raw Data is just a table with the answer listed in rows.  It's useful when you want to see the actual data you're working with, rather than a sum or average, etc., or when you're exploring a small table with a limited number of records.  
+Raw Data is just a table with the answer listed in rows.  It's useful when you want to see the actual data you're working with, rather than a sum or average, etc., or when you're exploring a small table with a limited number of records.
 
 When you filter your data to see groups of interesting users, orders, etc., Raw Data will show you an output of each individual record that matches your question's criteria.
 
diff --git a/docs/users-guide/05-visualizing-results.md b/docs/users-guide/05-visualizing-results.md
index a9189aa701ef190d6e66824e9cab496346baf500..ffaac59c495404d59f8967bca093e08ce5cd5f2a 100644
--- a/docs/users-guide/05-visualizing-results.md
+++ b/docs/users-guide/05-visualizing-results.md
@@ -39,7 +39,32 @@ Progress bars are for comparing a single number result to a goal value that you
 ![Progress bar](images/visualizations/progress.png)
 
 #### Tables
-The Table option is good for looking at tabular data (duh), or for lists of things like users. The options allow you to hide and rearrange fields in the table you're looking at. If your table is a result that contains one metric and two dimensions, Metabase will also automatically pivot your table, like in the example below (the example shows the count of orders grouped by the review rating for that order and the category of the product that was purchased; you can tell it's pivoted because the grouping field values are all in the first column and first row). You can turn this behavior off in the chart settings.
+The Table option is good for looking at tabular data (duh), or for lists of things like users or orders. The visualization options for tables allow you to add, hide, or rearrange fields in the table you're looking at.
+
+##### Adding or hiding fields
+
+![Additional fields](images/visualizations/add-fields.png)
+
+Open up the visualization options and you'll see the Data tab, which displays all the fields currently being shown in the table, as well as more fields from linked tables that you can add to the current table view.
+
+To hide a field, click the X icon on it; that'll send it down to the "More fields" area in case you want to bring it back. To add a linked field, just click the + icon on it, which will bring it to the "Visible fields" section. Click and drag any of the fields listed there to rearrange the order in which they appear.
+
+**Note:** changing these options doesn't change the actual table itself; it just creates a custom view of it that you can save as a "question" in Metabase and refer back to later, share with others, or add to a dashboard.
+
+##### Conditional formatting
+Sometimes is helpful to highlight certain rows or columns in your tables when they meet a specific condition. You can set up conditional formatting rules by going to the visualization settings while looking at any table, then clicking on the `Formatting` tab.
+
+![Conditional formatting](images/visualizations/conditional-formatting.png)
+
+When you add a new rule, you'll first need to pick which column(s) should be affected. For now, you can only pick numeric columns. Your columns can be formatted in one of two ways:
+
+1. **Single color:** pick this if you want to highlight cells in the column if they're greater, less than, or equal to a specific number. You can optionally highlight the whole row of a cell that matches the condition you pick so that it's easier to spot as you scroll down your table.
+2. **Color range:** choose this option if you want to tint all the cells in the column from smallest to largest or vice a versa.
+
+You can set as many rules on a table as you want. If two or more rules disagree with each other, the rule that's on the top of your list of rules will win. You can click and drag your rules to reorder them, and click on a rule to edit it.
+
+##### Pivoted tables
+If your table is a result that contains one metric and two dimensions, Metabase will also automatically "pivot" your table, like in the example below (the example shows the count of orders grouped by the review rating for that order and the category of the product that was purchased; you can tell it's pivoted because the grouping field values are all in the first column and first row). You can turn this behavior off in the chart settings.
 
 ![Pivot table](images/visualizations/pivot.png)
 
diff --git a/docs/users-guide/06-sharing-answers.md b/docs/users-guide/06-sharing-answers.md
index 7c80b05099b877fbc3263c1317bdf5283859ce50..c293c56f5bbf3234049a969903a518b6fbe9609b 100644
--- a/docs/users-guide/06-sharing-answers.md
+++ b/docs/users-guide/06-sharing-answers.md
@@ -1,15 +1,15 @@
 
-## Sharing your questions and answers
+## Sharing and organizing your questions and answers
 ---
 
 ### How to save a question
 Whenever you’ve arrived at an answer that you want to save for later, click the **SAVE** button in the top right of the screen. This will also save the visualization option you’ve chosen for your answer.
 
-![savebutton](images/SaveButton.png)
+![Save button](images/SaveButton.png)
 
-A pop-up box will appear, you to give your question a name and a description. We suggest phrasing the names for your questions in the form of a question, such as, “How many customers did we have last month?” After saving your question, you'll be asked if you want to add it to a dashboard.
+A pop-up box will appear, prompting you to give your question a name and description, and to pick which collection to save it in. Note that your administrator might have set things up so that you're only allowed to save questions in certain collections, but you can always save things in your Personal Collection. After saving your question, you'll be asked if you want to add it to a new or existing dashboard.
 
-Now, whenever you want to refer to your question again you can find it in the saved questions list by clicking on the **Questions** link from the main navigation. To edit your question, go to it and click the pencil icon in the top-right.
+Now, whenever you want to refer to your question again you can find it by searching for it in the search bar at the top of Metabase, or by navigating to the collection where you saved it. To edit your question, go to it and click the pencil icon in the top-right.
 
 ### Sharing questions with public links
 If your Metabase administrator has enabled [public sharing](../administration-guide/12-public-links.md) on a saved question or dashboard, you can go to that question or dashboard and click on the sharing icon to find its public links. Public links can be viewed by anyone, even if they don't have access to Metabase. You can also use the public embedding code to embed your question or dashboard in a simple web page or blog post.
@@ -17,34 +17,50 @@ If your Metabase administrator has enabled [public sharing](../administration-gu
 ![Share icon](images/share-icon.png)
 
 ### Organizing and finding your saved questions
-After your team has been using Metabase for a while, you’ll probably end up with lots of saved questions. The Questions page has several tools that’ll help you organize things and find what you’re looking for.
+After your team has been using Metabase for a while, you’ll probably end up with lots of saved questions. Metabase has several ways to help you organize things and find what you’re looking for.
 
-![Questions](images/saved-questions.png)
+![Our analytics](images/our-analytics-page.png)
 
 #### Collections
-Administrators of Metabase can create collections to put saved questions in. Depending on the permissions you've been given to collections, you'll be able to view the questions inside, edit them, and move questions from one collection to another. Questions that aren't saved in any collection will appear in the "Everything else" section of the main Questions page, and are visible to all Metabase users in your organization. If you're an administrator of your Metabase instance, here are [instructions for creating collections and managing permissions](../administration-guide/06-collections.md).
+Collections are the main way to organize questions, as well as dashboards and pulses. [Administrators can give you different kinds of access](../administration-guide/06-collections.md) to each collection:
 
-#### Shortcuts
-At the top of lists of saved questions you’ll find a dropdown with shortcuts to your favorite questions (mark a question as a favorite by clicking on the star icon that appears when you hover over it), questions you’ve recently viewed, questions that you’ve saved personally, and popular questions that are used the most by your team.
+- **View access:** you can see the collection and its contents, but you can't modify anything or put anything new into the collection.
+- **Curate access:** you can edit, move, or archive the collection and its contents. You can also move or save new things in it and create new collections inside of it, and can also pin items in the collection to the top of the screen. Only administrators can edit permissions for collections, however.
+- **No access:** you can't see the collection or its contents. If you have access to a dashboard, but it contains questions that are saved in a collection you don't have access to, those questions will show a permissions notification instead of the chart or table.
 
-#### Search and filtering
-On the main Questions page, you can search through all of your collections for a particular question using the search box in the top-right. You can also filter lists of saved questions by typing in the `Filter the list…` area.
+#### Your personal collection
+In addition to the collections you and your teammates have made, you'll also always have your own personal collection that only you and administrators can see. To find it, click on the "browse all items" button on the homepage and click on "my personal collection" in the list of collections.
 
-#### Moving
-To move a question into a collection, or from one collection to another, hover over it and click on the right-arrow icon that appears on the far right of the question. Note that you have to have permission to edit the collection that you're moving a question into, and the collection you're moving the question out of.
+You can use your personal collection as a scratch space to put experiments and explorations that you don't think would be particularly interesting to the rest of your team, or as a work-in-progress space where you can work on things and then move them to a shared place once they're ready.
 
-#### Archiving
-Sometimes questions outlive their usefulness and need to be sent to Question Heaven. To archive a question, just click on the box icon that appears on the far right when you hover over a question. Collections can also be archived and unarchived, but only by Metabase administrators.
+#### Pinned items
+
+![Pins](images/pinned-items.png)
+
+In each collection, you can pin important or useful dashboards or questions to make them stick to the top of the screen. Pinned items will also be displayed as large cards to make them stand out well. If you have Curate permissions for a collection, you can pin and un-pin things, and drag and drop pins to change their order.
+
+Any dashboards that are pinned in the main "Our analytics" collection will also show up on the homepage.
+
+#### Search
 
-Note that archiving a question removes it from all dashboards or Pulses where it appears, so be careful!
+![Search results](./images/search-results.png)
 
-If you have second thoughts and want to bring an archived question back, you can see all your archived questions from the **Archive** icon at the top-right of the Questions page. To unarchive a question, hover over it and click the box icon that appears on the far right.
+Type into the search box that's at the top of Metabase and hit enter to search through all the dashboards, questions, collections, and pulses your team has.
 
-#### Selecting multiple questions
-Clicking on the icon to the left of questions let's you select several at once so that you can move or archive many questions at once.
+#### Moving
+To move a question, dashboard, or pulse into a collection, or from one collection to another, just click and drag it onto the collection where you want it to go. You can also click on the `…` menu to the right of the question and pick the Move action. If you're trying to move several things at once, click on the items' icons to select them, then click the Move action that pops up at the bottom of the screen.
 
 ![Selecting questions](images/question-checkbox.png)
 
+Note that you have to have Curate permission for the collection that you're moving a question into *and* the collection you're moving the question out of.
+
+#### Archiving
+Sometimes questions outlive their usefulness and need to be sent to Question Heaven. To archive a question or dashboard, just click on the `…` menu that appears on the far right when you hover over a question and pick the Archive action. You'll only see that option if you have "curate" permission for the current collection. You can also archive multiple items at once, the same way as you move multiple items. Note that archiving a question removes it from all dashboards or Pulses where it appears, so be careful!
+
+You can also archive *collections* as long as you have curate permissions for the  collection you're trying to archive, the collection *it's* inside of, as well as any and all collections inside of *it*. Archiving a collection archives all of its contents as well.
+
+If you have second thoughts and want to bring an archived item back, you can see all your archived questions from the archive; click the menu icon in the top-right of any collection page to get to the archive. To unarchive a question, hover over it and click the unarchive icon that appears on the far right.
+
 ---
 
 ## Next: creating dashboards
diff --git a/docs/users-guide/07-dashboards.md b/docs/users-guide/07-dashboards.md
index 8008029fd4932acd63b217a72b893b818ca6e4f1..4c1e6ddff48b42abf91c9fab587c0712c6b2d2dc 100644
--- a/docs/users-guide/07-dashboards.md
+++ b/docs/users-guide/07-dashboards.md
@@ -10,12 +10,12 @@ Have a few key performance indicators that you want to be able to easily check?
 You can make as many dashboards as you want. Go nuts.
 
 ### How to create a dashboard
-Once you have a question saved, you can create a dashboard. Click the **Dashboards** link at the top of the screen, then click the plus icon in the top-right to create a new dashboard. Give your new dashboard a name and a description, then click **Create**, and you’ll be taken to your shiny new dashboard.
+Click the plus (+) icon in the top-right of the screen to open the menu to create a new dashboard. Give your new dashboard a name and a description, choose which collection it should be saved in, then click **Create**, and you’ll be taken to your shiny new dashboard.
 
 ![Create Dashboard](images/dashboards/DashboardCreate.png)
 
 ### Adding saved questions to a dashboard
-You can add a newly saved question to a dashboard directly from the window that pops up after you save the question, or by clicking the Add to Dashboard icon in the top-right of a question page. You can also go to one of your dashboards and click the plus icon in the top right to add any of your saved questions to the dashboard.
+You can add a newly saved question to a dashboard directly from the window that pops up after you save the question, or by clicking the Add to Dashboard icon in the top-right of a question page. You can also go to one of your dashboards and click the plus icon in the top right to add any of your saved questions to the dashboard. Dashboards and the questions they contain do not need to be saved in the same collection.
 
 Once you add a question to your dashboard, it’ll look something like this:
 
@@ -57,14 +57,12 @@ Questions in your dashboard will automatically update their display based on the
 ### Archiving a dashboard
 Archiving a dashboard does not archive the individual saved questions on it — it just archives the dashboard. To archive a dashboard while viewing it, click the pencil icon to enter edit mode, then click the Archive button.
 
-You can view all of your archived dashboards by clicking the box icon in the top-right of the Dashboards page. Archived dashboards in this list can be unarchived by clicking the icon of the box with the upward arrow next to that dashboard.
-
-(Note: as of Metabase v0.24, dashboards can no longer be permanently deleted; only archived.)
+You can view all of your archived items by clicking the menu icon in the top-right of any collection page. Archived dashboards in this list can be unarchived by clicking the icon of the box with the upward arrow next to that dashboard.
 
 ### Finding dashboards
-After a while, your team might have a lot of dashboards. To make it a little easier to find dashboards that you look at often, you can mark a dashboard as a favorite by clicking the star icon on it from the dashboards list. You can use the filter dropdown in the top of the list to view only your favorite dashboards, or only the ones that you created yourself.
+After a while, your team might have a lot of dashboards. To make it a little easier to find dashboards that your team looks at often, you can pin them to the top of the collection by clicking and dragging them to the top or by opening the `…` menu and selecting the Pin action.
 
-![Filter list](images/dashboards/FilterDashboards.png)
+You can also search for any dashboard (or question, collection, or pulse) by its title in the big search box at the top of Metabase.
 
 ### Fullscreen dashboards
 
diff --git a/docs/users-guide/09-multi-series-charting.md b/docs/users-guide/09-multi-series-charting.md
index f626a7d641e247ff534e6490deb208321614a3fb..174839b81ee6258b310bded3fe89dfa1aff7cafa 100644
--- a/docs/users-guide/09-multi-series-charting.md
+++ b/docs/users-guide/09-multi-series-charting.md
@@ -43,10 +43,8 @@ Once you have your chart looking how you’d like, hit done and your changes wil
 #### A quick note about SQL based questions.
 Metabase has less information about SQL based questions, so we cannot guarantee if they can be added reliably. You'll see a little warning sign next to SQL questions to indicate this and when you try adding them just be aware it may not work.
 
-###  Combining scalars
-If you need to compare flat numbers and get a sense of how they differ, Metabase also lets you turn multiple scalars into a bar chart. To do this, follow the same process outlined above. While editing a dashboard, click “edit data” on the scalar of your choice and then select the other scalars you’d like to see represented on the bar chart.
-
-At Metabase, we use this to create simple funnel visualizations.
+###  Combining Number charts
+If you need to compare single numbers and get a sense of how they differ, Metabase also lets you turn multiple Number charts into a bar chart. To do this, follow the same process outlined above. While editing a dashboard, click “edit data” on the Number chart of your choice and then select the other saved question(s) you’d like to see represented on the bar chart. (At Metabase, we use this to create simple funnel visualizations.)
 
 ### Creating a multi-series visualization in the query builder.
 If you’re creating a new question in the query builder, you can also view the result as a multi-series visualization. To do this you’ll need to add two dimensions to your question and use an aggregation that isn’t just “raw data.”
diff --git a/docs/users-guide/10-pulses.md b/docs/users-guide/10-pulses.md
index 511f5cdc65d2b33c24cd41a5c941f2aa8fb00ba2..36612bed79117555c9224f029ba04b015a6fd331 100644
--- a/docs/users-guide/10-pulses.md
+++ b/docs/users-guide/10-pulses.md
@@ -2,12 +2,10 @@
 ## Sharing updates with pulses
 The Pulses feature in Metabase gives you the ability to automatically send regular updates to your teammates to help everyone keep track of changes to the metrics that matter to you most. You can deliver a pulse via email or [Slack](https://slack.com/), on the schedule of your choice.
 
-Click the `Pulses` link in the top menu to view all of your pulses, and click `Create a pulse` to make a new one.
-
-![Create a pulse](images/pulses/01-empty-state.png)
+To create a new pulse, click the plus (+) button in the top-right of Metabase and select `New pulse`.
 
 ### Name it
-First, choose a name for your pulse. This will show up in the email subject line and the Slack message title, so choose something that will let people know what kind of updates the pulse will contain, like “Daily Marketing Update,” or “Users Metrics.”
+First, choose a name for your pulse. This will show up in the email subject line and the Slack message title, so choose something that will let people know what kind of updates the pulse will contain, like “Daily Marketing Update,” or “Users Metrics.” Next, choose which collection it should be saved in so that it's easy to find in the future.
 
 ![Giving it a name](images/pulses/02-name-it.png)
 
@@ -51,18 +49,12 @@ Each pulse you create can be delivered by email, Slack, or both. You can also se
 
 To send via Slack, you’ll need to choose which channel you want to post the pulse in, whether you want it to post hourly or daily, and at what time. Again, the schedule for Slack can be different from the schedule for email.
 
-Once you’re done, just click `Create pulse`. You’ll see your new pulse, along with its recipients, and the saved questions that are included in the pulse. If anyone else on your team wants to subscribe to a pulse that’s delivered by email, they can click the button that says `Get this email` from the Pulses screen.
-
-![A beautiful, completed pulse](images/pulses/06-created.png)
-
-### Editing and Deleting a Pulse
-If you ever need to make changes to a pulse, just hover over the pulse from the list and click the `edit` button that appears.
-
-![Edit button](images/pulses/07-edit-button.png)
+Once you’re done, just click `Create pulse` and you’ll see your new pulse in the collection where you chose to save it
 
-If you want to delete a pulse, you can find that option at the bottom of the edit screen. Just remember: if you delete a pulse, no one will receive it anymore.
+### Editing or archiving a pulse
+If you ever need to make changes to a pulse, just navigate to the collection where it's saved and click on it, or search for it in the big search bar at the top of Metabase.
 
-![The danger zone](images/pulses/08-delete.png)
+If a pulse has outlived its usefulness you can archive it by clicking on the Archive button at the bottom of the pulse's detail page. Just remember: if you archive a pulse, no one will receive it anymore. You can unarchive a pulse just like you can with questions and dashboards by navigating to the archive from the top-right button while viewing any collection and clicking on the View Archive menu option, then clicking on the `Unarchive this` button on the far right next to the pulse.
 
 ---
 
diff --git a/docs/users-guide/13-sql-parameters.md b/docs/users-guide/13-sql-parameters.md
index 953543fb20b38b03359fd9a5aa7cab9ad115192c..2794f2512d8f7490679672c6dde6b4ba9f5e4ea0 100644
--- a/docs/users-guide/13-sql-parameters.md
+++ b/docs/users-guide/13-sql-parameters.md
@@ -94,5 +94,5 @@ WHERE True
 
 ---
 
-## That’s it!
-If you still have questions, or want to share Metabase tips and tricks, head over to our [discussion board](http://discourse.metabase.com/). See you there!
+## Next: automated x-ray explorations
+Learn about how to easily and quickly see automatic explorations of your data with Metabase's powerful [x-ray feature](14-x-rays.md).
diff --git a/docs/users-guide/14-x-rays.md b/docs/users-guide/14-x-rays.md
index 09946cf01a2d6df3a1876b3c35a4f3b0dfbff5bd..153994c1790acbfc3fafb2de6ac038a6c214e6a0 100644
--- a/docs/users-guide/14-x-rays.md
+++ b/docs/users-guide/14-x-rays.md
@@ -2,39 +2,75 @@
 ---
 X-rays are a fast and easy way to get automatic insights and explorations of your data.
 
-### Exploring newly added datasets
+### Viewing x-rays by clicking on charts or tables
 
-When you first connect a database to Metabase, Metabot will offer to show you some automated explorations of your data.
+One great way to explore your data in general in Metabase is to click on points of interest in charts or tables, which shows you ways to further explore that point. We've added x-rays to this action menu, so if you for example find a point on your line chart that seems extra interesting, give it a click and x-ray it! We think you'll like what you see.
 
-![X-ray example](./images/x-rays/suggestions.png)
+![X-ray action in drill-through menu](images/x-rays/drill-through.png)
 
-Click on one of these to see an x-ray.
+### Comparisons
 
-![X-ray example](./images/x-rays/example.png)
+When you click on a bar or point on a chart, you can now also choose the Compare action from the menu that pops up to see how the thing you've clicked on compares to the rest of the data.
 
-You can see more suggested x-rays over on the right-hand side of the screen. Browsing through x-rays like this is a pretty fun way of getting a quick overview of your data.
+![Compare menu](images/x-rays/x-ray-compare-popover.png)
 
-### Saving x-rays
+If you're already looking at an x-ray of a table or a segment, Metabase will also give you the option to compare the current table or segment to other segments of the table, if there are any. This is a very fast, powerful way to see, for example, how different segments of your users or orders compare to each other.
 
-If you're logged in as an Administrator and you come across an x-ray that's particularly interesting, you can save it as a dashboard by clicking the green Save button. Metabase will create a new dashboard for you and put all of its charts in a new collection. The new collection and dashboard will only be visible to other Administrators by default.
+![Comparison](images/x-rays/x-ray-comparison-1.png)
+![Comparison](images/x-rays/x-ray-comparison-2.png)
 
-To quickly make your new dashboard visible to other users, go to the collection with its charts, click the lock icon to edit the collection's permissions, and choose which groups should be allowed to view the charts in this collection. Note that this might allow users to see charts and data that they might not normally have access to. For more about how Metabase handles permissions, check out these posts about [collection permissions](../administration-guide/06-collections.md) and [data access permissions](../administration-guide/05-setting-permissions.md).
 
-### Creating x-rays by clicking on charts or tables
+### Table x-rays
 
-One great way to explore your data in general in Metabase is to click on points of interest in charts or tables, which shows you ways to further explore that point. We've added x-rays to this action menu, so if you for example find a point on your line chart that seems extra interesting, give it a click and x-ray it! We think you'll like what you see.
+Another great way to get to know your data is by x-raying your tables. From the home page, scroll to the bottom of the screen, click on one of your connected databases, and then click the bolt icon on a table to view an x-ray of it.
+
+### X-raying question results
 
-![X-ray action in drill-through menu](./images/x-rays/drill-through.png)
+You can also see an x-ray of the results of a saved or unsaved question by clicking the blue compass button in the bottom-right of the question screen and selecting the x-ray action. This will show you an x-ray of the numbers and fields in your question's results.
 
-### X-rays in the Data Reference
+![X-ray results](images/x-rays/x-ray-action.png)
 
-You can also create an x-ray by navigating to a table, field, metric, or segment in the [Data Reference](./12-data-model-reference.md). Just click the x-ray link in the left sidebar.
+## X-rays in the Data Reference
 
-![Data Reference x-ray](./images/x-rays/data-reference.png)
+You can also view an x-ray by navigating to a table, field, metric, or segment in the [Data Reference](./12-data-model-reference.md). Just click the x-ray link in the lefthand sidebar.
+
+![Data Reference x-ray](images/x-rays/data-reference.png)
+
+### Browsing through x-rays
+
+One fun and interesting thing you can do once you're looking at an x-ray is to click and browse through the list of suggested next x-rays that show up in the righthand column.
+
+Depending on the x-ray you're currently viewing, you'll see suggestions that will let you:
+
+- compare the table or segment you're currently x-raying to another segment
+- "zoom out" and view an x-ray of the table the current x-ray is based on
+- "zoom in" to see a more detailed x-ray about a field or dimension of the current x-ray
+- go to an x-ray of a related item, like a metric based on the current table, or a different table that's related to the current one
+
+### Exploring newly added datasets
+
+If you're an administrator, when you first connect a database to Metabase, Metabot will offer to show you some automated explorations of your newly-connected data.
+
+![X-ray example](images/x-rays/suggestions.png)
+
+Click on one of these to see an x-ray.
+
+![X-ray example](images/x-rays/example.png)
+
+You can see more suggested x-rays over on the right-hand side of the screen. Browsing through x-rays like this is a pretty fun way of getting a quick overview of your data.
+
+### Saving x-rays
+
+If you come across an x-ray that's particularly interesting, you can save it as a dashboard by clicking the green Save button. Metabase will create a new dashboard and put it and all of its charts in a new collection, and will save this new collection wherever you choose.
 
 ### Where did the old x-rays go?
 
-We're reworking the way we do things like time series growth analysis and segment comparison, which were present in the previous version of x-rays. In the meantime, we've removed those previous x-rays, and will bring those features back in a more elegant and streamlined way in a future version of Metabase.
+We're reworking the way we do things like time series growth analysis, which was present in past versions of x-rays. In the meantime, we've removed those previous x-rays, and will bring those features back in a more elegant and streamlined way in a future version of Metabase.
 
-## Need help?
+### Need help?
 If you still have questions about x-rays or comparisons, you can head over to our [discussion forum](http://discourse.metabase.com/). See you there!
+
+---
+
+## Next: setting up alerts
+Learn how to get notified when one of your questions meets or goal or has results with [alerts](15-alerts.md).
diff --git a/docs/users-guide/15-alerts.md b/docs/users-guide/15-alerts.md
index 28d96a19ef2b67cf15d5ce2cfaace91da949bdcc..761fc45ba79495381e4cc3f033b6cb41c2a50902 100644
--- a/docs/users-guide/15-alerts.md
+++ b/docs/users-guide/15-alerts.md
@@ -3,7 +3,7 @@
 Whether you're keeping track of revenue, users, or negative reviews, there are often times when you want to be alerted about something. Metabase has a few different kinds of alerts you can set up, and you can choose to be notified via email or Slack.
 
 ### Getting alerts
-To start using alerts, someone on your team who's an administrator will need to make sure that [email integration](../administrator-guide/02-setting-up-email.md) is set up first.
+To start using alerts, someone on your team who's an administrator will need to make sure that [email integration](../administration-guide/02-setting-up-email.md) is set up first.
 
 ### Types of alerts
 There are three kinds of things you can get alerted about in Metabase:
@@ -16,7 +16,7 @@ We'll go through these one by one.
 ### Goal line alerts
 This kind of alert is useful when you're doing things like tracking daily active users and you want to know when you reach a certain number of them, or when you're tracking orders per week and you want to know whenever that number ever goes below a certain threshold.
 
-To start, you'll need a line, area, or bar chart displaying a number over time. (If you need help with that, check out the page on [asking questions](04-asking-questions).)
+To start, you'll need a line, area, or bar chart displaying a number over time. (If you need help with that, check out the page on [asking questions](04-asking-questions.md).)
 
 Now we need to set up a goal line. To do that, open up the visualization settings by clicking the gear icon next to the dropdown where you chose your chart type. Then click on the Display tab, and turn on the "Show goal" setting. Choose a value for your goal and click Done.
 
@@ -67,5 +67,7 @@ There are a few ways alerts can be stopped:
 - If a saved question that has an alert on it gets edited in such a way that the alert doesn't make sense anymore, the alert will get deleted. For example, if a saved question with a goal line alert on it gets edited, and the goal line is removed entirely, that alert will get deleted.
 - If a question gets archived, any alerts on it will be deleted.
 
+---
+
 ## That’s it!
 If you still have questions about using alerts, you can head over to our [discussion forum](http://discourse.metabase.com/). See you there!
diff --git a/docs/users-guide/images/MultiSeriesFinished.png b/docs/users-guide/images/MultiSeriesFinished.png
index 0b1b8cbfd051960c199522c81f90449903657c33..6c3fa315523e94b55976a000129e94a04dfd3f9e 100644
Binary files a/docs/users-guide/images/MultiSeriesFinished.png and b/docs/users-guide/images/MultiSeriesFinished.png differ
diff --git a/docs/users-guide/images/MultiSeriesQueryBuilder.png b/docs/users-guide/images/MultiSeriesQueryBuilder.png
index 4ebc8b367f6e66521c5fdd0640a2eb0db78545db..f786b6fcbb1537fbdb159df90fec98507276f326 100644
Binary files a/docs/users-guide/images/MultiSeriesQueryBuilder.png and b/docs/users-guide/images/MultiSeriesQueryBuilder.png differ
diff --git a/docs/users-guide/images/MultiSeriesTrigger.png b/docs/users-guide/images/MultiSeriesTrigger.png
index 87031ade0e1b1715ad7e21fc9ac5ad482746803f..c3d1a92ab368be0508f917cb44fa98050f48b9c6 100644
Binary files a/docs/users-guide/images/MultiSeriesTrigger.png and b/docs/users-guide/images/MultiSeriesTrigger.png differ
diff --git a/docs/users-guide/images/browse-data.png b/docs/users-guide/images/browse-data.png
new file mode 100644
index 0000000000000000000000000000000000000000..423205c7fdfdf7482782c08070552760dba6cf99
Binary files /dev/null and b/docs/users-guide/images/browse-data.png differ
diff --git a/docs/users-guide/images/collection-detail.png b/docs/users-guide/images/collection-detail.png
new file mode 100644
index 0000000000000000000000000000000000000000..5dd7fe9277762c16d3f42b6ee64c0a576450f582
Binary files /dev/null and b/docs/users-guide/images/collection-detail.png differ
diff --git a/docs/users-guide/images/create-menu.png b/docs/users-guide/images/create-menu.png
new file mode 100644
index 0000000000000000000000000000000000000000..6c93c4798b0f53282f34354ade51eb0725cbfbf3
Binary files /dev/null and b/docs/users-guide/images/create-menu.png differ
diff --git a/docs/users-guide/images/dashboard-filters/dashboard-filters.png b/docs/users-guide/images/dashboard-filters/dashboard-filters.png
index fb7047329b4c72431aa7f0fb2ed14b4f2ffa2dcf..a0c1f3fdbaff1eea4a582073a7b073b8f6fb52b7 100644
Binary files a/docs/users-guide/images/dashboard-filters/dashboard-filters.png and b/docs/users-guide/images/dashboard-filters/dashboard-filters.png differ
diff --git a/docs/users-guide/images/dashboards/DashboardAutorefresh.png b/docs/users-guide/images/dashboards/DashboardAutorefresh.png
index 575e3e7ede5c3a39fd9ed0c821028c6438505645..181d45553fedbe2960df63f7817a90272bedd491 100644
Binary files a/docs/users-guide/images/dashboards/DashboardAutorefresh.png and b/docs/users-guide/images/dashboards/DashboardAutorefresh.png differ
diff --git a/docs/users-guide/images/dashboards/DashboardCreate.png b/docs/users-guide/images/dashboards/DashboardCreate.png
index d70d98746be14ceb6cd4146d123fb4bea31e0e3f..c91a061360afedc84ffa5f131183d1733466a367 100644
Binary files a/docs/users-guide/images/dashboards/DashboardCreate.png and b/docs/users-guide/images/dashboards/DashboardCreate.png differ
diff --git a/docs/users-guide/images/dashboards/FilterDashboards.png b/docs/users-guide/images/dashboards/FilterDashboards.png
deleted file mode 100644
index 256a90cbd5b8016f6de73ec6aabbf7601f7c710e..0000000000000000000000000000000000000000
Binary files a/docs/users-guide/images/dashboards/FilterDashboards.png and /dev/null differ
diff --git a/docs/users-guide/images/dashboards/FirstDashboard.png b/docs/users-guide/images/dashboards/FirstDashboard.png
index 0b4d9084dffb5de17d2b9c0c79237c715ccae5de..a26f97c24747fa2ec9f4623a1e9cd04923574628 100644
Binary files a/docs/users-guide/images/dashboards/FirstDashboard.png and b/docs/users-guide/images/dashboards/FirstDashboard.png differ
diff --git a/docs/users-guide/images/dashboards/text-cards/new-text-card.png b/docs/users-guide/images/dashboards/text-cards/new-text-card.png
index 1b39a41df73724105e9e6d45a2c3c6225f327b2a..4447cfd80b324fd748147c208efc596e675c1fcf 100644
Binary files a/docs/users-guide/images/dashboards/text-cards/new-text-card.png and b/docs/users-guide/images/dashboards/text-cards/new-text-card.png differ
diff --git a/docs/users-guide/images/homepage-x-rays.png b/docs/users-guide/images/homepage-x-rays.png
new file mode 100644
index 0000000000000000000000000000000000000000..800eaef33e9b77693affa25877e33d35f008c85e
Binary files /dev/null and b/docs/users-guide/images/homepage-x-rays.png differ
diff --git a/docs/users-guide/images/metabase-homepage.png b/docs/users-guide/images/metabase-homepage.png
new file mode 100644
index 0000000000000000000000000000000000000000..2618c7204ea86349ba48d167b5c10eb83595caa2
Binary files /dev/null and b/docs/users-guide/images/metabase-homepage.png differ
diff --git a/docs/users-guide/images/metric-action-menu.png b/docs/users-guide/images/metric-action-menu.png
index a43ca62351abd6cc76de3fba1d85e0eb31edd550..c84407afa661081cdc4fe3cd7a8f4657b6a4d921 100644
Binary files a/docs/users-guide/images/metric-action-menu.png and b/docs/users-guide/images/metric-action-menu.png differ
diff --git a/docs/users-guide/images/metric-drill-through.png b/docs/users-guide/images/metric-drill-through.png
index 094d0a1df59a0a3778caeb37e922d41ee45c47c8..9415a5f9c927fe2a972617a5156b56d20011337f 100644
Binary files a/docs/users-guide/images/metric-drill-through.png and b/docs/users-guide/images/metric-drill-through.png differ
diff --git a/docs/users-guide/images/metrics-list.png b/docs/users-guide/images/metrics-list.png
index 4abc90ff8d3b784575167c88bdab9b0171c5def2..95b21fb6b7e36dbcd64e0367154846bdcc0832a1 100644
Binary files a/docs/users-guide/images/metrics-list.png and b/docs/users-guide/images/metrics-list.png differ
diff --git a/docs/users-guide/images/new-question-all-options.png b/docs/users-guide/images/new-question-all-options.png
index d9abb89be58d514d4f4df379ffbcc8e044826c43..fa0a3c9c794e8cbb2d635e0555476f2f4735fe13 100644
Binary files a/docs/users-guide/images/new-question-all-options.png and b/docs/users-guide/images/new-question-all-options.png differ
diff --git a/docs/users-guide/images/our-analytics-page.png b/docs/users-guide/images/our-analytics-page.png
new file mode 100644
index 0000000000000000000000000000000000000000..ccd13915992b2baee6d16bd8c33a0f474c715ab5
Binary files /dev/null and b/docs/users-guide/images/our-analytics-page.png differ
diff --git a/docs/users-guide/images/our-data.png b/docs/users-guide/images/our-data.png
new file mode 100644
index 0000000000000000000000000000000000000000..b50515d710e06a45a9a3632bb2dc881cd830d6b4
Binary files /dev/null and b/docs/users-guide/images/our-data.png differ
diff --git a/docs/users-guide/images/pinned-items.png b/docs/users-guide/images/pinned-items.png
new file mode 100644
index 0000000000000000000000000000000000000000..0453c64cb496530ad7ade11c241f4d49e772f5a8
Binary files /dev/null and b/docs/users-guide/images/pinned-items.png differ
diff --git a/docs/users-guide/images/pulses/01-empty-state.png b/docs/users-guide/images/pulses/01-empty-state.png
deleted file mode 100644
index 754ae15f99a5d46e999277f721132408916b75fc..0000000000000000000000000000000000000000
Binary files a/docs/users-guide/images/pulses/01-empty-state.png and /dev/null differ
diff --git a/docs/users-guide/images/pulses/02-name-it.png b/docs/users-guide/images/pulses/02-name-it.png
index d4402c280ae1a68e490092f30884a83c73e4a3a6..e213cd79e36a1278aebd522356d8e2936e337bcd 100644
Binary files a/docs/users-guide/images/pulses/02-name-it.png and b/docs/users-guide/images/pulses/02-name-it.png differ
diff --git a/docs/users-guide/images/pulses/03-pick-your-data.png b/docs/users-guide/images/pulses/03-pick-your-data.png
index 6a3bb322f934d20e0328912fbe9f01672a693abd..1e6d730059ee09abf2c9cf785209ce34715fac0f 100644
Binary files a/docs/users-guide/images/pulses/03-pick-your-data.png and b/docs/users-guide/images/pulses/03-pick-your-data.png differ
diff --git a/docs/users-guide/images/pulses/06-created.png b/docs/users-guide/images/pulses/06-created.png
deleted file mode 100644
index f28e570fbf9cb417d458932e817a870b64627990..0000000000000000000000000000000000000000
Binary files a/docs/users-guide/images/pulses/06-created.png and /dev/null differ
diff --git a/docs/users-guide/images/pulses/07-edit-button.png b/docs/users-guide/images/pulses/07-edit-button.png
deleted file mode 100644
index 20e480ec3fb4cde5986dab36e87af53502c75325..0000000000000000000000000000000000000000
Binary files a/docs/users-guide/images/pulses/07-edit-button.png and /dev/null differ
diff --git a/docs/users-guide/images/saved-questions.png b/docs/users-guide/images/saved-questions.png
deleted file mode 100644
index 91c771735e8881859e4c34bb7242c96f667f73c3..0000000000000000000000000000000000000000
Binary files a/docs/users-guide/images/saved-questions.png and /dev/null differ
diff --git a/docs/users-guide/images/search-results.png b/docs/users-guide/images/search-results.png
new file mode 100644
index 0000000000000000000000000000000000000000..1ecc3053cf328fa26aed14314af2933960a9924c
Binary files /dev/null and b/docs/users-guide/images/search-results.png differ
diff --git a/docs/users-guide/images/visualizations/add-fields.png b/docs/users-guide/images/visualizations/add-fields.png
new file mode 100644
index 0000000000000000000000000000000000000000..988927d832addb29b497cf05d24fb19f4160f60f
Binary files /dev/null and b/docs/users-guide/images/visualizations/add-fields.png differ
diff --git a/docs/users-guide/images/visualizations/conditional-formatting.png b/docs/users-guide/images/visualizations/conditional-formatting.png
new file mode 100644
index 0000000000000000000000000000000000000000..5d60851d3df313450237e8c51ceab98082715a9b
Binary files /dev/null and b/docs/users-guide/images/visualizations/conditional-formatting.png differ
diff --git a/docs/users-guide/images/x-ray-action-time-series.png b/docs/users-guide/images/x-ray-action-time-series.png
deleted file mode 100644
index 63d87d366ba3c20237e16c4bc5eb786a4a9c5c0d..0000000000000000000000000000000000000000
Binary files a/docs/users-guide/images/x-ray-action-time-series.png and /dev/null differ
diff --git a/docs/users-guide/images/x-ray-action.png b/docs/users-guide/images/x-ray-action.png
deleted file mode 100644
index 71af33ef3215b973245ab1e630ff8fdd91599141..0000000000000000000000000000000000000000
Binary files a/docs/users-guide/images/x-ray-action.png and /dev/null differ
diff --git a/docs/users-guide/images/x-ray-compare-button.png b/docs/users-guide/images/x-ray-compare-button.png
deleted file mode 100644
index 6d57067985412353e6f18e5dc69263405f35eb89..0000000000000000000000000000000000000000
Binary files a/docs/users-guide/images/x-ray-compare-button.png and /dev/null differ
diff --git a/docs/users-guide/images/x-ray-comparison.png b/docs/users-guide/images/x-ray-comparison.png
deleted file mode 100644
index 16168753f55ffe08f8589009b93954c11a99bf6d..0000000000000000000000000000000000000000
Binary files a/docs/users-guide/images/x-ray-comparison.png and /dev/null differ
diff --git a/docs/users-guide/images/x-ray-data-reference.png b/docs/users-guide/images/x-ray-data-reference.png
deleted file mode 100644
index 2ffd2f11471c03756203b5675f4245e0f33562ac..0000000000000000000000000000000000000000
Binary files a/docs/users-guide/images/x-ray-data-reference.png and /dev/null differ
diff --git a/docs/users-guide/images/x-ray-fidelity.png b/docs/users-guide/images/x-ray-fidelity.png
deleted file mode 100644
index c69df7cf32aeb518e6d105fd07ff755ecaca453a..0000000000000000000000000000000000000000
Binary files a/docs/users-guide/images/x-ray-fidelity.png and /dev/null differ
diff --git a/docs/users-guide/images/x-ray-time-series.png b/docs/users-guide/images/x-ray-time-series.png
deleted file mode 100644
index b702da3c37bf76185d1ae8de5456b479a4d03576..0000000000000000000000000000000000000000
Binary files a/docs/users-guide/images/x-ray-time-series.png and /dev/null differ
diff --git a/docs/users-guide/images/x-ray.png b/docs/users-guide/images/x-ray.png
deleted file mode 100644
index 5e37259405f75d8aa105180568209864e882552b..0000000000000000000000000000000000000000
Binary files a/docs/users-guide/images/x-ray.png and /dev/null differ
diff --git a/docs/users-guide/images/x-rays/example.png b/docs/users-guide/images/x-rays/example.png
index 629cb481dc3b92199c8907ebe025f02b77d15c2c..4dde0fe6a443cca9744faaa4777b138ece1035ea 100644
Binary files a/docs/users-guide/images/x-rays/example.png and b/docs/users-guide/images/x-rays/example.png differ
diff --git a/docs/users-guide/images/x-rays/x-ray-action.png b/docs/users-guide/images/x-rays/x-ray-action.png
new file mode 100644
index 0000000000000000000000000000000000000000..3dce83a3e075a23f124892cdb4e07e154fc8ad87
Binary files /dev/null and b/docs/users-guide/images/x-rays/x-ray-action.png differ
diff --git a/docs/users-guide/images/x-rays/x-ray-compare-popover.png b/docs/users-guide/images/x-rays/x-ray-compare-popover.png
new file mode 100644
index 0000000000000000000000000000000000000000..c871ef88cb01a4b1349943f84122b31ed8081697
Binary files /dev/null and b/docs/users-guide/images/x-rays/x-ray-compare-popover.png differ
diff --git a/docs/users-guide/images/x-rays/x-ray-comparison-1.png b/docs/users-guide/images/x-rays/x-ray-comparison-1.png
new file mode 100644
index 0000000000000000000000000000000000000000..0d84e73273153b271012e8d1341d0efb6dd2de05
Binary files /dev/null and b/docs/users-guide/images/x-rays/x-ray-comparison-1.png differ
diff --git a/docs/users-guide/images/x-rays/x-ray-comparison-2.png b/docs/users-guide/images/x-rays/x-ray-comparison-2.png
new file mode 100644
index 0000000000000000000000000000000000000000..258ad9fd8e48d769d1e8eb059e8814303d952e37
Binary files /dev/null and b/docs/users-guide/images/x-rays/x-ray-comparison-2.png differ
diff --git a/docs/users-guide/start.md b/docs/users-guide/start.md
index 37cc3ea6441293378eea4f0e8fd8d879c6664ee9..a737d161e266d50f99fc8df314992a373fa82df5 100644
--- a/docs/users-guide/start.md
+++ b/docs/users-guide/start.md
@@ -2,7 +2,7 @@
 
 **This guide will teach you:**
 
-*   [What Metabase does](01-what-is-metabase.md)
+*   [An overview of Metabase](01-what-is-metabase.md)
 *   [The basics of database terminology](02-database-basics.md)
 *   [Basic exploration in Metabase](03-basic-exploration.md)
 *   [Asking questions in Metabase](04-asking-questions.md)
diff --git a/frontend/interfaces/grid-styled.js b/frontend/interfaces/grid-styled.js
new file mode 100644
index 0000000000000000000000000000000000000000..e5a24d142d420ff1918ed19010f5958fd5b7211e
--- /dev/null
+++ b/frontend/interfaces/grid-styled.js
@@ -0,0 +1,3 @@
+declare module "grid-styled" {
+  declare var exports: any;
+}
diff --git a/frontend/lint/eslint-rules/no-color-literals.js b/frontend/lint/eslint-rules/no-color-literals.js
new file mode 100644
index 0000000000000000000000000000000000000000..32f96d1e393bf67716dc846af09d78badc251bbc
--- /dev/null
+++ b/frontend/lint/eslint-rules/no-color-literals.js
@@ -0,0 +1,39 @@
+/**
+ * @fileoverview Rule to disallow color literals
+ * @author Tom Robinson
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+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}\))/g;
+const LINT_MESSAGE =
+  "Color literals forbidden. Import colors from 'metabase/lib/colors'.";
+
+module.exports = {
+  meta: {
+    docs: {
+      description: "disallow color literals",
+      category: "Possible Errors",
+      recommended: true,
+    },
+    schema: [], // no options
+  },
+  create: function(context) {
+    return {
+      Literal(node) {
+        if (typeof node.value === "string" && COLOR_REGEX.test(node.value)) {
+          context.report({ node, message: LINT_MESSAGE });
+        }
+      },
+      TemplateLiteral(node) {
+        if (node.quasis.filter(q => COLOR_REGEX.test(q.value.raw)).length > 0) {
+          context.report({ node, message: LINT_MESSAGE });
+        }
+      },
+    };
+  },
+};
diff --git a/frontend/src/metabase-lib/lib/Question.js b/frontend/src/metabase-lib/lib/Question.js
index 48cdca51409534e991cf543456437dcc855e1ace..0ee2e88756a7921b4a93b9302e1ce62b9fb7921a 100644
--- a/frontend/src/metabase-lib/lib/Question.js
+++ b/frontend/src/metabase-lib/lib/Question.js
@@ -40,6 +40,8 @@ import type {
 } from "metabase/meta/types/Card";
 
 import { MetabaseApi, CardApi } from "metabase/services";
+import Questions from "metabase/entities/questions";
+
 import AtomicQuery from "metabase-lib/lib/queries/AtomicQuery";
 
 import type { Dataset } from "metabase/meta/types/Dataset";
@@ -315,7 +317,7 @@ export default class Question {
           type: "query",
           database: SAVED_QUESTIONS_FAUX_DATABASE,
           query: {
-            source_table: "card__" + this.id(),
+            "source-table": "card__" + this.id(),
           },
         },
       };
@@ -407,6 +409,31 @@ export default class Question {
     }
   }
 
+  getComparisonDashboardUrl(filters /*?: Filter[] = []*/) {
+    let cellQuery = "";
+    if (filters.length > 0) {
+      const mbqlFilter = filters.length > 1 ? ["and", ...filters] : filters[0];
+      cellQuery = `/cell/${Card_DEPRECATED.utf8_to_b64url(
+        JSON.stringify(mbqlFilter),
+      )}`;
+    }
+    const questionId = this.id();
+    const query = this.query();
+    if (query instanceof StructuredQuery) {
+      const tableId = query.tableId();
+      if (tableId) {
+        if (questionId != null && !isTransientId(questionId)) {
+          return `/auto/dashboard/question/${questionId}${cellQuery}/compare/table/${tableId}`;
+        } else {
+          const adHocQuery = Card_DEPRECATED.utf8_to_b64url(
+            JSON.stringify(this.card().dataset_query),
+          );
+          return `/auto/dashboard/adhoc/${adHocQuery}${cellQuery}/compare/table/${tableId}`;
+        }
+      }
+    }
+  }
+
   setResultsMetadata(resultsMetadata) {
     let metadataColumns = resultsMetadata && resultsMetadata.columns;
     let metadataChecksum = resultsMetadata && resultsMetadata.checksum;
@@ -471,16 +498,30 @@ export default class Question {
     }
   }
 
+  // NOTE: prefer `reduxCreate` so the store is automatically updated
   async apiCreate() {
-    const createdCard = await CardApi.create(this.card());
+    const createdCard = await Questions.api.create(this.card());
     return this.setCard(createdCard);
   }
 
+  // NOTE: prefer `reduxUpdate` so the store is automatically updated
   async apiUpdate() {
-    const updatedCard = await CardApi.update(this.card());
+    const updatedCard = await Questions.api.update(this.card());
     return this.setCard(updatedCard);
   }
 
+  async reduxCreate(dispatch) {
+    const action = await dispatch(Questions.actions.create(this.card()));
+    return this.setCard(Questions.HACK_getObjectFromAction(action));
+  }
+
+  async reduxUpdate(dispatch) {
+    const action = await dispatch(
+      Questions.actions.update({ id: this.id() }, this.card()),
+    );
+    return this.setCard(Questions.HACK_getObjectFromAction(action));
+  }
+
   // TODO: Fix incorrect Flow signature
   parameters(): ParameterObject[] {
     return getParametersWithExtras(this.card(), this._parameterValues);
@@ -508,7 +549,7 @@ export default class Question {
     } else if (!this._card.id) {
       if (
         this._card.dataset_query.query &&
-        this._card.dataset_query.query.source_table
+        this._card.dataset_query.query["source-table"]
       ) {
         return true;
       } else if (
diff --git a/frontend/src/metabase-lib/lib/metadata/Metric.js b/frontend/src/metabase-lib/lib/metadata/Metric.js
index d4e804504764d14118a6bf23ee3c293d91c5e59a..33aa7f708ffb9e8a0c0bd5766d40f1d0105a16ec 100644
--- a/frontend/src/metabase-lib/lib/metadata/Metric.js
+++ b/frontend/src/metabase-lib/lib/metadata/Metric.js
@@ -16,7 +16,7 @@ export default class Metric extends Base {
   table: Table;
 
   aggregationClause(): Aggregation {
-    return ["METRIC", this.id];
+    return ["metric", this.id];
   }
 
   isActive(): boolean {
diff --git a/frontend/src/metabase-lib/lib/metadata/Segment.js b/frontend/src/metabase-lib/lib/metadata/Segment.js
index 54df1c6180930de1ac07160aad77e033500745fe..431ba1b0ea4c62161dd97103dc7d959303dbd6d1 100644
--- a/frontend/src/metabase-lib/lib/metadata/Segment.js
+++ b/frontend/src/metabase-lib/lib/metadata/Segment.js
@@ -16,7 +16,7 @@ export default class Segment extends Base {
   table: Table;
 
   filterClause(): FilterClause {
-    return ["SEGMENT", this.id];
+    return ["segment", this.id];
   }
 
   isActive(): boolean {
diff --git a/frontend/src/metabase-lib/lib/queries/NativeQuery.js b/frontend/src/metabase-lib/lib/queries/NativeQuery.js
index 542a5d19b2ff6787e90f43b08f5e47a611b57067..6cef33dd1585a4f2ca36351c9d885d56fe64cb23 100644
--- a/frontend/src/metabase-lib/lib/queries/NativeQuery.js
+++ b/frontend/src/metabase-lib/lib/queries/NativeQuery.js
@@ -16,7 +16,7 @@ import {
   getEngineNativeRequiresTable,
 } from "metabase/lib/engine";
 
-import { chain, assoc, getIn, assocIn } from "icepick";
+import { chain, assoc, getIn, assocIn, updateIn } from "icepick";
 import _ from "underscore";
 
 import type {
@@ -32,7 +32,7 @@ export const NATIVE_QUERY_TEMPLATE: NativeDatasetQuery = {
   type: "native",
   native: {
     query: "",
-    template_tags: {},
+    "template-tags": {},
   },
 };
 
@@ -141,7 +141,7 @@ export default class NativeQuery extends AtomicQuery {
       chain(this._datasetQuery)
         .assocIn(["native", "query"], newQueryText)
         .assocIn(
-          ["native", "template_tags"],
+          ["native", "template-tags"],
           this._getUpdatedTemplateTags(newQueryText),
         )
         .value(),
@@ -159,6 +159,24 @@ export default class NativeQuery extends AtomicQuery {
     );
   }
 
+  setParameterIndex(id: string, newIndex: number) {
+    // NOTE: currently all NativeQuery parameters are implicitly generated from
+    // template tags, and the order is determined by the key order
+    return new NativeQuery(
+      this._originalQuestion,
+      updateIn(
+        this._datasetQuery,
+        ["native", "template_tags"],
+        templateTags => {
+          const entries = Array.from(Object.entries(templateTags));
+          const oldIndex = _.findIndex(entries, entry => entry[1].id === id);
+          entries.splice(newIndex, 0, entries.splice(oldIndex, 1)[0]);
+          return _.object(entries);
+        },
+      ),
+    );
+  }
+
   lineCount(): number {
     const queryText = this.queryText();
     return queryText ? countLines(queryText) : 0;
@@ -190,7 +208,7 @@ export default class NativeQuery extends AtomicQuery {
     return Object.values(this.templateTagsMap());
   }
   templateTagsMap(): TemplateTags {
-    return getIn(this.datasetQuery(), ["native", "template_tags"]) || {};
+    return getIn(this.datasetQuery(), ["native", "template-tags"]) || {};
   }
 
   setDatasetQuery(datasetQuery: DatasetQuery): NativeQuery {
diff --git a/frontend/src/metabase-lib/lib/queries/StructuredQuery.js b/frontend/src/metabase-lib/lib/queries/StructuredQuery.js
index 6e251a792633f75938f43ca755b0ff5a23be2665..bc342fe5b0eddf0db4d52d69e2a3606c8450c79e 100644
--- a/frontend/src/metabase-lib/lib/queries/StructuredQuery.js
+++ b/frontend/src/metabase-lib/lib/queries/StructuredQuery.js
@@ -55,7 +55,7 @@ export const STRUCTURED_QUERY_TEMPLATE = {
   database: null,
   type: "query",
   query: {
-    source_table: null,
+    "source-table": null,
   },
 };
 
@@ -95,7 +95,7 @@ export default class StructuredQuery extends AtomicQuery {
       ...STRUCTURED_QUERY_TEMPLATE,
       database: databaseId || null,
       query: {
-        source_table: tableId || null,
+        "source-table": tableId || null,
       },
     };
 
@@ -209,7 +209,7 @@ export default class StructuredQuery extends AtomicQuery {
         this._originalQuestion,
         chain(this.datasetQuery())
           .assoc("database", table.database.id)
-          .assocIn(["query", "source_table"], table.id)
+          .assocIn(["query", "source-table"], table.id)
           .value(),
       );
     } else {
@@ -221,7 +221,7 @@ export default class StructuredQuery extends AtomicQuery {
    * @returns the table ID, if a table is selected.
    */
   tableId(): ?TableId {
-    return this.query().source_table;
+    return this.query()["source-table"];
   }
 
   /**
@@ -282,7 +282,7 @@ export default class StructuredQuery extends AtomicQuery {
   }
 
   /**
-   * @returns an array of aggregation options for the currently selected table, excluding the "rows" pseudo-aggregation
+   * @returns an array of aggregation options for the currently selected table
    */
   aggregationOptionsWithoutRows(): AggregationOption[] {
     return this.aggregationOptions().filter(option => option.short !== "rows");
@@ -499,7 +499,7 @@ export default class StructuredQuery extends AtomicQuery {
       .filter(f => isSegmentFilter(f))
       .map(segmentFilter => {
         // segment id is stored as the second part of the filter clause
-        // e.x. ["SEGMENT", 1]
+        // e.x. ["segment", 1]
         const segmentId = segmentFilter[1];
         return this.metadata().segment(segmentId);
       });
@@ -558,7 +558,7 @@ export default class StructuredQuery extends AtomicQuery {
       const usedFields = new Set(
         this.sorts()
           .filter(b => !_.isEqual(b, sort))
-          .map(b => Q_deprecated.getFieldTargetId(b[0])),
+          .map(b => Q_deprecated.getFieldTargetId(b[1])),
       );
 
       return this.fieldOptions(field => !usedFields.has(field.id));
@@ -593,10 +593,10 @@ export default class StructuredQuery extends AtomicQuery {
     );
   }
 
-  addSort(order_by: OrderBy) {
+  addSort(orderBy: OrderBy) {
     return this._updateQuery(Q.addOrderBy, arguments);
   }
-  updateSort(index: number, order_by: OrderBy) {
+  updateSort(index: number, orderBy: OrderBy) {
     return this._updateQuery(Q.updateOrderBy, arguments);
   }
   removeSort(index: number) {
@@ -605,8 +605,8 @@ export default class StructuredQuery extends AtomicQuery {
   clearSort() {
     return this._updateQuery(Q.clearOrderBy, arguments);
   }
-  replaceSort(order_by: OrderBy) {
-    return this.clearSort().addSort(order_by);
+  replaceSort(orderBy: OrderBy) {
+    return this.clearSort().addSort(orderBy);
   }
 
   // LIMIT
@@ -635,12 +635,24 @@ export default class StructuredQuery extends AtomicQuery {
     return this._updateQuery(Q.removeExpression, arguments);
   }
 
-  // FIELD OPTIONS
+  // FIELDS
+  /**
+   * Returns dimension options that can appear in the `fields` clause
+   */
+  fieldsOptions(dimensionFilter = () => true): DimensionOptions {
+    if (this.isBareRows() && this.breakouts().length === 0) {
+      return this.dimensionOptions(dimensionFilter);
+    }
+    // TODO: allow adding fields connected by broken out PKs?
+    return { count: 0, dimensions: [], fks: [] };
+  }
+
+  // DIMENSION OPTIONS
 
   // TODO Atte Keinänen 6/18/17: Refactor to dimensionOptions which takes a dimensionFilter
   // See aggregationFieldOptions for an explanation why that covers more use cases
-  fieldOptions(fieldFilter = () => true): DimensionOptions {
-    const fieldOptions = {
+  dimensionOptions(dimensionFilter = () => true): DimensionOptions {
+    const dimensionOptions = {
       count: 0,
       fks: [],
       dimensions: [],
@@ -648,11 +660,6 @@ export default class StructuredQuery extends AtomicQuery {
 
     const table = this.tableMetadata();
     if (table) {
-      const dimensionFilter = dimension => {
-        const field = dimension.field && dimension.field();
-        return !field || (field.isDimension() && fieldFilter(field));
-      };
-
       const dimensionIsFKReference = dimension =>
         dimension.field && dimension.field() && dimension.field().isFK();
 
@@ -660,8 +667,8 @@ export default class StructuredQuery extends AtomicQuery {
       // .filter(d => !dimensionIsFKReference(d));
 
       for (const dimension of filteredNonFKDimensions) {
-        fieldOptions.count++;
-        fieldOptions.dimensions.push(dimension);
+        dimensionOptions.count++;
+        dimensionOptions.dimensions.push(dimension);
       }
 
       const fkDimensions = this.dimensions().filter(dimensionIsFKReference);
@@ -671,8 +678,8 @@ export default class StructuredQuery extends AtomicQuery {
           .filter(dimensionFilter);
 
         if (fkDimensions.length > 0) {
-          fieldOptions.count += fkDimensions.length;
-          fieldOptions.fks.push({
+          dimensionOptions.count += fkDimensions.length;
+          dimensionOptions.fks.push({
             field: dimension.field(),
             dimension: dimension,
             dimensions: fkDimensions,
@@ -681,7 +688,17 @@ export default class StructuredQuery extends AtomicQuery {
       }
     }
 
-    return fieldOptions;
+    return dimensionOptions;
+  }
+
+  // FIELD OPTIONS
+
+  fieldOptions(fieldFilter = () => true) {
+    const dimensionFilter = dimension => {
+      const field = dimension.field && dimension.field();
+      return !field || (field.isDimension() && fieldFilter(field));
+    };
+    return this.dimensionOptions(dimensionFilter);
   }
 
   // DIMENSIONS
diff --git a/frontend/src/metabase-shared/color_selector.js b/frontend/src/metabase-shared/color_selector.js
new file mode 100644
index 0000000000000000000000000000000000000000..f995d006af6f5893fe48efc321cc0f1fbd2a1cae
--- /dev/null
+++ b/frontend/src/metabase-shared/color_selector.js
@@ -0,0 +1,45 @@
+// This runs in the Nashorn JavaScript engine and there are some limitations
+//
+// 1. This is not currently automatically built with the rest of the application, please run `yarn build-shared` after modifying
+// 2. Avoid including unecessary libraries as the JS engine takes a long time to parse and execute them
+// 3. Related to #2, we aren't currently including `babel-polyfill` so don't use features that require it, e.x. iterables / for-of
+
+import { makeCellBackgroundGetter } from "metabase/visualizations/lib/table_format";
+
+global.console = {
+  log: print,
+  warn: print,
+  error: print,
+};
+
+global.makeCellBackgroundGetter = function(
+  rowsJavaList,
+  colsJSON,
+  settingsJSON,
+) {
+  const rows = rowsJavaList;
+  const cols = JSON.parse(colsJSON);
+  const settings = JSON.parse(settingsJSON);
+  try {
+    const getter = makeCellBackgroundGetter(rows, cols, settings);
+    return (value, rowIndex, colName) => {
+      const color = getter(value, rowIndex, colName);
+      if (color) {
+        return roundColor(color);
+      }
+      return null;
+    };
+  } catch (e) {
+    print("ERROR", e);
+    return () => null;
+  }
+};
+
+// HACK: d3 may return rgb values with decimals but the rendering engine used for pulses doesn't support that
+function roundColor(color) {
+  return color.replace(
+    /rgba\((\d+(?:\.\d+)),\s*(\d+(?:\.\d+)),\s*(\d+(?:\.\d+)),\s*(\d+\.\d+)\)/,
+    (_, r, g, b, a) =>
+      `rgba(${Math.round(r)},${Math.round(g)},${Math.round(b)},${a})`,
+  );
+}
diff --git a/frontend/src/metabase-shared/dependencies/d3.js b/frontend/src/metabase-shared/dependencies/d3.js
new file mode 100644
index 0000000000000000000000000000000000000000..39d2b264bf9da5fcdf2d311b84b68c95465b6aba
--- /dev/null
+++ b/frontend/src/metabase-shared/dependencies/d3.js
@@ -0,0 +1,9 @@
+// minimal set of d3 functions needed for color_selector.js
+
+import { scaleLinear } from "d3-scale";
+
+export default {
+  scale: {
+    linear: scaleLinear,
+  },
+};
diff --git a/frontend/src/metabase/App.jsx b/frontend/src/metabase/App.jsx
index 768127b06f684e1ae744bc3712ba8737cfc97305..32d4d78077e622a3a0f3de6694c5fcd22ff5b434 100644
--- a/frontend/src/metabase/App.jsx
+++ b/frontend/src/metabase/App.jsx
@@ -2,18 +2,21 @@
 
 import React, { Component } from "react";
 import { connect } from "react-redux";
-
+import ScrollToTop from "metabase/hoc/ScrollToTop";
 import Navbar from "metabase/nav/containers/Navbar.jsx";
 
 import UndoListing from "metabase/containers/UndoListing";
 
-import NotFound from "metabase/components/NotFound.jsx";
-import Unauthorized from "metabase/components/Unauthorized.jsx";
-import Archived from "metabase/components/Archived.jsx";
-import GenericError from "metabase/components/GenericError.jsx";
+import {
+  Archived,
+  NotFound,
+  GenericError,
+  Unauthorized,
+} from "metabase/containers/ErrorPages";
 
 const mapStateToProps = (state, props) => ({
   errorPage: state.app.errorPage,
+  currentUser: state.currentUser,
 });
 
 const getErrorComponent = ({ status, data, context }) => {
@@ -50,18 +53,20 @@ export default class App extends Component {
   }
 
   render() {
-    const { children, location, errorPage } = this.props;
+    const { children, currentUser, location, errorPage } = this.props;
 
     if (this.state.hasError) {
       return <div>😢</div>;
     }
 
     return (
-      <div className="relative">
-        <Navbar location={location} />
-        {errorPage ? getErrorComponent(errorPage) : children}
-        <UndoListing />
-      </div>
+      <ScrollToTop>
+        <div className="relative">
+          {currentUser && <Navbar location={location} />}
+          {errorPage ? getErrorComponent(errorPage) : children}
+          <UndoListing />
+        </div>
+      </ScrollToTop>
     );
   }
 }
diff --git a/frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx b/frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx
index 289fa6ce53330a46a9a552403e99fccc2ddc9477..48e97382828407539dc492e62586472fb2420133 100644
--- a/frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx
+++ b/frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx
@@ -3,12 +3,14 @@ import cx from "classnames";
 import _ from "underscore";
 import { assocIn } from "icepick";
 import { t } from "c-3po";
-import FormMessage from "metabase/components/form/FormMessage";
 
+import FormMessage from "metabase/components/form/FormMessage";
 import SchedulePicker from "metabase/components/SchedulePicker";
-import MetabaseAnalytics from "metabase/lib/analytics";
 import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
 
+import MetabaseAnalytics from "metabase/lib/analytics";
+import colors from "metabase/lib/colors";
+
 export const SyncOption = ({ selected, name, children, select }) => (
   <div
     className={cx("py3 relative", { "cursor-pointer": !selected })}
@@ -20,7 +22,7 @@ export const SyncOption = ({ selected, name, children, select }) => (
         width: 18,
         height: 18,
         borderWidth: 2,
-        borderColor: selected ? "#509ee3" : "#ddd",
+        borderColor: selected ? colors["brand"] : colors["text-light"],
         borderStyle: "solid",
       }}
     >
@@ -30,7 +32,7 @@ export const SyncOption = ({ selected, name, children, select }) => (
           style={{
             width: 8,
             height: 8,
-            backgroundColor: selected ? "#509ee3" : "#ddd",
+            backgroundColor: selected ? colors["brand"] : colors["text-light"],
           }}
         />
       )}
diff --git a/frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx b/frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx
index 2e763d11a70d9964e9b0dcf22e967082a2159f0e..ef008e5455fac4fabf8599d3caae44b5660c522d 100644
--- a/frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx
+++ b/frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx
@@ -1,6 +1,6 @@
 import React, { Component } from "react";
 import PropTypes from "prop-types";
-import { t } from "c-3po";
+import { t, jt } from "c-3po";
 
 import Button from "metabase/components/Button";
 import ModalContent from "metabase/components/ModalContent.jsx";
@@ -32,6 +32,7 @@ export default class DeleteDatabaseModal extends Component {
 
   render() {
     const { database } = this.props;
+    const { confirmValue } = this.state;
 
     let formError;
     if (this.state.error) {
@@ -46,8 +47,12 @@ export default class DeleteDatabaseModal extends Component {
       formError = <span className="text-error px2">{errorMessage}</span>;
     }
 
-    let confirmed = this.state.confirmValue.toUpperCase() === "DELETE";
+    // allow English or localized
+    let confirmed =
+      confirmValue.toUpperCase() === "DELETE" ||
+      confirmValue.toUpperCase() === t`DELETE`;
 
+    const headsUp = <strong>{t`Just a heads up:`}</strong>;
     return (
       <ModalContent
         title={t`Delete this database?`}
@@ -55,7 +60,7 @@ export default class DeleteDatabaseModal extends Component {
       >
         <div className="mb4">
           {database.is_sample && (
-            <p className="text-paragraph">{t`<strong>Just a heads up:</strong> without the Sample Dataset, the Query Builder tutorial won't work. You can always restore the Sample Dataset, but any questions you've saved using this data will be lost.`}</p>
+            <p className="text-paragraph">{jt`${headsUp} without the Sample Dataset, the Query Builder tutorial won't work. You can always restore the Sample Dataset, but any questions you've saved using this data will be lost.`}</p>
           )}
           <p className="text-paragraph">
             {t`All saved questions, metrics, and segments that rely on this database will be lost.`}{" "}
diff --git a/frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx b/frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx
index f8f1cf5b46f43acfbdc0629d2ef0e739c1f16f8e..61557e48bf82bb07ef5c5cd8b45c5dcc4c3ff0d2 100644
--- a/frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx
+++ b/frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx
@@ -1,16 +1,18 @@
+/* @flow weak */
+
 import React, { Component } from "react";
 import PropTypes from "prop-types";
 import { connect } from "react-redux";
 import title from "metabase/hoc/Title";
-import cx from "classnames";
+import { t } from "c-3po";
 
 import MetabaseSettings from "metabase/lib/settings";
 import DeleteDatabaseModal from "../components/DeleteDatabaseModal.jsx";
 import DatabaseEditForms from "../components/DatabaseEditForms.jsx";
 import DatabaseSchedulingForm from "../components/DatabaseSchedulingForm";
-import { t } from "c-3po";
 import ActionButton from "metabase/components/ActionButton.jsx";
 import Breadcrumbs from "metabase/components/Breadcrumbs.jsx";
+import Radio from "metabase/components/Radio.jsx";
 import ModalWithTrigger from "metabase/components/ModalWithTrigger.jsx";
 
 import {
@@ -39,33 +41,6 @@ const mapStateToProps = (state, props) => ({
   formState: getFormState(state),
 });
 
-export const Tab = ({ name, setTab, currentTab }) => {
-  const isCurrentTab = currentTab === name.toLowerCase();
-
-  return (
-    <div
-      className={cx("cursor-pointer py2", { "text-brand": isCurrentTab })}
-      // TODO Use css classes instead?
-      style={isCurrentTab ? { borderBottom: "3px solid #509EE3" } : {}}
-      onClick={() => setTab(name)}
-    >
-      <h3>{name}</h3>
-    </div>
-  );
-};
-
-export const Tabs = ({ tabs, currentTab, setTab }) => (
-  <div className="border-bottom">
-    <ol className="Form-offset flex align center">
-      {tabs.map((tab, index) => (
-        <li key={index} className="mr3">
-          <Tab name={tab} setTab={setTab} currentTab={currentTab} />
-        </li>
-      ))}
-    </ol>
-  </div>
-);
-
 const mapDispatchToProps = {
   reset,
   initializeDatabase,
@@ -78,14 +53,26 @@ const mapDispatchToProps = {
   selectEngine,
 };
 
+type TabName = "connection" | "scheduling";
+type TabOption = { name: string, value: TabName };
+
+const TABS: TabOption[] = [
+  { name: t`Connection`, value: "connection" },
+  { name: t`Scheduling`, value: "scheduling" },
+];
+
 @connect(mapStateToProps, mapDispatchToProps)
 @title(({ database }) => database && database.name)
 export default class DatabaseEditApp extends Component {
+  state: {
+    currentTab: TabName,
+  };
+
   constructor(props, context) {
     super(props, context);
 
     this.state = {
-      currentTab: "connection",
+      currentTab: TABS[0].value,
     };
   }
 
@@ -146,13 +133,14 @@ export default class DatabaseEditApp extends Component {
           <div className="Grid-cell">
             <div className="Form-new bordered rounded shadowed pt0">
               {showTabs && (
-                <Tabs
-                  tabs={[t`Connection`, t`Scheduling`]}
-                  currentTab={currentTab}
-                  setTab={tab =>
-                    this.setState({ currentTab: tab.toLowerCase() })
-                  }
-                />
+                <div className="Form-offset border-bottom">
+                  <Radio
+                    value={currentTab}
+                    options={TABS}
+                    onChange={currentTab => this.setState({ currentTab })}
+                    underlined
+                  />
+                </div>
               )}
               <LoadingAndErrorWrapper loading={!database} error={null}>
                 {() => (
diff --git a/frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx b/frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx
index d3d49e0748f15ae8238ef256ea2082e40ae168ac..9f52b35ddeb85defbfdf166c25781a596d8d1e38 100644
--- a/frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx
+++ b/frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx
@@ -1,40 +1,46 @@
+/* @flow weak */
+
 import React, { Component } from "react";
 import PropTypes from "prop-types";
 import { connect } from "react-redux";
 import { Link } from "react-router";
+import { t } from "c-3po";
 
 import cx from "classnames";
 import MetabaseSettings from "metabase/lib/settings";
+
 import ModalWithTrigger from "metabase/components/ModalWithTrigger.jsx";
 import LoadingSpinner from "metabase/components/LoadingSpinner.jsx";
-import { t } from "c-3po";
+import FormMessage from "metabase/components/form/FormMessage";
+
 import CreatedDatabaseModal from "../components/CreatedDatabaseModal.jsx";
 import DeleteDatabaseModal from "../components/DeleteDatabaseModal.jsx";
 
-import {
-  getDatabasesSorted,
-  hasSampleDataset,
-  getDeletes,
-  getDeletionError,
-} from "../selectors";
-import * as databaseActions from "../database";
-import FormMessage from "metabase/components/form/FormMessage";
+import Databases from "metabase/entities/databases";
+import { entityListLoader } from "metabase/entities/containers/EntityListLoader";
 
-const mapStateToProps = (state, props) => {
-  return {
-    created: props.location.query.created,
-    databases: getDatabasesSorted(state),
-    hasSampleDataset: hasSampleDataset(state),
-    engines: MetabaseSettings.get("engines"),
-    deletes: getDeletes(state),
-    deletionError: getDeletionError(state),
-  };
-};
+import { getDeletes, getDeletionError } from "../selectors";
+import { deleteDatabase, addSampleDataset } from "../database";
+
+const mapStateToProps = (state, props) => ({
+  hasSampleDataset: Databases.selectors.getHasSampleDataset(state),
+
+  created: props.location.query.created,
+  engines: MetabaseSettings.get("engines"),
+
+  deletes: getDeletes(state),
+  deletionError: getDeletionError(state),
+});
 
 const mapDispatchToProps = {
-  ...databaseActions,
+  fetchDatabases: Databases.actions.fetchList,
+  // NOTE: still uses deleteDatabase from metabaseadmin/databases/databases.js
+  // rather than metabase/entities/databases since it updates deletes/deletionError
+  deleteDatabase: deleteDatabase,
+  addSampleDataset: addSampleDataset,
 };
 
+@entityListLoader({ entityType: "databases" })
 @connect(mapStateToProps, mapDispatchToProps)
 export default class DatabaseList extends Component {
   static propTypes = {
@@ -45,10 +51,6 @@ export default class DatabaseList extends Component {
     deletionError: PropTypes.object,
   };
 
-  componentWillMount() {
-    this.props.fetchDatabases();
-  }
-
   componentWillReceiveProps(newProps) {
     if (!this.props.created && newProps.created) {
       this.refs.createdDatabaseModal.open();
@@ -156,7 +158,7 @@ export default class DatabaseList extends Component {
                 })}
               >
                 <a
-                  className="text-grey-2 text-brand-hover no-decoration"
+                  className="text-light text-brand-hover no-decoration"
                   onClick={() => this.props.addSampleDataset()}
                 >{t`Bring the sample dataset back`}</a>
               </span>
diff --git a/frontend/src/metabase/admin/databases/database.js b/frontend/src/metabase/admin/databases/database.js
index e8eefc1f413c9836eef5d83acdde0114ec5b44c1..ccaab74beb621dbd44b0254bf8aa9571346ef636 100644
--- a/frontend/src/metabase/admin/databases/database.js
+++ b/frontend/src/metabase/admin/databases/database.js
@@ -1,4 +1,4 @@
-import _ from "underscore";
+/* @flow weak */
 
 import { createAction } from "redux-actions";
 import {
@@ -12,6 +12,7 @@ import MetabaseAnalytics from "metabase/lib/analytics";
 import MetabaseSettings from "metabase/lib/settings";
 
 import { MetabaseApi } from "metabase/services";
+import Databases from "metabase/entities/databases";
 
 // Default schedules for db sync and deep analysis
 export const DEFAULT_SCHEDULES = {
@@ -69,22 +70,13 @@ export const CLEAR_FORM_STATE = "metabase/admin/databases/CLEAR_FORM_STATE";
 export const MIGRATE_TO_NEW_SCHEDULING_SETTINGS =
   "metabase/admin/databases/MIGRATE_TO_NEW_SCHEDULING_SETTINGS";
 
+// NOTE: some but not all of these actions have been migrated to use metabase/entities/databases
+
 export const reset = createAction(RESET);
 
 // selectEngine (uiControl)
 export const selectEngine = createAction(SELECT_ENGINE);
 
-// fetchDatabases
-export const fetchDatabases = createThunkAction(FETCH_DATABASES, function() {
-  return async function(dispatch, getState) {
-    try {
-      return await MetabaseApi.db_list();
-    } catch (error) {
-      console.error("error fetching databases", error);
-    }
-  };
-});
-
 // Migrates old "Enable in-depth database analysis" option to new "Let me choose when Metabase syncs and scans" option
 // Migration is run as a separate action because that makes it easy to track in tests
 const migrateDatabaseToNewSchedulingSettings = database => {
@@ -112,7 +104,10 @@ export const initializeDatabase = function(databaseId) {
   return async function(dispatch, getState) {
     if (databaseId) {
       try {
-        const database = await MetabaseApi.db_get({ dbId: databaseId });
+        const action = await dispatch(
+          Databases.actions.fetch({ id: databaseId }, { reload: true }),
+        );
+        const database = Databases.HACK_getObjectFromAction(action);
         dispatch.action(INITIALIZE_DATABASE, database);
 
         // If the new scheduling toggle isn't set, run the migration
@@ -196,13 +191,10 @@ export const createDatabase = function(database) {
   return async function(dispatch, getState) {
     try {
       dispatch.action(CREATE_DATABASE_STARTED, {});
-      const createdDatabase = await MetabaseApi.db_create(database);
+      const action = await dispatch(Databases.actions.create(database));
+      const createdDatabase = Databases.HACK_getObjectFromAction(action);
       MetabaseAnalytics.trackEvent("Databases", "Create", database.engine);
 
-      // update the db metadata already here because otherwise there will be a gap between "Adding..." status
-      // and seeing the db that was just added
-      await dispatch(fetchDatabases());
-
       dispatch.action(CREATE_DATABASE);
       dispatch(push("/admin/databases?created=" + createdDatabase.id));
     } catch (error) {
@@ -221,7 +213,8 @@ export const updateDatabase = function(database) {
   return async function(dispatch, getState) {
     try {
       dispatch.action(UPDATE_DATABASE_STARTED, { database });
-      const savedDatabase = await MetabaseApi.db_update(database);
+      const action = await dispatch(Databases.actions.update(database));
+      const savedDatabase = Databases.HACK_getObjectFromAction(action);
       MetabaseAnalytics.trackEvent("Databases", "Update", database.engine);
 
       dispatch.action(UPDATE_DATABASE, { database: savedDatabase });
@@ -270,7 +263,7 @@ export const deleteDatabase = function(databaseId, isDetailView = true) {
     try {
       dispatch.action(DELETE_DATABASE_STARTED, { databaseId });
       dispatch(push("/admin/databases/"));
-      await MetabaseApi.db_delete({ dbId: databaseId });
+      await dispatch(Databases.actions.delete({ id: databaseId }));
       MetabaseAnalytics.trackEvent(
         "Databases",
         "Delete",
@@ -334,18 +327,6 @@ export const discardSavedFieldValues = createThunkAction(
 
 // reducers
 
-const databases = handleActions(
-  {
-    [FETCH_DATABASES]: { next: (state, { payload }) => payload },
-    [ADD_SAMPLE_DATASET]: {
-      next: (state, { payload }) => (payload ? [...state, payload] : state),
-    },
-    [DELETE_DATABASE]: (state, { payload: { databaseId } }) =>
-      databaseId ? _.reject(state, d => d.id === databaseId) : state,
-  },
-  null,
-);
-
 const editingDatabase = handleActions(
   {
     [RESET]: () => null,
@@ -420,7 +401,6 @@ const formState = handleActions(
 );
 
 export default combineReducers({
-  databases,
   editingDatabase,
   deletionError,
   databaseCreationStep,
diff --git a/frontend/src/metabase/admin/databases/selectors.js b/frontend/src/metabase/admin/databases/selectors.js
index 5582bccb4326abbe33dfdee5e0d17db488d36c85..acfc858f52fd6b310b316481576f76da0aa5f6be 100644
--- a/frontend/src/metabase/admin/databases/selectors.js
+++ b/frontend/src/metabase/admin/databases/selectors.js
@@ -1,19 +1,5 @@
 /* @flow weak */
 
-import _ from "underscore";
-import { createSelector } from "reselect";
-
-// Database List
-export const databases = state => state.admin.databases.databases;
-
-export const getDatabasesSorted = createSelector([databases], databases =>
-  _.sortBy(databases, "name"),
-);
-
-export const hasSampleDataset = createSelector([databases], databases =>
-  _.some(databases, d => d.is_sample),
-);
-
 // Database Edit
 export const getEditingDatabase = state =>
   state.admin.databases.editingDatabase;
@@ -21,5 +7,6 @@ export const getFormState = state => state.admin.databases.formState;
 export const getDatabaseCreationStep = state =>
   state.admin.databases.databaseCreationStep;
 
+// Database List
 export const getDeletes = state => state.admin.databases.deletes;
 export const getDeletionError = state => state.admin.databases.deletionError;
diff --git a/frontend/src/metabase/admin/datamodel/components/ObjectActionSelect.jsx b/frontend/src/metabase/admin/datamodel/components/ObjectActionSelect.jsx
index e8f857284c07f64e002cfee662ceca7fb503288e..d93fd995bb9a10850cbd195844cba1b5fba80857 100644
--- a/frontend/src/metabase/admin/datamodel/components/ObjectActionSelect.jsx
+++ b/frontend/src/metabase/admin/datamodel/components/ObjectActionSelect.jsx
@@ -29,7 +29,7 @@ export default class ObjectActionsSelect extends Component {
         <PopoverWithTrigger
           ref="popover"
           triggerElement={
-            <span className="text-grey-1 text-grey-4-hover">
+            <span className="text-light text-medium-hover">
               <Icon name={"ellipsis"} />
             </span>
           }
diff --git a/frontend/src/metabase/admin/datamodel/components/PartialQueryBuilder.jsx b/frontend/src/metabase/admin/datamodel/components/PartialQueryBuilder.jsx
index 7a839543166c94cb994f5dac19ab709071d9037c..4bab3127a2ec4e79c7156d0d8c218f285a6368ac 100644
--- a/frontend/src/metabase/admin/datamodel/components/PartialQueryBuilder.jsx
+++ b/frontend/src/metabase/admin/datamodel/components/PartialQueryBuilder.jsx
@@ -25,7 +25,7 @@ export default class PartialQueryBuilder extends Component {
       database: tableMetadata.db_id,
       query: {
         ...value,
-        source_table: tableMetadata.id,
+        "source-table": tableMetadata.id,
       },
     });
   }
@@ -49,7 +49,7 @@ export default class PartialQueryBuilder extends Component {
       database: tableMetadata.db_id,
       query: {
         ...value,
-        source_table: tableMetadata.id,
+        "source-table": tableMetadata.id,
       },
     };
 
diff --git a/frontend/src/metabase/admin/datamodel/components/database/ColumnsList.jsx b/frontend/src/metabase/admin/datamodel/components/database/ColumnsList.jsx
index bb1f42b6263fdf5c48725ab6b39752ad13ab6615..434f90b16d4d91695e6eda599b6a805c547f7d57 100644
--- a/frontend/src/metabase/admin/datamodel/components/database/ColumnsList.jsx
+++ b/frontend/src/metabase/admin/datamodel/components/database/ColumnsList.jsx
@@ -15,7 +15,7 @@ export default class ColumnsList extends Component {
     return (
       <div id="ColumnsList" className="my3">
         <h2 className="px1 text-orange">{t`Columns`}</h2>
-        <div className="text-uppercase text-grey-3 py1">
+        <div className="text-uppercase text-medium py1">
           <div
             style={{ minWidth: 420 }}
             className="float-left px1"
diff --git a/frontend/src/metabase/admin/datamodel/components/database/MetadataHeader.jsx b/frontend/src/metabase/admin/datamodel/components/database/MetadataHeader.jsx
index 576498b45fc257cfd4be1ec57173fc1eadf68204..860afd6b47549bc28f02c1e6619d565f32de2c2f 100644
--- a/frontend/src/metabase/admin/datamodel/components/database/MetadataHeader.jsx
+++ b/frontend/src/metabase/admin/datamodel/components/database/MetadataHeader.jsx
@@ -84,7 +84,7 @@ export default class MetadataHeader extends Component {
     return (
       <div className="MetadataEditor-header flex align-center flex-no-shrink">
         <div className="MetadataEditor-headerSection py2 h2">
-          <span className="text-grey-4">{t`Current database:`}</span>{" "}
+          <span className="text-medium">{t`Current database:`}</span>{" "}
           {this.renderDbSelector()}
         </div>
         <div className="MetadataEditor-headerSection flex flex-align-right align-center flex-no-shrink">
diff --git a/frontend/src/metabase/admin/datamodel/components/database/MetadataSchema.jsx b/frontend/src/metabase/admin/datamodel/components/database/MetadataSchema.jsx
index 0ef17b9b7e743bb8e4d4dabe49773e7968d60c44..313e5b514d6e796f91f4dfb38bfb57554a526622 100644
--- a/frontend/src/metabase/admin/datamodel/components/database/MetadataSchema.jsx
+++ b/frontend/src/metabase/admin/datamodel/components/database/MetadataSchema.jsx
@@ -39,7 +39,7 @@ export default class MetadataSchema extends Component {
           </div>
         </div>
         <table className="mt2 full">
-          <thead className="text-uppercase text-grey-3 py1">
+          <thead className="text-uppercase text-medium py1">
             <tr>
               <th className={tdClassName}>{t`Column`}</th>
               <th className={tdClassName}>{t`Data Type`}</th>
diff --git a/frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx b/frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx
index def501db12b99aca9aedda35093d6e8489a1260e..e4b1d383524ce0cbe6d1ece92ffcb7c390f9e659 100644
--- a/frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx
+++ b/frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx
@@ -79,7 +79,7 @@ export default class MetadataTable extends Component {
     if (this.props.tableMetadata.visibility_type) {
       subTypes = (
         <span id="VisibilitySubTypes" className="border-left mx2">
-          <span className="mx2 text-uppercase text-grey-3">{t`Why Hide?`}</span>
+          <span className="mx2 text-uppercase text-medium">{t`Why Hide?`}</span>
           {this.renderVisibilityType(t`Technical Data`, "technical")}
           {this.renderVisibilityType(t`Irrelevant/Cruft`, "cruft")}
         </span>
@@ -101,7 +101,7 @@ export default class MetadataTable extends Component {
     }
 
     return (
-      <div className="MetadataTable px3">
+      <div className="MetadataTable px3 full">
         <div className="MetadataTable-title flex flex-column bordered rounded">
           <InputBlurChange
             className="AdminInput TableEditor-table-name text-bold border-bottom rounded-top"
@@ -117,7 +117,7 @@ export default class MetadataTable extends Component {
             placeholder={t`No table description yet`}
           />
         </div>
-        <div className="MetadataTable-header flex align-center py2 text-grey-3">
+        <div className="MetadataTable-header flex align-center py2 text-medium">
           <span className="mx1 text-uppercase">{t`Visibility`}</span>
           {this.renderVisibilityWidget()}
           <span className="flex-align-right flex align-center">
diff --git a/frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx b/frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx
index 7b58dfbfa1724dfb9aeef78c0f3f99dcf7bd1fa9..bd48ca1ad4016438e8c4e4c3fb8d16d31b7ee3b1 100644
--- a/frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx
+++ b/frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx
@@ -50,7 +50,7 @@ export default class MetricsList extends Component {
           </tbody>
         </table>
         {tableMetadata.metrics.length === 0 && (
-          <div className="flex layout-centered m4 text-grey-3">
+          <div className="flex layout-centered m4 text-medium">
             {t`Create metrics to add them to the View dropdown in the query builder`}
           </div>
         )}
diff --git a/frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx b/frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx
index 3801e7abed03e3c6111d9d45487c03a26ffa6ccc..316e0353c29c924de221b71f65daee6c0806638c 100644
--- a/frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx
+++ b/frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx
@@ -50,7 +50,7 @@ export default class SegmentsList extends Component {
           </tbody>
         </table>
         {tableMetadata.segments.length === 0 && (
-          <div className="flex layout-centered m4 text-grey-3">
+          <div className="flex layout-centered m4 text-medium">
             {t`Create segments to add them to the Filter dropdown in the query builder`}
           </div>
         )}
diff --git a/frontend/src/metabase/admin/datamodel/components/revisions/Revision.jsx b/frontend/src/metabase/admin/datamodel/components/revisions/Revision.jsx
index 688c901b64c01bc90bdbaaa018559024af7f334f..c2f0b0638b60156f091ce2d0cd57caea73ede5cc 100644
--- a/frontend/src/metabase/admin/datamodel/components/revisions/Revision.jsx
+++ b/frontend/src/metabase/admin/datamodel/components/revisions/Revision.jsx
@@ -72,7 +72,7 @@ export default class Revision extends Component {
           />
         </div>
         <div className="flex-full mt1 mb4">
-          <div className="flex mb1 text-grey-4">
+          <div className="flex mb1 text-medium">
             <span className="">
               <strong>{this.getName()}</strong> {this.getAction()}
             </span>
diff --git a/frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx b/frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx
index 789a99f6c20f91caa8b34fd43914045b3d53e87d..bdc10d3171b94e656ef3f0eebd6e64d154c53a91 100644
--- a/frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx
+++ b/frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx
@@ -39,6 +39,7 @@ import { DatetimeFieldDimension } from "metabase-lib/lib/Dimension";
 import { rescanFieldValues, discardFieldValues } from "../field";
 
 import { has_field_values_options } from "metabase/lib/core";
+import colors from "metabase/lib/colors";
 
 const SelectClasses =
   "h3 bordered border-dark shadowed p2 inline-block flex align-center rounded text-bold";
@@ -276,14 +277,14 @@ export const BackButton = ({ databaseId, tableId }) => (
   <Link
     to={`/admin/datamodel/database/${databaseId}/table/${tableId}`}
     className="circle text-white p2 mt3 ml3 flex align-center justify-center  absolute top left"
-    style={{ backgroundColor: "#8091AB" }}
+    style={{ backgroundColor: colors["bg-dark"] }}
   >
     <Icon name="backArrow" />
   </Link>
 );
 
 const SelectSeparator = () => (
-  <Icon name="chevronright" size={12} className="mx2 text-grey-3" />
+  <Icon name="chevronright" size={12} className="mx2 text-medium" />
 );
 
 export class FieldHeader extends Component {
@@ -468,7 +469,7 @@ export const SectionHeader = ({ title, description }) => (
   <div className="border-bottom py2 mb2">
     <h2 className="text-italic">{title}</h2>
     {description && (
-      <p className="mb0 text-grey-4 mt1 text-paragraph text-measure">
+      <p className="mb0 text-medium mt1 text-paragraph text-measure">
         {description}
       </p>
     )}
@@ -726,7 +727,7 @@ export class FieldRemapping extends Component {
                 {fkMappingField ? (
                   fkMappingField.display_name
                 ) : (
-                  <span className="text-grey-1">{t`Choose a field`}</span>
+                  <span className="text-light">{t`Choose a field`}</span>
                 )}
               </SelectButton>
             }
diff --git a/frontend/src/metabase/admin/datamodel/datamodel.js b/frontend/src/metabase/admin/datamodel/datamodel.js
index 7ec91a8f626a9d985b2585ab9da7763777a7a743..0cd6868c7f7eb18bdfd600e64afafef1ece6ac49 100644
--- a/frontend/src/metabase/admin/datamodel/datamodel.js
+++ b/frontend/src/metabase/admin/datamodel/datamodel.js
@@ -271,7 +271,9 @@ export const fetchRevisions = createThunkAction(
       dispatch(action),
       RevisionsApi.get({ entity, id }),
     ]);
-    await dispatch(loadTableMetadata(object.payload.definition.source_table));
+    await dispatch(
+      loadTableMetadata(object.payload.definition["source-table"]),
+    );
     return { object: object.payload, revisions };
   },
 );
diff --git a/frontend/src/metabase/admin/people/components/GroupDetail.jsx b/frontend/src/metabase/admin/people/components/GroupDetail.jsx
index 2d8d5d647edc146638fa5cc536ac4569e97e5949..5fe95cde4cf15ba0cc4d4aac82e966c78e46cee5 100644
--- a/frontend/src/metabase/admin/people/components/GroupDetail.jsx
+++ b/frontend/src/metabase/admin/people/components/GroupDetail.jsx
@@ -7,6 +7,7 @@ import {
   isAdminGroup,
   isDefaultGroup,
   canEditMembership,
+  getGroupNameLocalized,
 } from "metabase/lib/groups";
 
 import { PermissionsApi } from "metabase/services";
@@ -28,9 +29,9 @@ const GroupDescription = ({ group }) =>
   isDefaultGroup(group) ? (
     <div className="px2 text-measure">
       <p>
-        {t`All users belong to the ${
-          group.name
-        } group and can't be removed from it. Setting permissions for this group is a great way to
+        {t`All users belong to the ${getGroupNameLocalized(
+          group,
+        )} group and can't be removed from it. Setting permissions for this group is a great way to
                 make sure you know what new Metabase users will be able to see.`}
       </p>
     </div>
@@ -115,10 +116,10 @@ const AddUserRow = ({
         onCancel={onCancel}
       >
         {selectedUsers.map(user => (
-          <div className="bg-slate-light p1 px2 mr1 rounded flex align-center">
+          <div className="bg-medium p1 px2 mr1 rounded flex align-center">
             {user.common_name}
             <Icon
-              className="pl1 cursor-pointer text-slate text-grey-4-hover"
+              className="pl1 cursor-pointer text-slate text-medium-hover"
               name="close"
               onClick={() => onRemoveUserFromSelection(user)}
             />
@@ -147,7 +148,7 @@ const UserRow = ({ user, showRemoveButton, onRemoveUserClicked }) => (
         className="text-right cursor-pointer"
         onClick={onRemoveUserClicked.bind(null, user)}
       >
-        <Icon name="close" className="text-grey-1" size={16} />
+        <Icon name="close" className="text-light" size={16} />
       </td>
     ) : null}
   </tr>
@@ -324,8 +325,8 @@ export default class GroupDetail extends Component {
 
     return (
       <AdminPaneLayout
-        title={group.name}
-        buttonText="Add members"
+        title={getGroupNameLocalized(group)}
+        buttonText={t`Add members`}
         buttonAction={
           canEditMembership(group) ? this.onAddUsersClicked.bind(this) : null
         }
diff --git a/frontend/src/metabase/admin/people/components/GroupSelect.jsx b/frontend/src/metabase/admin/people/components/GroupSelect.jsx
index 7c2a9aa6e54ee5aa523c16de5b82a931befc3359..57dd78835c8f90a2e48110c322fa27ca6b1590c0 100644
--- a/frontend/src/metabase/admin/people/components/GroupSelect.jsx
+++ b/frontend/src/metabase/admin/people/components/GroupSelect.jsx
@@ -7,6 +7,7 @@ import {
   isAdminGroup,
   canEditMembership,
   getGroupColor,
+  getGroupNameLocalized,
 } from "metabase/lib/groups";
 import cx from "classnames";
 import _ from "underscore";
@@ -24,7 +25,7 @@ export const GroupOption = ({ group, selectedGroups = {}, onGroupChange }) => {
       <span className={cx("pr1", getGroupColor(group), { disabled })}>
         <CheckBox checked={selected} size={18} />
       </span>
-      {group.name}
+      {getGroupNameLocalized(group)}
     </div>
   );
 };
diff --git a/frontend/src/metabase/admin/people/components/GroupsListing.jsx b/frontend/src/metabase/admin/people/components/GroupsListing.jsx
index d1858b0daf78710b6373788b63931003ddfd6537..dd55ff298f5b53d09ff99217d82f3994027f734e 100644
--- a/frontend/src/metabase/admin/people/components/GroupsListing.jsx
+++ b/frontend/src/metabase/admin/people/components/GroupsListing.jsx
@@ -5,7 +5,11 @@ import _ from "underscore";
 import cx from "classnames";
 
 import MetabaseAnalytics from "metabase/lib/analytics";
-import { isDefaultGroup, isAdminGroup } from "metabase/lib/groups";
+import {
+  isDefaultGroup,
+  isAdminGroup,
+  getGroupNameLocalized,
+} from "metabase/lib/groups";
 import { KEYCODE_ENTER } from "metabase/lib/keyboard";
 
 import { PermissionsApi } from "metabase/services";
@@ -79,7 +83,7 @@ function ActionsPopover({ group, onEditGroupClicked, onDeleteGroupClicked }) {
   return (
     <PopoverWithTrigger
       className="block"
-      triggerElement={<Icon className="text-grey-1" name="ellipsis" />}
+      triggerElement={<Icon className="text-light" name="ellipsis" />}
     >
       <ul className="UserActionsSelect">
         <li
@@ -175,9 +179,12 @@ function GroupRow({
           className="link no-decoration"
         >
           <span className="text-white inline-block">
-            <UserAvatar background={color} user={{ first_name: group.name }} />
+            <UserAvatar
+              background={color}
+              user={{ first_name: getGroupNameLocalized(group) }}
+            />
           </span>
-          <span className="ml2 text-bold">{group.name}</span>
+          <span className="ml2 text-bold">{getGroupNameLocalized(group)}</span>
         </Link>
       </td>
       <td>{group.members || 0}</td>
diff --git a/frontend/src/metabase/admin/people/components/UserActionsSelect.jsx b/frontend/src/metabase/admin/people/components/UserActionsSelect.jsx
index d1bdb6beeb462821638c8a68268360f61b204377..65ab545de807f89bd315987324e2b374383fbdbc 100644
--- a/frontend/src/metabase/admin/people/components/UserActionsSelect.jsx
+++ b/frontend/src/metabase/admin/people/components/UserActionsSelect.jsx
@@ -67,7 +67,7 @@ export default class UserActionsSelect extends Component {
         ref="popover"
         className="block"
         triggerElement={
-          <span className="text-grey-1">
+          <span className="text-light">
             <Icon name={"ellipsis"} />
           </span>
         }
diff --git a/frontend/src/metabase/admin/people/components/UserGroupSelect.jsx b/frontend/src/metabase/admin/people/components/UserGroupSelect.jsx
index 880a55b0dd444257c744b4cbfb3ea75f2a6c2e06..602a9485d40b4c61129f9c3091239bbc6607c616 100644
--- a/frontend/src/metabase/admin/people/components/UserGroupSelect.jsx
+++ b/frontend/src/metabase/admin/people/components/UserGroupSelect.jsx
@@ -69,10 +69,10 @@ export default class UserGroupSelect extends Component {
         ref="popover"
         triggerElement={
           <div className="flex align-center">
-            <span className="mr1 text-grey-4">
+            <span className="mr1 text-medium">
               <GroupSummary groups={groups} selectedGroups={user.memberships} />
             </span>
-            <Icon className="text-grey-2" name="chevrondown" size={10} />
+            <Icon className="text-light" name="chevrondown" size={10} />
           </div>
         }
         triggerClasses="AdminSelectBorderless py1"
diff --git a/frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx b/frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx
index 42fb81fc4ec2428a24eca1fb0d3ca277ca28140a..837e2aac14889fe03b730a7888bab952743bed45 100644
--- a/frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx
+++ b/frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx
@@ -160,10 +160,7 @@ export default class PeopleListingApp extends Component {
       });
     } else {
       // generate a password
-      const password = MetabaseUtils.generatePassword(
-        14,
-        MetabaseSettings.get("password_complexity"),
-      );
+      const password = MetabaseUtils.generatePassword();
 
       // trigger the reset
       this.props.resetPasswordManually(user, password);
@@ -438,15 +435,19 @@ export default class PeopleListingApp extends Component {
     let title = t`People`;
     if (deactivated.length > 0) {
       title = (
-        <Radio
-          className="h6"
-          value={!!showDeactivated}
-          options={[
-            { name: t`Active`, value: false },
-            { name: t`Deactivated`, value: true },
-          ]}
-          onChange={showDeactivated => this.setState({ showDeactivated })}
-        />
+        <div className="mb2">
+          <Radio
+            className="h6"
+            value={!!showDeactivated}
+            options={[
+              { name: t`Active`, value: false },
+              { name: t`Deactivated`, value: true },
+            ]}
+            underlined
+            py={1}
+            onChange={showDeactivated => this.setState({ showDeactivated })}
+          />
+        </div>
       );
     }
 
@@ -517,7 +518,7 @@ export default class PeopleListingApp extends Component {
                               <Tooltip tooltip={t`Reactivate this account`}>
                                 <Icon
                                   name="refresh"
-                                  className="text-grey-1 text-brand-hover cursor-pointer"
+                                  className="text-light text-brand-hover cursor-pointer"
                                   size={20}
                                   onClick={() =>
                                     this.props.showModal({
diff --git a/frontend/src/metabase/admin/people/people.js b/frontend/src/metabase/admin/people/people.js
index 8b8c4d1f949140e0b378cc36174230c6a06eb0b0..78bf2e16e7f803e9593c4716bb9b0209a0c7b1d9 100644
--- a/frontend/src/metabase/admin/people/people.js
+++ b/frontend/src/metabase/admin/people/people.js
@@ -9,6 +9,7 @@ import {
 import { normalize, schema } from "normalizr";
 
 import MetabaseAnalytics from "metabase/lib/analytics";
+import { isMetaBotGroup } from "metabase/lib/groups";
 
 import { SessionApi, UserApi, PermissionsApi } from "metabase/services";
 
@@ -215,7 +216,7 @@ const groups = handleActions(
   {
     [LOAD_GROUPS]: {
       next: (state, { payload }) =>
-        payload && payload.filter(group => group.name !== "MetaBot"),
+        payload && payload.filter(group => !isMetaBotGroup(group)),
     },
   },
   null,
diff --git a/frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx b/frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx
index a1a0e669ec3c9a9798e43ffc7dfdb08004872605..7fc92fcf1dd8d31fe28c3a9644404cd28b20e9a9 100644
--- a/frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx
+++ b/frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx
@@ -4,6 +4,7 @@ import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper.j
 import Confirm from "metabase/components/Confirm.jsx";
 import PermissionsGrid from "../components/PermissionsGrid.jsx";
 import PermissionsConfirm from "../components/PermissionsConfirm.jsx";
+import PermissionsTabs from "../components/PermissionsTabs.jsx";
 import EditBar from "metabase/components/EditBar.jsx";
 import Breadcrumbs from "metabase/components/Breadcrumbs.jsx";
 import Button from "metabase/components/Button";
@@ -13,12 +14,13 @@ import cx from "classnames";
 import _ from "underscore";
 
 const PermissionsEditor = ({
-  title = t`Permissions`,
+  tab,
   admin,
   grid,
   onUpdatePermission,
   onSave,
   onCancel,
+  onChangeTab,
   confirmCancel,
   isDirty,
   saveError,
@@ -64,13 +66,16 @@ const PermissionsEditor = ({
               buttons={[cancelButton, saveButton]}
             />
           )}
-          <div className="wrapper pt2">
-            {grid && grid.crumbs ? (
+          {tab && (
+            <div className="border-bottom mb3">
+              <PermissionsTabs tab={tab} onChangeTab={onChangeTab} />
+            </div>
+          )}
+          {grid && grid.crumbs && grid.crumbs.length > 0 ? (
+            <div className="px2 pb1 ml3">
               <Breadcrumbs className="py1" crumbs={grid.crumbs} />
-            ) : (
-              <h2>{title}</h2>
-            )}
-          </div>
+            </div>
+          ) : null}
           <PermissionsGrid
             className="flex-full"
             grid={grid}
diff --git a/frontend/src/metabase/admin/permissions/components/PermissionsGrid.jsx b/frontend/src/metabase/admin/permissions/components/PermissionsGrid.jsx
index c58400bc904f537ee0a8c46b4eb7b55516b725b4..8eac7187d1d80ef5cfb7be7455eaf65f875fc3c7 100644
--- a/frontend/src/metabase/admin/permissions/components/PermissionsGrid.jsx
+++ b/frontend/src/metabase/admin/permissions/components/PermissionsGrid.jsx
@@ -1,6 +1,7 @@
 /* eslint-disable react/display-name */
 
 import React, { Component } from "react";
+import { connect } from "react-redux";
 
 import { Link } from "react-router";
 
@@ -13,13 +14,14 @@ import Modal from "metabase/components/Modal.jsx";
 import FixedHeaderGrid from "./FixedHeaderGrid.jsx";
 import { AutoSizer } from "react-virtualized";
 
-import { isAdminGroup } from "metabase/lib/groups";
-import { capitalize, pluralize } from "metabase/lib/formatting";
+import { isAdminGroup, getGroupNameLocalized } from "metabase/lib/groups";
 import cx from "classnames";
 import _ from "underscore";
 
-const LIGHT_BORDER = "rgb(225, 226, 227)";
-const DARK_BORDER = "rgb(161, 163, 169)";
+import colors from "metabase/lib/colors";
+
+const LIGHT_BORDER = colors["text-light"];
+const DARK_BORDER = colors["text-medium"];
 const BORDER_RADIUS = 4;
 
 const getBorderStyles = ({
@@ -46,8 +48,8 @@ const HEADER_WIDTH = 240;
 
 const DEFAULT_OPTION = {
   icon: "unknown",
-  iconColor: "#9BA5B1",
-  bgColor: "#DFE8EA",
+  iconColor: colors["text-medium"],
+  bgColor: colors["bg-medium"],
 };
 
 const PermissionsHeader = ({ permissions, isFirst, isLast }) => (
@@ -69,7 +71,7 @@ const PermissionsHeader = ({ permissions, isFirst, isLast }) => (
         }}
       >
         {permission.header && (
-          <h5 className="my1 text-centered text-grey-3 text-uppercase text-light">
+          <h5 className="my1 text-centered text-medium text-uppercase text-light">
             {permission.header}
           </h5>
         )}
@@ -88,7 +90,7 @@ const GroupHeader = ({
 }) => (
   <div>
     <h4 className="text-centered full my1 flex layout-centered">
-      {group.name}
+      {getGroupNameLocalized(group)}
       {group.tooltip && (
         <Tooltip tooltip={group.tooltip} maxWidth="24em">
           <Icon className="ml1" name="question" />
@@ -115,12 +117,17 @@ const EntityHeader = ({
   isLast,
 }) => (
   <div className="flex flex-column">
-    <div className={cx("relative flex", { "align-self-center mb1": isColumn })}>
-      <Icon name={icon} className="mr1" />
-      <div className="flex-full">
+    <div
+      className={cx("relative flex", {
+        "align-self-center mb1": isColumn,
+        "align-center": !isColumn,
+      })}
+    >
+      <Icon name={icon} className="ml3 mr2 text-light" />
+      <div>
         <h4>{entity.name}</h4>
         {entity.subtitle && (
-          <div className="mt1 h5 text-monospace text-normal text-grey-2 text-uppercase">
+          <div className="mt1 h5 text-monospace text-normal text-light text-uppercase">
             {entity.subtitle}
           </div>
         )}
@@ -144,14 +151,6 @@ const EntityHeader = ({
   </div>
 );
 
-const CornerHeader = ({ grid }) => (
-  <div className="absolute bottom left right flex flex-column align-center pb1">
-    <div className="flex align-center">
-      <h3 className="ml1">{capitalize(pluralize(grid.type))}</h3>
-    </div>
-  </div>
-);
-
 const PermissionsCell = ({
   group,
   permissions,
@@ -188,6 +187,20 @@ const PermissionsCell = ({
   </div>
 );
 
+const ActionsList = connect()(({ actions, dispatch }) => (
+  <ul className="border-top">
+    {actions.map(action => (
+      <li>
+        {typeof action === "function" ? (
+          action()
+        ) : (
+          <AccessOption option={action} onChange={dispatch} />
+        )}
+      </li>
+    ))}
+  </ul>
+));
+
 class GroupPermissionCell extends Component {
   constructor(props, context) {
     super(props, context);
@@ -223,6 +236,8 @@ class GroupPermissionCell extends Component {
     const { confirmations } = this.state;
 
     const value = permission.getter(group.id, entity.id);
+    const actions =
+      permission.actions && permission.actions(group.id, entity.id);
     const options = permission.options(group.id, entity.id);
     const warning =
       permission.warning && permission.warning(group.id, entity.id);
@@ -258,7 +273,9 @@ class GroupPermissionCell extends Component {
                 name={option.icon}
                 size={28}
                 style={{
-                  color: this.state.hovered ? "#fff" : option.iconColor,
+                  color: this.state.hovered
+                    ? colors["text-white"]
+                    : option.iconColor,
                 }}
               />
               {confirmations &&
@@ -324,6 +341,9 @@ class GroupPermissionCell extends Component {
             this.refs.popover.close();
           }}
         />
+        {actions && actions.length > 0 ? (
+          <ActionsList actions={actions} />
+        ) : null}
       </PopoverWithTrigger>
     );
   }
@@ -332,7 +352,7 @@ class GroupPermissionCell extends Component {
 const AccessOption = ({ value, option, onChange }) => (
   <div
     className={cx(
-      "flex py2 px2 align-center bg-brand-hover text-white-hover cursor-pointer",
+      "flex py2 pl2 pr3 align-center bg-brand-hover text-white-hover cursor-pointer text-bold",
       {
         "bg-brand text-white": value === option,
       },
@@ -341,9 +361,9 @@ const AccessOption = ({ value, option, onChange }) => (
   >
     <Icon
       name={option.icon}
-      className="mr1"
+      className="mr2"
       style={{ color: option.iconColor }}
-      size={18}
+      size={22}
     />
     {option.title}
   </div>
@@ -479,9 +499,6 @@ const PermissionsGrid = ({
                 )}
               </div>
             )}
-            renderCorner={
-              showHeader ? () => <CornerHeader grid={grid} /> : undefined
-            }
           />
         )}
       </AutoSizer>
diff --git a/frontend/src/metabase/admin/permissions/components/PermissionsTabs.jsx b/frontend/src/metabase/admin/permissions/components/PermissionsTabs.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..e0c8b76680a33d5ba6d3b6bf67faa8b5d37d8275
--- /dev/null
+++ b/frontend/src/metabase/admin/permissions/components/PermissionsTabs.jsx
@@ -0,0 +1,21 @@
+import React from "react";
+
+import { t } from "c-3po";
+
+import Radio from "metabase/components/Radio";
+
+const PermissionsTabs = ({ tab, onChangeTab }) => (
+  <div className="px3 mt2 ml2">
+    <Radio
+      value={tab}
+      options={[
+        { name: t`Data permissions`, value: `databases` },
+        { name: t`Collection permissions`, value: `collections` },
+      ]}
+      onChange={onChangeTab}
+      underlined
+      py={1}
+    />
+  </div>
+);
+export default PermissionsTabs;
diff --git a/frontend/src/metabase/admin/permissions/containers/CollectionPermissionsModal.jsx b/frontend/src/metabase/admin/permissions/containers/CollectionPermissionsModal.jsx
index cdc1f76f368473ae1d8c15a14e00b28a269a83e6..854bcf6f9b9ec5716cd84c31683a395dd8d0dad9 100644
--- a/frontend/src/metabase/admin/permissions/containers/CollectionPermissionsModal.jsx
+++ b/frontend/src/metabase/admin/permissions/containers/CollectionPermissionsModal.jsx
@@ -52,10 +52,10 @@ export default class CollectionPermissionsModal extends Component {
         title={t`Permissions for this collection`}
         onClose={onClose}
         footer={[
-          <Link className="link" to="/collections/permissions">
-            See all collection permissions
+          <Link className="link" to="/admin/permissions/collections">
+            {t`See all collection permissions`}
           </Link>,
-          <Button onClick={onClose}>Cancel</Button>,
+          <Button onClick={onClose}>{t`Cancel`}</Button>,
           <Button
             primary
             disabled={!isDirty}
@@ -68,7 +68,7 @@ export default class CollectionPermissionsModal extends Component {
               }
             }}
           >
-            Save
+            {t`Save`}
           </Button>,
         ]}
       >
@@ -78,7 +78,7 @@ export default class CollectionPermissionsModal extends Component {
               className="spread"
               grid={grid}
               onUpdatePermission={onUpdatePermission}
-              cellHeight={40}
+              cellHeight={60}
               isPivoted={true}
               showHeader={false}
             />
diff --git a/frontend/src/metabase/admin/permissions/containers/CollectionsPermissionsApp.jsx b/frontend/src/metabase/admin/permissions/containers/CollectionsPermissionsApp.jsx
index 139042077729d8dae6500402e81838ac67e257ba..1d0e2a948cdb257c60fa777353c1312a84335dc1 100644
--- a/frontend/src/metabase/admin/permissions/containers/CollectionsPermissionsApp.jsx
+++ b/frontend/src/metabase/admin/permissions/containers/CollectionsPermissionsApp.jsx
@@ -14,8 +14,12 @@ import {
   getSaveError,
   getDiff,
 } from "../selectors";
-import { updatePermission, savePermissions } from "../permissions";
-import { goBack, push } from "react-router-redux";
+import {
+  updatePermission,
+  savePermissions,
+  loadPermissions,
+} from "../permissions";
+import { push } from "react-router-redux";
 
 const mapStateToProps = (state, props) => {
   return {
@@ -23,19 +27,22 @@ const mapStateToProps = (state, props) => {
     isDirty: getIsDirty(state, props),
     saveError: getSaveError(state, props),
     diff: getDiff(state, props),
+    tab: "collections",
   };
 };
 
 const mapDispatchToProps = {
   onUpdatePermission: updatePermission,
   onSave: savePermissions,
-  onCancel: () => (window.history.length > 1 ? goBack() : push("/questions")),
+  onCancel: loadPermissions,
+  onChangeTab: tab => push(`/admin/permissions/${tab}`),
 };
 
 const Editor = connect(mapStateToProps, mapDispatchToProps)(PermissionsEditor);
 
 @connect(null, {
   loadCollections: Collections.actions.fetchList,
+  push,
 })
 @fitViewport
 export default class CollectionsPermissionsApp extends Component {
@@ -48,12 +55,11 @@ export default class CollectionsPermissionsApp extends Component {
         {...this.props}
         load={CollectionsApi.graph}
         save={CollectionsApi.updateGraph}
-        fitClassNames={this.props.fitClassNames}
+        fitClassNames={this.props.fitClassNames + " flex-column"}
       >
         <Editor
           {...this.props}
-          collectionId={this.props.location.query.collectionId}
-          admin={false}
+          collectionId={this.props.params.collectionId}
           confirmCancel={false}
         />
       </PermissionsApp>
diff --git a/frontend/src/metabase/admin/permissions/containers/DataPermissionsApp.jsx b/frontend/src/metabase/admin/permissions/containers/DataPermissionsApp.jsx
index dbc999d2e044a5f95901dfe6daaa792eb660034f..4528cb2e3ef42d0fcceadf9d28826bf4a4972ccf 100644
--- a/frontend/src/metabase/admin/permissions/containers/DataPermissionsApp.jsx
+++ b/frontend/src/metabase/admin/permissions/containers/DataPermissionsApp.jsx
@@ -20,7 +20,7 @@ export default class DataPermissionsApp extends Component {
         {...this.props}
         load={PermissionsApi.graph}
         save={PermissionsApi.updateGraph}
-        fitClassNames={this.props.fitClassNames}
+        fitClassNames={this.props.fitClassNames + " flex-column"}
       />
     );
   }
diff --git a/frontend/src/metabase/admin/permissions/containers/DatabasesPermissionsApp.jsx b/frontend/src/metabase/admin/permissions/containers/DatabasesPermissionsApp.jsx
index bb2ba035f94050aa5ebcbac4c50061ed823155ff..ab7908a2bf6ef4f130cb5bb09f0f066d8029e2d3 100644
--- a/frontend/src/metabase/admin/permissions/containers/DatabasesPermissionsApp.jsx
+++ b/frontend/src/metabase/admin/permissions/containers/DatabasesPermissionsApp.jsx
@@ -1,4 +1,5 @@
 import { connect } from "react-redux";
+import { push } from "react-router-redux";
 
 import PermissionsEditor from "../components/PermissionsEditor.jsx";
 
@@ -20,6 +21,7 @@ const mapStateToProps = (state, props) => {
     isDirty: getIsDirty(state, props),
     saveError: getSaveError(state, props),
     diff: getDiff(state, props),
+    tab: "databases",
   };
 };
 
@@ -27,6 +29,7 @@ const mapDispatchToProps = {
   onUpdatePermission: updatePermission,
   onSave: savePermissions,
   onCancel: loadPermissions,
+  onChangeTab: tab => push(`/admin/permissions/${tab}`),
 };
 
 export default connect(mapStateToProps, mapDispatchToProps)(PermissionsEditor);
diff --git a/frontend/src/metabase/admin/permissions/containers/SchemasPermissionsApp.jsx b/frontend/src/metabase/admin/permissions/containers/SchemasPermissionsApp.jsx
index 170675051a4c0db70823dbf9d309c5a61cbbed13..2fd9990d57d2be600cc88952f933a5605b11f3fe 100644
--- a/frontend/src/metabase/admin/permissions/containers/SchemasPermissionsApp.jsx
+++ b/frontend/src/metabase/admin/permissions/containers/SchemasPermissionsApp.jsx
@@ -1,4 +1,5 @@
 import { connect } from "react-redux";
+import { push } from "react-router-redux";
 
 import PermissionsEditor from "../components/PermissionsEditor.jsx";
 
@@ -20,6 +21,7 @@ const mapStateToProps = (state, props) => {
     isDirty: getIsDirty(state, props),
     saveError: getSaveError(state, props),
     diff: getDiff(state, props),
+    tab: "databases",
   };
 };
 
@@ -27,6 +29,7 @@ const mapDispatchToProps = {
   onUpdatePermission: updatePermission,
   onSave: savePermissions,
   onCancel: loadPermissions,
+  onChangeTab: tab => push(`/admin/permissions/${tab}`),
 };
 
 export default connect(mapStateToProps, mapDispatchToProps)(PermissionsEditor);
diff --git a/frontend/src/metabase/admin/permissions/containers/TablesPermissionsApp.jsx b/frontend/src/metabase/admin/permissions/containers/TablesPermissionsApp.jsx
index a091682cf86907716ea12571dee656231fd2fbaa..c369590c8b4c19c17a1f9345f961473caf7eade1 100644
--- a/frontend/src/metabase/admin/permissions/containers/TablesPermissionsApp.jsx
+++ b/frontend/src/metabase/admin/permissions/containers/TablesPermissionsApp.jsx
@@ -1,4 +1,5 @@
 import { connect } from "react-redux";
+import { push } from "react-router-redux";
 
 import PermissionsEditor from "../components/PermissionsEditor.jsx";
 
@@ -20,6 +21,7 @@ const mapStateToProps = (state, props) => {
     isDirty: getIsDirty(state, props),
     saveError: getSaveError(state, props),
     diff: getDiff(state, props),
+    tab: "databases",
   };
 };
 
@@ -27,6 +29,7 @@ const mapDispatchToProps = {
   onUpdatePermission: updatePermission,
   onSave: savePermissions,
   onCancel: loadPermissions,
+  onChangeTab: tab => push(`/admin/permissions/${tab}`),
 };
 
 export default connect(mapStateToProps, mapDispatchToProps)(PermissionsEditor);
diff --git a/frontend/src/metabase/admin/permissions/containers/TogglePropagateAction.jsx b/frontend/src/metabase/admin/permissions/containers/TogglePropagateAction.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..c0841855633f73d5ab91a3ca01aaca1568ca2122
--- /dev/null
+++ b/frontend/src/metabase/admin/permissions/containers/TogglePropagateAction.jsx
@@ -0,0 +1,32 @@
+import React from "react";
+
+import { connect } from "react-redux";
+
+import { t } from "c-3po";
+
+import { getPropagatePermissions } from "../selectors";
+import { setPropagatePermissions } from "../permissions";
+
+import Toggle from "metabase/components/Toggle";
+
+const mapStateToProps = (state, props) => ({
+  propagate: getPropagatePermissions(state, props),
+});
+const mapDispatchToProps = {
+  setPropagatePermissions,
+};
+
+const TogglePropagateAction = connect(mapStateToProps, mapDispatchToProps)(
+  ({ propagate, setPropagatePermissions }) => (
+    <div
+      className="flex align-center bg-medium px2 py1 cursor-pointer"
+      onClick={() => setPropagatePermissions(!propagate)}
+    >
+      <span className="mr2 text-small">{t`Also change sub-collections`}</span>
+      <Toggle small value={propagate} />
+    </div>
+  ),
+);
+
+// eslint-disable-next-line react/display-name
+export default () => <TogglePropagateAction />;
diff --git a/frontend/src/metabase/admin/permissions/permissions.js b/frontend/src/metabase/admin/permissions/permissions.js
index 223e27a545f454122ab5cc7e9967777977dd3162..51094cf0ad9b93bffb3501fc5682d1691f4ff19c 100644
--- a/frontend/src/metabase/admin/permissions/permissions.js
+++ b/frontend/src/metabase/admin/permissions/permissions.js
@@ -67,6 +67,10 @@ export const savePermissions = createThunkAction(
   },
 );
 
+const SET_PROPAGATE_PERMISSIONS =
+  "metabase/admin/permissions/SET_PROPAGATE_PERMISSIONS";
+export const setPropagatePermissions = createAction(SET_PROPAGATE_PERMISSIONS);
+
 const save = handleActions(
   {
     [RESET]: { next: (state, { payload }) => payload.save },
@@ -139,6 +143,13 @@ const saveError = handleActions(
   null,
 );
 
+const propagatePermissions = handleActions(
+  {
+    [SET_PROPAGATE_PERMISSIONS]: { next: (state, { payload }) => payload },
+  },
+  true,
+);
+
 export default combineReducers({
   save,
   load,
@@ -148,4 +159,6 @@ export default combineReducers({
   saveError,
   revision,
   groups,
+
+  propagatePermissions,
 });
diff --git a/frontend/src/metabase/admin/permissions/routes.jsx b/frontend/src/metabase/admin/permissions/routes.jsx
index 18c79f63b1e126adfc741bb644805a71c7fbc138..0d2dc7ab511b73151ecb0b011d15d4e994b69d30 100644
--- a/frontend/src/metabase/admin/permissions/routes.jsx
+++ b/frontend/src/metabase/admin/permissions/routes.jsx
@@ -1,41 +1,50 @@
 import React from "react";
 import { Route } from "metabase/hoc/Title";
-import { IndexRedirect } from "react-router";
+import { IndexRedirect, IndexRoute } from "react-router";
 import { t } from "c-3po";
 import DataPermissionsApp from "./containers/DataPermissionsApp.jsx";
 import DatabasesPermissionsApp from "./containers/DatabasesPermissionsApp.jsx";
 import SchemasPermissionsApp from "./containers/SchemasPermissionsApp.jsx";
 import TablesPermissionsApp from "./containers/TablesPermissionsApp.jsx";
+import CollectionPermissions from "./containers/CollectionsPermissionsApp.jsx";
 
 const getRoutes = store => (
-  <Route
-    title={t`Permissions`}
-    path="permissions"
-    component={DataPermissionsApp}
-  >
+  <Route title={t`Permissions`} path="permissions">
     <IndexRedirect to="databases" />
-    <Route path="databases" component={DatabasesPermissionsApp} />
-    <Route
-      path="databases/:databaseId/schemas"
-      component={SchemasPermissionsApp}
-    />
-    <Route
-      path="databases/:databaseId/schemas/:schemaName/tables"
-      component={TablesPermissionsApp}
-    />
 
-    {/* NOTE: this route is to support null schemas, inject the empty string as the schemaName */}
-    <Route
-      path="databases/:databaseId/tables"
-      component={(
-        props, // eslint-disable-line react/display-name
-      ) => (
-        <TablesPermissionsApp
-          {...props}
-          params={{ ...props.params, schemaName: "" }}
-        />
-      )}
-    />
+    {/* "DATABASES" a.k.a. "data" section */}
+    <Route path="databases" component={DataPermissionsApp}>
+      {/* DATABASES */}
+      <IndexRoute component={DatabasesPermissionsApp} />
+
+      {/* SCHEMAS */}
+      <Route path=":databaseId/schemas" component={SchemasPermissionsApp} />
+
+      {/* TABLES */}
+      <Route
+        path=":databaseId/schemas/:schemaName/tables"
+        component={TablesPermissionsApp}
+      />
+
+      {/* TABLES NO SCHEMA */}
+      {/* NOTE: this route is to support null schemas, inject the empty string as the schemaName */}
+      <Route
+        path=":databaseId/tables"
+        component={(
+          props, // eslint-disable-line react/display-name
+        ) => (
+          <TablesPermissionsApp
+            {...props}
+            params={{ ...props.params, schemaName: "" }}
+          />
+        )}
+      />
+    </Route>
+
+    {/* "COLLECTIONS" section */}
+    <Route path="collections" component={CollectionPermissions}>
+      <Route path=":collectionId" />
+    </Route>
   </Route>
 );
 
diff --git a/frontend/src/metabase/admin/permissions/selectors.js b/frontend/src/metabase/admin/permissions/selectors.js
index d9b68b33ceeff9f35fea7edcff0afd4f596e60de..5bbd0dcab35c03b10f7fe538628e7f1fbc3b5522 100644
--- a/frontend/src/metabase/admin/permissions/selectors.js
+++ b/frontend/src/metabase/admin/permissions/selectors.js
@@ -4,7 +4,11 @@ import { createSelector } from "reselect";
 
 import { push } from "react-router-redux";
 
+import TogglePropagateAction from "./containers/TogglePropagateAction";
+
 import MetabaseAnalytics from "metabase/lib/analytics";
+import colors, { alpha } from "metabase/lib/colors";
+
 import { t } from "c-3po";
 import {
   isDefaultGroup,
@@ -219,20 +223,22 @@ function getRevokingAccessToAllTablesWarningModal(
   }
 }
 
+const BG_ALPHA = 0.15;
+
 const OPTION_GREEN = {
   icon: "check",
-  iconColor: "#9CC177",
-  bgColor: "#F6F9F2",
+  iconColor: colors["success"],
+  bgColor: alpha(colors["success"], BG_ALPHA),
 };
 const OPTION_YELLOW = {
   icon: "eye",
-  iconColor: "#F9D45C",
-  bgColor: "#FEFAEE",
+  iconColor: colors["warning"],
+  bgColor: alpha(colors["warning"], BG_ALPHA),
 };
 const OPTION_RED = {
   icon: "close",
-  iconColor: "#EEA5A5",
-  bgColor: "#FDF3F3",
+  iconColor: colors["error"],
+  bgColor: alpha(colors["error"], BG_ALPHA),
 };
 
 const OPTION_ALL = {
@@ -269,14 +275,14 @@ const OPTION_COLLECTION_WRITE = {
   ...OPTION_GREEN,
   value: "write",
   title: t`Curate collection`,
-  tooltip: t`Can add and remove questions from this collection`,
+  tooltip: t`Can edit this collection and its contents`,
 };
 
 const OPTION_COLLECTION_READ = {
   ...OPTION_YELLOW,
   value: "read",
   title: t`View collection`,
-  tooltip: t`Can view questions in this collection`,
+  tooltip: t`Can view items in this collection`,
 };
 
 export const getTablesPermissionsGrid = createSelector(
@@ -659,9 +665,12 @@ export const getDatabasesPermissionsGrid = createSelector(
 import Collections from "metabase/entities/collections";
 
 const getCollectionId = (state, props) => props && props.collectionId;
+
 const getSingleCollectionPermissionsMode = (state, props) =>
   (props && props.singleCollectionMode) || false;
 
+const permissionsCollectionFilter = collection => !collection.is_personal;
+
 const getCollections = createSelector(
   [
     Collections.selectors.getExpandedCollectionsById,
@@ -671,10 +680,14 @@ const getCollections = createSelector(
   (collectionsById, collectionId, singleMode) => {
     if (collectionId && collectionsById[collectionId]) {
       if (singleMode) {
+        // pass the `singleCollectionMode=true` prop when we just want to show permissions for the provided collection, and not it's subcollections
         return [collectionsById[collectionId]];
       } else {
-        return collectionsById[collectionId].children;
+        return collectionsById[collectionId].children.filter(
+          permissionsCollectionFilter,
+        );
       }
+      // default to root collection
     } else if (collectionsById["root"]) {
       return [collectionsById["root"]];
     } else {
@@ -685,23 +698,51 @@ const getCollections = createSelector(
 const getCollectionPermission = (permissions, groupId, { collectionId }) =>
   getIn(permissions, [groupId, collectionId]);
 
+export const getPropagatePermissions = state =>
+  state.admin.permissions.propagatePermissions;
+
 export const getCollectionsPermissionsGrid = createSelector(
   getCollections,
   getGroups,
   getPermissions,
-  (collections, groups: Array<Group>, permissions: GroupsPermissions) => {
+  getPropagatePermissions,
+  (
+    collections,
+    groups: Array<Group>,
+    permissions: GroupsPermissions,
+    propagatePermissions: boolean,
+  ) => {
     if (!groups || groups.length === 0 || !permissions || !collections) {
       return null;
     }
 
+    const crumbs = [];
+    let parent = collections[0] && collections[0].parent;
+    if (parent) {
+      while (parent) {
+        if (crumbs.length > 0) {
+          crumbs.unshift([
+            parent.name,
+            `/admin/permissions/collections/${parent.id}`,
+          ]);
+        } else {
+          crumbs.unshift([parent.name]);
+        }
+        parent = parent.parent;
+      }
+      crumbs.unshift([t`Collections`, "/admin/permissions/collections"]);
+    }
+
     const defaultGroup = _.find(groups, isDefaultGroup);
 
     return {
       type: "collection",
       icon: "collection",
+      crumbs,
       groups,
       permissions: {
         access: {
+          header: t`Collection Access`,
           options(groupId, entityId) {
             return [
               OPTION_COLLECTION_WRITE,
@@ -709,11 +750,38 @@ export const getCollectionsPermissionsGrid = createSelector(
               OPTION_NONE,
             ];
           },
+          actions(groupId, { collectionId }) {
+            const collection = _.findWhere(collections, {
+              id: collectionId,
+            });
+            if (collection && collection.children.length > 0) {
+              return [TogglePropagateAction];
+            } else {
+              return [];
+            }
+          },
           getter(groupId, entityId) {
             return getCollectionPermission(permissions, groupId, entityId);
           },
           updater(groupId, { collectionId }, value) {
-            return assocIn(permissions, [groupId, collectionId], value);
+            let newPermissions = assocIn(
+              permissions,
+              [groupId, collectionId],
+              value,
+            );
+            if (propagatePermissions) {
+              const collection = _.findWhere(collections, {
+                id: collectionId,
+              });
+              for (const descendent of getDecendentCollections(collection)) {
+                newPermissions = assocIn(
+                  newPermissions,
+                  [groupId, descendent.id],
+                  value,
+                );
+              }
+            }
+            return newPermissions;
           },
           confirm(groupId, entityId, value) {
             return [
@@ -729,14 +797,34 @@ export const getCollectionsPermissionsGrid = createSelector(
             ];
           },
           warning(groupId, entityId) {
-            return getPermissionWarning(
-              getCollectionPermission,
-              null,
-              defaultGroup,
+            const collection = _.findWhere(collections, {
+              id: entityId.collectionId,
+            });
+            if (!collection) {
+              return;
+            }
+            const collectionPerm = getCollectionPermission(
               permissions,
               groupId,
               entityId,
             );
+            const descendentCollections = getDecendentCollections(collection);
+            const descendentPerms = getPermissionsSet(
+              descendentCollections,
+              permissions,
+              groupId,
+            );
+            if (
+              collectionPerm === "none" &&
+              (descendentPerms.has("read") || descendentPerms.has("write"))
+            ) {
+              return t`This group has permission to view at least one subcollection of this collection.`;
+            } else if (
+              collectionPerm === "read" &&
+              descendentPerms.has("write")
+            ) {
+              return t`This group has permission to edit at least one subcollection of this collection.`;
+            }
           },
         },
       },
@@ -748,8 +836,8 @@ export const getCollectionsPermissionsGrid = createSelector(
           name: collection.name,
           link: collection.children &&
             collection.children.length > 0 && {
-              name: t`View collections`,
-              url: `/collections/permissions?collectionId=${collection.id}`,
+              name: t`View sub-collections`,
+              url: `/admin/permissions/collections/${collection.id}`,
             },
         };
       }),
@@ -757,6 +845,22 @@ export const getCollectionsPermissionsGrid = createSelector(
   },
 );
 
+function getDecendentCollections(collection) {
+  const subCollections = collection.children.filter(
+    permissionsCollectionFilter,
+  );
+  return subCollections.concat(...subCollections.map(getDecendentCollections));
+}
+
+function getPermissionsSet(collections, permissions, groupId) {
+  let perms = collections.map(collection =>
+    getCollectionPermission(permissions, groupId, {
+      collectionId: collection.id,
+    }),
+  );
+  return new Set(perms);
+}
+
 export const getDiff = createSelector(
   getMetadata,
   getGroups,
diff --git a/frontend/src/metabase/admin/settings/components/SettingHeader.jsx b/frontend/src/metabase/admin/settings/components/SettingHeader.jsx
index 40f71a1539df000300733df8486bc4e1ad5d3e7d..2694aa222e168bec731a315159c3e84e8fde8400 100644
--- a/frontend/src/metabase/admin/settings/components/SettingHeader.jsx
+++ b/frontend/src/metabase/admin/settings/components/SettingHeader.jsx
@@ -2,10 +2,10 @@ import React from "react";
 
 const SettingHeader = ({ setting }) => (
   <div>
-    <div className="text-grey-4 text-bold text-uppercase">
+    <div className="text-medium text-bold text-uppercase">
       {setting.display_name}
     </div>
-    <div className="text-grey-4 text-measure my1">
+    <div className="text-medium text-measure my1">
       {setting.description}
       {setting.note && <div>{setting.note}</div>}
     </div>
diff --git a/frontend/src/metabase/admin/settings/components/SettingsSetupList.jsx b/frontend/src/metabase/admin/settings/components/SettingsSetupList.jsx
index 549e5a6f1c6e58b4440f9afac5cd63bc5b230dcc..a76191d4cf9f1cb3731299c15d4dc2ebcd3ffcab 100644
--- a/frontend/src/metabase/admin/settings/components/SettingsSetupList.jsx
+++ b/frontend/src/metabase/admin/settings/components/SettingsSetupList.jsx
@@ -4,6 +4,7 @@ import Icon from "metabase/components/Icon.jsx";
 import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper.jsx";
 import { SetupApi } from "metabase/services";
 import { t } from "c-3po";
+import colors from "metabase/lib/colors";
 
 const TaskList = ({ tasks }) => (
   <ol>
@@ -16,7 +17,7 @@ const TaskList = ({ tasks }) => (
 );
 
 const TaskSectionHeader = ({ name }) => (
-  <h4 className="text-grey-4 text-bold text-uppercase pb2">{name}</h4>
+  <h4 className="text-medium text-bold text-uppercase pb2">{name}</h4>
 );
 
 const TaskSection = ({ name, tasks }) => (
@@ -40,14 +41,14 @@ const CompletionBadge = ({ completed }) => (
     style={{
       borderWidth: 1,
       borderStyle: "solid",
-      borderColor: completed ? "#9CC177" : "#DCE9EA",
-      backgroundColor: completed ? "#9CC177" : "#fff",
+      borderColor: completed ? colors["success"] : colors["text-light"],
+      backgroundColor: completed ? colors["success"] : colors["text-white"],
       width: 32,
       height: 32,
       borderRadius: 99,
     }}
   >
-    {completed && <Icon name="check" color={"#fff"} />}
+    {completed && <Icon name="check" color={colors["text-white"]} />}
   </div>
 );
 
diff --git a/frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx b/frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx
index ef04708c76492a1d6d623e969daf9e43a1f9f981..a64966b0d71112259ae9ee6b9776153c45a281dd 100644
--- a/frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx
+++ b/frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx
@@ -116,10 +116,10 @@ export default class SettingsSingleSignOnForm extends Component {
             className="mb2"
           />
           <h2>{t`Sign in with Google`}</h2>
-          <p className="text-grey-4">
+          <p className="text-medium">
             {t`Allows users with existing Metabase accounts to login with a Google account that matches their email address in addition to their Metabase username and password.`}
           </p>
-          <p className="text-grey-4">
+          <p className="text-medium">
             {jt`To allow users to sign in with Google you'll need to give Metabase a Google Developers console application client ID. It only takes a few steps and instructions on how to create a key can be found ${(
               <a
                 className="link"
@@ -139,7 +139,7 @@ export default class SettingsSingleSignOnForm extends Component {
           />
           <div className="py3">
             <div className="flex align-center">
-              <p className="text-grey-4">{t`Allow users to sign up on their own if their Google account email address is from:`}</p>
+              <p className="text-medium">{t`Allow users to sign up on their own if their Google account email address is from:`}</p>
             </div>
             <div className="mt1 bordered rounded inline-block">
               <div className="inline-block px2 h2">@</div>
diff --git a/frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx b/frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx
index 35aa29f65917a2ed3b499b6fca72c21d09be9bf0..cdb79ec0ee9ac572f7213bb628ebd19ee005768e 100644
--- a/frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx
+++ b/frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx
@@ -239,7 +239,7 @@ export default class SettingsSlackForm extends Component {
             />
             Slack
           </h1>
-          <h3 className="text-grey-1">{t`Answers sent right to your Slack #channels`}</h3>
+          <h3 className="text-light">{t`Answers sent right to your Slack #channels`}</h3>
 
           <div className="pt3">
             <a
diff --git a/frontend/src/metabase/admin/settings/components/SettingsUpdatesForm.jsx b/frontend/src/metabase/admin/settings/components/SettingsUpdatesForm.jsx
index ba19a7e02966ea722dd2e232060bd1f0bde7f584..0773fad426a664346f96f716c464e1c328b38d61 100644
--- a/frontend/src/metabase/admin/settings/components/SettingsUpdatesForm.jsx
+++ b/frontend/src/metabase/admin/settings/components/SettingsUpdatesForm.jsx
@@ -21,7 +21,7 @@ export default class SettingsUpdatesForm extends Component {
   renderVersion(version) {
     return (
       <div className="pb3">
-        <h3 className="text-grey-4">
+        <h3 className="text-medium">
           {this.removeVersionPrefixIfNeeded(version.version)}{" "}
           {version.patch ? "(patch release)" : null}
         </h3>
@@ -95,7 +95,7 @@ export default class SettingsUpdatesForm extends Component {
     } else {
       return (
         <div>
-          <div className="p2 bg-green bordered rounded border-green flex flex-row align-center justify-between">
+          <div className="p2 bg-green bordered rounded border-success flex flex-row align-center justify-between">
             <span className="text-white text-bold">{jt`Metabase ${this.removeVersionPrefixIfNeeded(
               versionInfo.latest.version,
             )} is available.  You're running ${this.removeVersionPrefixIfNeeded(
@@ -112,7 +112,7 @@ export default class SettingsUpdatesForm extends Component {
             >{t`Update`}</a>
           </div>
 
-          <div className="text-grey-3">
+          <div className="text-medium">
             <h3 className="py3 text-uppercase">{t`What's Changed:`}</h3>
 
             {this.renderVersion(versionInfo.latest)}
diff --git a/frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx b/frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx
index ca0170a61ee18ba053df755d57def90e11be9d6c..64d3a3ba589be574b2c05b51a388ad69ba4c469a 100644
--- a/frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx
+++ b/frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx
@@ -255,9 +255,9 @@ const SettingContainer = ({
 }) => (
   <div className={className}>
     {name && (
-      <div className="text-grey-4 text-bold text-uppercase my1">{name}</div>
+      <div className="text-medium text-bold text-uppercase my1">{name}</div>
     )}
-    {description && <div className="text-grey-4 my1">{description}</div>}
+    {description && <div className="text-medium my1">{description}</div>}
     {children}
   </div>
 );
@@ -341,7 +341,7 @@ const EditMap = ({
             )}
           </LoadingAndErrorWrapper>
         ) : (
-          <div className="flex-full flex layout-centered text-bold text-grey-1 text-centered">
+          <div className="flex-full flex layout-centered text-bold text-light text-centered">
             {t`Load a GeoJSON file to see a preview`}
           </div>
         )}
diff --git a/frontend/src/metabase/admin/settings/components/widgets/EmbeddingLegalese.jsx b/frontend/src/metabase/admin/settings/components/widgets/EmbeddingLegalese.jsx
index 6bd1b1d2a2a9eac9c8d8cb5fd74ebbc53201520b..01bae33b4da44ae7cedefbee68633ff42c92bac2 100644
--- a/frontend/src/metabase/admin/settings/components/widgets/EmbeddingLegalese.jsx
+++ b/frontend/src/metabase/admin/settings/components/widgets/EmbeddingLegalese.jsx
@@ -5,7 +5,7 @@ import { t } from "c-3po";
 const EmbeddingLegalese = ({ onChange }) => (
   <div className="bordered rounded text-measure p4">
     <h3 className="text-brand">{t`Using embedding`}</h3>
-    <p className="text-grey-4" style={{ lineHeight: 1.48 }}>
+    <p className="text-medium" style={{ lineHeight: 1.48 }}>
       {t`By enabling embedding you're agreeing to the embedding license located at`}{" "}
       <a
         className="link"
@@ -15,7 +15,7 @@ const EmbeddingLegalese = ({ onChange }) => (
         metabase.com/license/embedding
       </a>.
     </p>
-    <p className="text-grey-4" style={{ lineHeight: 1.48 }}>
+    <p className="text-medium" style={{ lineHeight: 1.48 }}>
       {t`In plain English, when you embed charts or dashboards from Metabase in your own application, that application isn't subject to the Affero General Public License that covers the rest of Metabase, provided you keep the Metabase logo and the "Powered by Metabase" visible on those embeds. You should, however, read the license text linked above as that is the actual license that you will be agreeing to by enabling this feature.`}
     </p>
     <div className="flex layout-centered mt4">
diff --git a/frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx b/frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx
index 0fbd283ea79f444c9cd1e373eb22006f4e294323..0b474eea4a43d548af9d6c3899323281ec7e30dd 100644
--- a/frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx
+++ b/frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx
@@ -46,10 +46,14 @@ export default class LdapGroupMappingsWidget extends React.Component {
     };
   }
 
-  _showEditModal = (e: Event) => {
+  _showEditModal = async (e: Event) => {
     e.preventDefault();
+    // just load the setting again to make sure it's up to date
+    const setting = _.findWhere(await SettingsApi.list(), {
+      key: "ldap-group-mappings",
+    });
     this.setState({
-      mappings: this.props.settingValues["ldap-group-mappings"] || {},
+      mappings: (setting && setting.value) || {},
       showEditModal: true,
     });
     PermissionsApi.groups().then(groups => this.setState({ groups }));
@@ -278,10 +282,10 @@ class MappingGroupSelect extends React.Component {
         ref="popover"
         triggerElement={
           <div className="flex align-center">
-            <span className="mr1 text-grey-4">
+            <span className="mr1 text-medium">
               <GroupSummary groups={groups} selectedGroups={selected} />
             </span>
-            <Icon className="text-grey-2" name="chevrondown" size={10} />
+            <Icon className="text-light" name="chevrondown" size={10} />
           </div>
         }
         triggerClasses="AdminSelectBorderless py1"
diff --git a/frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx b/frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx
index 87eecc50b87a56ec360f3c50c1b18d26241adf83..2786326438fc4299ad345b8a8363d75299d8240e 100644
--- a/frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx
+++ b/frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx
@@ -127,7 +127,7 @@ export default class PublicLinksListing extends Component {
                         >
                           <Icon
                             name="close"
-                            className="text-grey-2 text-grey-4-hover cursor-pointer"
+                            className="text-light text-medium-hover cursor-pointer"
                           />
                         </Confirm>
                       </td>
diff --git a/frontend/src/metabase/admin/settings/containers/SettingsEditorApp.jsx b/frontend/src/metabase/admin/settings/containers/SettingsEditorApp.jsx
index 3d5b8e5378985c2e2484eaac4bb8a5158f58ad00..6abc940f7691db7224293a5e74aee250390c2bec 100644
--- a/frontend/src/metabase/admin/settings/containers/SettingsEditorApp.jsx
+++ b/frontend/src/metabase/admin/settings/containers/SettingsEditorApp.jsx
@@ -125,7 +125,7 @@ export default class SettingsEditorApp extends Component {
       return null;
     }
 
-    if (activeSection.name === "Email") {
+    if (activeSection.slug === "email") {
       return (
         <SettingsEmailForm
           ref="emailForm"
@@ -135,9 +135,9 @@ export default class SettingsEditorApp extends Component {
           clearEmailSettings={this.props.clearEmailSettings}
         />
       );
-    } else if (activeSection.name === "Setup") {
+    } else if (activeSection.slug === "setup") {
       return <SettingsSetupList ref="settingsForm" />;
-    } else if (activeSection.name === "Slack") {
+    } else if (activeSection.slug === "slack") {
       return (
         <SettingsSlackForm
           ref="slackForm"
@@ -145,7 +145,7 @@ export default class SettingsEditorApp extends Component {
           updateSlackSettings={this.props.updateSlackSettings}
         />
       );
-    } else if (activeSection.name === "Updates") {
+    } else if (activeSection.slug === "updates") {
       return (
         <SettingsUpdatesForm
           settings={this.props.settings}
@@ -153,7 +153,7 @@ export default class SettingsEditorApp extends Component {
           updateSetting={this.updateSetting}
         />
       );
-    } else if (activeSection.name === "Authentication") {
+    } else if (activeSection.slug === "authentication") {
       // HACK - the presence of this param is a way for us to tell if
       // a user is looking at a sub section of the autentication section
       // since allowing for multi page settings more broadly would require
@@ -225,13 +225,13 @@ export default class SettingsEditorApp extends Component {
         "justify-between",
         "no-decoration",
         {
-          selected: activeSection && section.name === activeSection.name, // this.state.currentSection === idx
+          selected: activeSection && section.slug === activeSection.slug, // this.state.currentSection === idx
         },
       );
 
       // if this is the Updates section && there is a new version then lets add a little indicator
       let newVersionIndicator;
-      if (section.name === "Updates" && newVersionAvailable) {
+      if (section.slug === "updates" && newVersionAvailable) {
         newVersionIndicator = (
           <span
             style={{ padding: "4px 8px 4px 8px" }}
@@ -243,7 +243,7 @@ export default class SettingsEditorApp extends Component {
       }
 
       return (
-        <li key={section.name}>
+        <li key={section.slug}>
           <Link to={"/admin/settings/" + section.slug} className={classes}>
             <span>{section.name}</span>
             {newVersionIndicator}
diff --git a/frontend/src/metabase/admin/settings/selectors.js b/frontend/src/metabase/admin/settings/selectors.js
index b7a71832cb188cb027a16da169354865bbda1e08..62400567a8d95cdfa973168822dc2e755b81302e 100644
--- a/frontend/src/metabase/admin/settings/selectors.js
+++ b/frontend/src/metabase/admin/settings/selectors.js
@@ -252,13 +252,11 @@ const SECTIONS = [
         display_name: t`User filter`,
         type: "string",
         validations: [
-          [
-            value =>
-              (value.match(/\(/g) || []).length !==
-              (value.match(/\)/g) || []).length
-                ? t`Check your parentheses`
-                : null,
-          ],
+          value =>
+            (value.match(/\(/g) || []).length !==
+            (value.match(/\)/g) || []).length
+              ? t`Check your parentheses`
+              : null,
         ],
       },
       {
diff --git a/frontend/src/metabase/app.js b/frontend/src/metabase/app.js
index 1eaf904db0de92ac05a604416d1f54c35ef203ba..e825d8bffc66281d7c968c81d58f6a4daa3f53c4 100644
--- a/frontend/src/metabase/app.js
+++ b/frontend/src/metabase/app.js
@@ -1,22 +1,22 @@
 /* @flow weak */
 
 import "babel-polyfill";
+
+// Use of classList.add and .remove in Background and FitViewPort Hocs requires
+// this polyfill so that those work in older browsers
+import "classlist-polyfill";
+
 import "number-to-locale-string";
 
 // If enabled this monkeypatches `t` and `jt` to return blacked out
 // strings/elements to assist in finding untranslated strings.
 import "metabase/lib/i18n-debug";
 
-// make the i18n function "t" global so we don't have to import it in basically every file
-import { t, jt } from "c-3po";
-global.t = t;
-global.jt = jt;
-
 // set the locale before loading anything else
-import { setLocalization } from "metabase/lib/i18n";
-if (window.MetabaseLocalization) {
-  setLocalization(window.MetabaseLocalization);
-}
+import "metabase/lib/i18n";
+
+// NOTE: why do we need to load this here?
+import "metabase/lib/colors";
 
 import React from "react";
 import ReactDOM from "react-dom";
diff --git a/frontend/src/metabase/auth/AuthApp.jsx b/frontend/src/metabase/auth/AuthApp.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..a329be379934a644faed0145a26f9796e7e304e4
--- /dev/null
+++ b/frontend/src/metabase/auth/AuthApp.jsx
@@ -0,0 +1,6 @@
+import fitViewPort from "metabase/hoc/FitViewPort";
+
+// Auth components expect a full viewport experience to center most of the pages
+const AuthApp = ({ children }) => children;
+
+export default fitViewPort(AuthApp);
diff --git a/frontend/src/metabase/auth/components/AuthScene.jsx b/frontend/src/metabase/auth/components/AuthScene.jsx
index 5e392ff8a62e8f60c605d3be0f12fea2155e222c..b08f66febcebfe550f2fca40c981599de6428e7d 100644
--- a/frontend/src/metabase/auth/components/AuthScene.jsx
+++ b/frontend/src/metabase/auth/components/AuthScene.jsx
@@ -1,3 +1,5 @@
+/* eslint-disable no-color-literals */
+
 import React, { Component } from "react";
 import ReactDOM from "react-dom";
 
diff --git a/frontend/src/metabase/auth/containers/LoginApp.jsx b/frontend/src/metabase/auth/containers/LoginApp.jsx
index d47097dcd53fc6ad76d9d0c0a015de80f3abb05b..cfbe749f8c076f8c044cf1638c7bf2127143a05e 100644
--- a/frontend/src/metabase/auth/containers/LoginApp.jsx
+++ b/frontend/src/metabase/auth/containers/LoginApp.jsx
@@ -3,10 +3,11 @@ import { findDOMNode } from "react-dom";
 import { Link } from "react-router";
 import { connect } from "react-redux";
 
-import cx from "classnames";
 import { t } from "c-3po";
 import AuthScene from "../components/AuthScene.jsx";
 import SSOLoginButton from "../components/SSOLoginButton.jsx";
+import Button from "metabase/components/Button";
+import CheckBox from "metabase/components/CheckBox";
 import FormField from "metabase/components/form/FormField.jsx";
 import FormLabel from "metabase/components/form/FormLabel.jsx";
 import FormMessage from "metabase/components/form/FormMessage.jsx";
@@ -34,6 +35,7 @@ export default class LoginApp extends Component {
     this.state = {
       credentials: {},
       valid: false,
+      rememberMe: true,
     };
   }
 
@@ -112,7 +114,7 @@ export default class LoginApp extends Component {
     const ldapEnabled = Settings.ldapEnabled();
 
     return (
-      <div className="viewport-height full bg-white flex flex-column flex-full md-layout-centered">
+      <div className="full bg-white flex flex-column flex-full md-layout-centered">
         <div className="Login-wrapper wrapper Grid Grid--full md-Grid--1of2 relative z2">
           <div className="Grid-cell flex layout-centered text-brand">
             <LogoIcon className="Logo my4 sm-my0" width={66} height={85} />
@@ -133,7 +135,7 @@ export default class LoginApp extends Component {
                     className="mx1 absolute text-centered left right"
                     style={{ bottom: -8 }}
                   >
-                    <span className="text-bold px3 py2 text-grey-3 bg-white">{t`OR`}</span>
+                    <span className="text-bold px3 py2 text-medium bg-white">{t`OR`}</span>
                   </div>
                 </div>
               )}
@@ -198,21 +200,22 @@ export default class LoginApp extends Component {
               </FormField>
 
               <div className="Form-field">
-                <ul className="Form-offset">
-                  <input name="remember" type="checkbox" defaultChecked />{" "}
-                  <label className="inline-block">{t`Remember Me:`}</label>
-                </ul>
+                <div className="Form-offset flex align-center">
+                  <CheckBox
+                    name="remember"
+                    checked={this.state.rememberMe}
+                    onChange={() =>
+                      this.setState({ rememberMe: !this.state.rememberMe })
+                    }
+                  />
+                  <span className="ml1">{t`Remember Me`}</span>
+                </div>
               </div>
 
-              <div className="Form-actions p2 Grid Grid--full md-Grid--1of2">
-                <button
-                  className={cx("Button Grid-cell", {
-                    "Button--primary": this.state.valid,
-                  })}
-                  disabled={!this.state.valid}
-                >
+              <div className="Form-actions p4">
+                <Button primary={this.state.valid} disabled={!this.state.valid}>
                   {t`Sign in`}
-                </button>
+                </Button>
                 <Link
                   to={
                     "/auth/forgot_password" +
@@ -220,7 +223,7 @@ export default class LoginApp extends Component {
                       ? "?email=" + this.state.credentials.username
                       : "")
                   }
-                  className="Grid-cell py2 sm-py0 text-grey-3 md-text-right text-centered flex-full link"
+                  className="Grid-cell py2 sm-py0 md-text-right text-centered flex-full link"
                   onClick={e => {
                     window.OSX ? window.OSX.resetPassword() : null;
                   }}
diff --git a/frontend/src/metabase/auth/containers/PasswordResetApp.jsx b/frontend/src/metabase/auth/containers/PasswordResetApp.jsx
index f551e17961a4da007652fca7d09e6cef63944836..68219853dec871cde9d5e7e2989f96059d966657 100644
--- a/frontend/src/metabase/auth/containers/PasswordResetApp.jsx
+++ b/frontend/src/metabase/auth/containers/PasswordResetApp.jsx
@@ -93,7 +93,9 @@ export default class PasswordResetApp extends Component {
 
   render() {
     const { resetError, resetSuccess, newUserJoining } = this.props;
-    const passwordComplexity = MetabaseSettings.passwordComplexity(false);
+    const passwordComplexity = MetabaseSettings.passwordComplexityDescription(
+      false,
+    );
 
     const requestLink = (
       <Link to="/auth/forgot_password" className="link">
@@ -144,7 +146,7 @@ export default class PasswordResetApp extends Component {
                 >
                   <h3 className="Login-header Form-offset">{t`New password`}</h3>
 
-                  <p className="Form-offset text-grey-3 mb4">{t`To keep your data secure, passwords ${passwordComplexity}`}</p>
+                  <p className="Form-offset text-medium mb4">{t`To keep your data secure, passwords ${passwordComplexity}`}</p>
 
                   <FormMessage
                     formError={
diff --git a/frontend/src/metabase/collections/containers/CollectionCreate.jsx b/frontend/src/metabase/collections/containers/CollectionCreate.jsx
index fb737d0c93b67b946e5385bc0645afd045a1513e..1f35cbca8b996748821bec4dd0ff3f52428dc7df 100644
--- a/frontend/src/metabase/collections/containers/CollectionCreate.jsx
+++ b/frontend/src/metabase/collections/containers/CollectionCreate.jsx
@@ -1,26 +1,32 @@
 import React, { Component } from "react";
 import { connect } from "react-redux";
-import { push, goBack } from "react-router-redux";
+import { goBack } from "react-router-redux";
 
 import CollectionForm from "metabase/containers/CollectionForm";
+import Collections from "metabase/entities/collections";
 
-@connect(null, { push, goBack })
+const mapStateToProps = (state, props) => ({
+  initialCollectionId: Collections.selectors.getInitialCollectionId(
+    state,
+    props,
+  ),
+});
+
+const mapDispatchToProps = {
+  goBack,
+};
+
+@connect(mapStateToProps, mapDispatchToProps)
 export default class CollectionCreate extends Component {
   render() {
-    const { push, params } = this.props;
-    const collectionId =
-      params && params.collectionId && parseFloat(params.collectionId);
+    const { initialCollectionId, goBack } = this.props;
     return (
       <CollectionForm
-        collection={
-          collectionId != null
-            ? {
-                parent_id: collectionId,
-              }
-            : null
-        }
-        onSaved={({ id }) => push(`/collection/${id}`)}
-        onClose={this.props.goBack}
+        collection={{
+          parent_id: initialCollectionId,
+        }}
+        onSaved={goBack}
+        onClose={goBack}
       />
     );
   }
diff --git a/frontend/src/metabase/components/AccordianList.jsx b/frontend/src/metabase/components/AccordianList.jsx
index 45f4b6cf41e036252bce82caa720c4495943bb17..80aa0c2431d14692013c3329ba17f1cd8f441a60 100644
--- a/frontend/src/metabase/components/AccordianList.jsx
+++ b/frontend/src/metabase/components/AccordianList.jsx
@@ -243,6 +243,9 @@ export default class AccordianList extends Component {
     const sectionIsTogglable = sectionIndex =>
       alwaysTogglable || sections.length > 1;
 
+    // if any section is searchable just enable a global search
+    let globalSearch = false;
+
     const rows = [];
     for (const [sectionIndex, section] of sections.entries()) {
       const isLastSection = sectionIndex === sections.length - 1;
@@ -265,7 +268,11 @@ export default class AccordianList extends Component {
         section.items &&
         section.items.length > 0
       ) {
-        rows.push({ type: "search", section, sectionIndex, isLastSection });
+        if (alwaysExpanded) {
+          globalSearch = true;
+        } else {
+          rows.push({ type: "search", section, sectionIndex, isLastSection });
+        }
       }
       if (
         sectionIsExpanded(sectionIndex) &&
@@ -295,6 +302,15 @@ export default class AccordianList extends Component {
       }
     }
 
+    if (globalSearch) {
+      rows.unshift({
+        type: "search",
+        section: {},
+        sectionIndex: 0,
+        isLastSection: false,
+      });
+    }
+
     const maxHeight =
       this.props.maxHeight > 0 && this.props.maxHeight < Infinity
         ? this.props.maxHeight
@@ -352,7 +368,7 @@ export default class AccordianList extends Component {
                 >
                   {type === "header" ? (
                     alwaysExpanded ? (
-                      <div className="px2 pt2 pb1 h6 text-grey-2 text-uppercase text-bold">
+                      <div className="px2 pt2 pb1 h6 text-light text-uppercase text-bold">
                         {section.name}
                       </div>
                     ) : (
diff --git a/frontend/src/metabase/components/ActivityFeed.js b/frontend/src/metabase/components/ActivityFeed.js
deleted file mode 100644
index e60b68b5d524db8ffc8820c9cf01d64c04cc202b..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/components/ActivityFeed.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import React from "react";
-import HomepageApp from "metabase/home/containers/HomepageApp";
-
-class ActivityFeed extends React.Component {
-  render() {
-    return <HomepageApp />;
-  }
-}
-
-export default ActivityFeed;
diff --git a/frontend/src/metabase/components/AdminEmptyText.jsx b/frontend/src/metabase/components/AdminEmptyText.jsx
index 79270885cfe9ad4801e659391024c6874f91352e..6df1372edec5926ab9530179a3471e6aee9eaf40 100644
--- a/frontend/src/metabase/components/AdminEmptyText.jsx
+++ b/frontend/src/metabase/components/AdminEmptyText.jsx
@@ -2,7 +2,7 @@ import React from "react";
 import PropTypes from "prop-types";
 
 const AdminEmptyText = ({ message }) => (
-  <h2 className="text-grey-3">{message}</h2>
+  <h2 className="text-medium">{message}</h2>
 );
 
 AdminEmptyText.propTypes = {
diff --git a/frontend/src/metabase/components/AdminHeader.jsx b/frontend/src/metabase/components/AdminHeader.jsx
index b85e2b86d08398bc21b08854143bf5a112660252..8b21a6a6a4e85ffa72ee3354ab8434678b3792e4 100644
--- a/frontend/src/metabase/components/AdminHeader.jsx
+++ b/frontend/src/metabase/components/AdminHeader.jsx
@@ -6,7 +6,7 @@ export default class AdminHeader extends Component {
   render() {
     return (
       <div className="MetadataEditor-header clearfix relative flex-no-shrink">
-        <div className="MetadataEditor-headerSection float-left h2 text-grey-4">
+        <div className="MetadataEditor-headerSection float-left h2 text-medium">
           {this.props.title}
         </div>
         <div className="MetadataEditor-headerSection absolute right float-right top bottom flex layout-centered">
diff --git a/frontend/src/metabase/components/ArchiveCollectionModal.jsx b/frontend/src/metabase/components/ArchiveCollectionModal.jsx
index 21f878408d79f4550027eae0125498aeef897ad4..b8bab4f937588c67289c9be6189e9be996155bff 100644
--- a/frontend/src/metabase/components/ArchiveCollectionModal.jsx
+++ b/frontend/src/metabase/components/ArchiveCollectionModal.jsx
@@ -8,7 +8,10 @@ import { t } from "c-3po";
 import Button from "metabase/components/Button";
 import ModalContent from "metabase/components/ModalContent.jsx";
 
+import * as Urls from "metabase/lib/urls";
+
 import Collections from "metabase/entities/collections";
+import { entityObjectLoader } from "metabase/entities/containers/EntityObjectLoader";
 
 const mapDispatchToProps = {
   setCollectionArchived: Collections.actions.setArchived,
@@ -16,14 +19,20 @@ const mapDispatchToProps = {
 };
 
 @connect(null, mapDispatchToProps)
+@entityObjectLoader({
+  entityType: "collections",
+  entityId: (state, props) => props.params.collectionId,
+})
 @withRouter
 class ArchiveCollectionModal extends React.Component {
   async _archive() {
-    await this.props.setCollectionArchived(
-      { id: this.props.params.collectionId },
-      true,
-    );
-    this.props.push("/");
+    const { object, setCollectionArchived, push, params } = this.props;
+    await setCollectionArchived({ id: params.collectionId }, true);
+    const parentId =
+      object.effective_ancestors.length > 0
+        ? object.effective_ancestors.pop().id
+        : null;
+    push(Urls.collection(parentId));
   }
   render() {
     return (
@@ -31,11 +40,11 @@ class ArchiveCollectionModal extends React.Component {
         title={t`Archive this collection?`}
         onClose={() => this.props.onClose()}
       >
-        <Box px={3}>
-          <Text>
+        <Box>
+          <p>
             {t`The dashboards, collections, and pulses in this collection will also be archived.`}
-          </Text>
-          <Flex py={3}>
+          </p>
+          <Flex pt={2}>
             <Button warning ml="auto" onClick={() => this._archive()}>
               {t`Archive`}
             </Button>
diff --git a/frontend/src/metabase/components/Archived.jsx b/frontend/src/metabase/components/Archived.jsx
deleted file mode 100644
index 69bdbf8ea67103026d1c234487ffdca9a7e9e559..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/components/Archived.jsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import React from "react";
-import EmptyState from "metabase/components/EmptyState";
-import Link from "metabase/components/Link";
-import { t } from "c-3po";
-
-import fitViewport from "metabase/hoc/FitViewPort";
-
-// TODO: port to ErrorMessage for more consistent style
-
-const Archived = ({ entityName, linkTo }) => (
-  <div className="full-height flex justify-center align-center">
-    <EmptyState
-      message={
-        <div>
-          <div>{t`This ${entityName} has been archived`}</div>
-          <Link
-            to={linkTo}
-            className="my2 link"
-            style={{ fontSize: "14px" }}
-          >{t`View the archive`}</Link>
-        </div>
-      }
-      icon="viewArchive"
-    />
-  </div>
-);
-
-export default fitViewport(Archived);
diff --git a/frontend/src/metabase/components/ArchivedItem.jsx b/frontend/src/metabase/components/ArchivedItem.jsx
index 40c1c4ae50623fd6cdcb92178dfef5f214ad79f1..636be28e985adf3e6cb22e3fb678dfb317b1278d 100644
--- a/frontend/src/metabase/components/ArchivedItem.jsx
+++ b/frontend/src/metabase/components/ArchivedItem.jsx
@@ -10,11 +10,13 @@ import IconWrapper from "metabase/components/IconWrapper";
 import Swapper from "metabase/components/Swapper";
 import Tooltip from "metabase/components/Tooltip";
 
+import colors from "metabase/lib/colors";
+
 const ArchivedItem = ({
   name,
   type,
   icon,
-  color = "#DEEAF1",
+  color = colors["text-light"],
   isAdmin = false,
   onUnarchive,
 
@@ -22,7 +24,7 @@ const ArchivedItem = ({
   onToggleSelected,
   showSelect,
 }) => (
-  <div className="flex align-center p2 hover-parent hover--visibility border-bottom bg-grey-0-hover">
+  <div className="flex align-center p2 hover-parent hover--visibility border-bottom bg-light-hover">
     <IconWrapper p={1} mr={1} align="center" justify="center">
       <Swapper
         startSwapped={showSelect}
diff --git a/frontend/src/metabase/components/Breadcrumbs.css b/frontend/src/metabase/components/Breadcrumbs.css
index 586d2d9ff1ac01d5cca6325c3562faa4c5869bb9..a236c6173d5183acd7f32769f91a7c4cb7358063 100644
--- a/frontend/src/metabase/components/Breadcrumbs.css
+++ b/frontend/src/metabase/components/Breadcrumbs.css
@@ -1,10 +1,10 @@
 :root {
-  --breadcrumbs-color: #bfc1c2;
-  --breadcrumb-page-color: #636060;
+  --breadcrumbs-color: var(--color-text-light);
+  --breadcrumb-page-color: var(--color-text-dark);
   --breadcrumb-divider-spacing: 0.75em;
   /* taken from Sidebar.css, should probably factor them out into variables */
-  --sidebar-breadcrumbs-color: #9caebe;
-  --sidebar-breadcrumb-page-color: #2d86d4;
+  --sidebar-breadcrumbs-color: var(--color-text-medium);
+  --sidebar-breadcrumb-page-color: var(--color-brand);
 }
 
 :local(.breadcrumbs) {
@@ -17,6 +17,7 @@
   font-size: 0.75rem;
   font-weight: bold;
   text-transform: uppercase;
+  cursor: pointer;
 }
 
 :local(.breadcrumbDivider) {
diff --git a/frontend/src/metabase/components/BrowseApp.jsx b/frontend/src/metabase/components/BrowseApp.jsx
index 477c0be53ebd97b6f0444cfe60954fb51c570675..150b6963130826b315ed773922b83ddb6e43d8a1 100644
--- a/frontend/src/metabase/components/BrowseApp.jsx
+++ b/frontend/src/metabase/components/BrowseApp.jsx
@@ -1,5 +1,5 @@
 import React from "react";
-import { Box } from "grid-styled";
+import { Box, Flex } from "grid-styled";
 import { t } from "c-3po";
 import BrowserCrumbs from "metabase/components/BrowserCrumbs";
 
@@ -15,11 +15,45 @@ import { Grid, GridItem } from "metabase/components/Grid";
 import Icon from "metabase/components/Icon";
 import Link from "metabase/components/Link";
 import Subhead from "metabase/components/Subhead";
+import Tooltip from "metabase/components/Tooltip";
+
+import _ from "underscore";
+
+/** Returns a default Question instance for the provided table */
+function getDefaultQuestionForTable(table) {
+  if (table.entity_type === "entity/GoogleAnalyticsTable") {
+    const dateField = _.findWhere(table.fields, { name: "ga:date" });
+    if (dateField) {
+      return Question.create()
+        .setDatasetQuery({
+          database: table.db_id,
+          type: "query",
+          query: {
+            "source-table": table.id,
+            aggregation: [["metric", "ga:users"], ["metric", "ga:pageviews"]],
+            breakout: [
+              ["datetime-field", ["field-id", dateField.id], "as", "week"],
+            ],
+            filter: ["time-interval", ["field-id", dateField.id], -365, "day"],
+          },
+        })
+        .setDisplay("line");
+    }
+  }
+  return Question.create({
+    databaseId: table.db_id,
+    tableId: table.id,
+  });
+}
 
 export const DatabaseListLoader = props => (
   <EntityListLoader entityType="databases" {...props} />
 );
 
+const PAGE_PADDING = [1, 2, 4];
+const ITEM_WIDTHS = [1, 1 / 2, 1 / 3];
+const ANALYTICS_CONTEXT = "Data Browse";
+
 const SchemaListLoader = ({ dbId, ...props }) => (
   <EntityListLoader entityType="schemas" entityQuery={{ dbId }} {...props} />
 );
@@ -52,27 +86,40 @@ export class SchemaBrowser extends React.Component {
           {({ schemas }) =>
             schemas.length > 1 ? (
               <Box>
-                <BrowserCrumbs
-                  crumbs={[
-                    { title: t`Your data`, to: "browse" },
-                    { title: <DatabaseName dbId={dbId} /> },
-                  ]}
-                />
+                <Box my={2}>
+                  <BrowserCrumbs
+                    analyticsContext={ANALYTICS_CONTEXT}
+                    crumbs={[
+                      { title: t`Our data`, to: "browse" },
+                      { title: <DatabaseName dbId={dbId} /> },
+                    ]}
+                  />
+                </Box>
                 <Grid>
                   {schemas.map(schema => (
-                    <GridItem w={1 / 3}>
+                    <GridItem w={ITEM_WIDTHS} key={schema.id}>
                       <Link
                         to={`/browse/${dbId}/schema/${schema.name}`}
                         mb={1}
                         hover={{ color: normal.purple }}
+                        data-metabase-event={`${ANALYTICS_CONTEXT};Schema Click`}
+                        className="overflow-hidden"
                       >
-                        <Card hoverable>
-                          <EntityItem
-                            name={schema.name}
-                            iconName="folder"
-                            iconColor={normal.purple}
-                            item={schema}
-                          />
+                        <Card hoverable px={1}>
+                          <Flex align="center">
+                            <EntityItem
+                              name={schema.name}
+                              iconName="folder"
+                              iconColor={normal.purple}
+                              item={schema}
+                            />
+                            <Box ml="auto">
+                              <Icon name="reference" />
+                              <Tooltip tooltip={t`X-ray this schema`}>
+                                <Icon name="bolt" mx={1} />
+                              </Tooltip>
+                            </Box>
+                          </Flex>
                         </Card>
                       </Link>
                     </GridItem>
@@ -98,35 +145,76 @@ export class TableBrowser extends React.Component {
           {({ tables, loading, error }) => {
             return (
               <Box>
-                <BrowserCrumbs
-                  crumbs={[
-                    { title: t`Your data`, to: "browse" },
-                    {
-                      title: <DatabaseName dbId={dbId} />,
-                      to: `browse/${dbId}`,
-                    },
-                    schemaName != null && { title: schemaName },
-                  ]}
-                />
+                <Box mt={3} mb={2}>
+                  <BrowserCrumbs
+                    analyticsContext={ANALYTICS_CONTEXT}
+                    crumbs={[
+                      { title: t`Our data`, to: "browse" },
+                      {
+                        title: <DatabaseName dbId={dbId} />,
+                        to: `browse/${dbId}`,
+                      },
+                      schemaName != null && { title: schemaName },
+                    ]}
+                  />
+                </Box>
                 <Grid>
                   {tables.map(table => {
-                    const link = Question.create({
-                      databaseId: parseInt(dbId),
-                      tableId: table.id,
-                    }).getUrl();
-
+                    const link = getDefaultQuestionForTable(table).getUrl();
                     return (
-                      <GridItem w={1 / 3}>
-                        <Link to={link} mb={1} hover={{ color: normal.purple }}>
-                          <Card hoverable>
-                            <EntityItem
-                              item={table}
-                              name={table.display_name || table.name}
-                              iconName="table"
-                              iconColor={normal.purple}
-                            />
-                          </Card>
-                        </Link>
+                      <GridItem w={ITEM_WIDTHS} key={table.id}>
+                        <Card
+                          hoverable
+                          px={1}
+                          className="hover-parent hover--visibility"
+                        >
+                          <Flex align="center">
+                            <Link
+                              to={link}
+                              ml={1}
+                              hover={{ color: normal.purple }}
+                              data-metabase-event={`${ANALYTICS_CONTEXT};Table Click`}
+                              className="overflow-hidden"
+                            >
+                              <EntityItem
+                                item={table}
+                                name={table.display_name || table.name}
+                                iconName="table"
+                                iconColor={normal.purple}
+                              />
+                            </Link>
+                            <Box ml="auto" mr={1} className="hover-child">
+                              <Flex align="center">
+                                <Tooltip tooltip={t`X-ray this table`}>
+                                  <Link
+                                    to={`auto/dashboard/table/${table.id}`}
+                                    data-metabase-event={`${ANALYTICS_CONTEXT};Table Item;X-ray Click`}
+                                  >
+                                    <Icon
+                                      name="bolt"
+                                      mx={1}
+                                      color={normal.yellow}
+                                      size={20}
+                                    />
+                                  </Link>
+                                </Tooltip>
+                                <Tooltip tooltip={t`Learn about this table`}>
+                                  <Link
+                                    to={`reference/databases/${dbId}/tables/${
+                                      table.id
+                                    }`}
+                                    data-metabase-event={`${ANALYTICS_CONTEXT};Table Item;Reference Click`}
+                                  >
+                                    <Icon
+                                      name="reference"
+                                      color={normal.grey1}
+                                    />
+                                  </Link>
+                                </Tooltip>
+                              </Flex>
+                            </Box>
+                          </Flex>
+                        </Card>
                       </GridItem>
                     );
                   })}
@@ -142,7 +230,7 @@ export class TableBrowser extends React.Component {
 
 export class BrowseApp extends React.Component {
   render() {
-    return <Box mx={4}>{this.props.children}</Box>;
+    return <Box mx={PAGE_PADDING}>{this.props.children}</Box>;
   }
 }
 
@@ -150,14 +238,22 @@ export class DatabaseBrowser extends React.Component {
   render() {
     return (
       <Box>
-        <BrowserCrumbs crumbs={[{ title: t`Your data` }]} />
+        <Box my={2}>
+          <BrowserCrumbs
+            crumbs={[{ title: t`Our data` }]}
+            analyticsContext={ANALYTICS_CONTEXT}
+          />
+        </Box>
         <DatabaseListLoader>
           {({ databases, loading, error }) => {
             return (
               <Grid>
                 {databases.map(database => (
-                  <GridItem>
-                    <Link to={`browse/${database.id}`}>
+                  <GridItem w={ITEM_WIDTHS} key={database.id}>
+                    <Link
+                      to={`browse/${database.id}`}
+                      data-metabase-event={`${ANALYTICS_CONTEXT};Database Click`}
+                    >
                       <Card p={3} hover={{ color: normal.blue }}>
                         <Icon name="database" color={normal.grey2} mb={3} />
                         <Subhead>{database.name}</Subhead>
diff --git a/frontend/src/metabase/components/BrowserCrumbs.jsx b/frontend/src/metabase/components/BrowserCrumbs.jsx
index f3b8c18ccd9187151d2a6f9517fa2b78589b6ef8..2f09021aeb2aae287177cbf930db351f9ac58aff 100644
--- a/frontend/src/metabase/components/BrowserCrumbs.jsx
+++ b/frontend/src/metabase/components/BrowserCrumbs.jsx
@@ -1,30 +1,43 @@
 import React from "react";
-import { Box, Flex } from "grid-styled";
+import { Flex } from "grid-styled";
 import Icon from "metabase/components/Icon";
 import Link from "metabase/components/Link";
-import Subhead from "metabase/components/Subhead";
+
+import colors from "metabase/lib/colors";
 
 // TODO: merge with Breadcrumbs
 
-const BrowseHeader = ({ children }) => (
-  <Box my={3}>
-    <Subhead>{children}</Subhead>
-  </Box>
+const Crumb = ({ children }) => (
+  <h5
+    className="text-uppercase text-brand-hover text-medium"
+    style={{ fontWeight: 900 }}
+  >
+    {children}
+  </h5>
 );
 
-const BrowserCrumbs = ({ crumbs }) => (
+const BrowserCrumbs = ({ crumbs, analyticsContext }) => (
   <Flex align="center">
     {crumbs.filter(c => c).map((crumb, index, crumbs) => [
-      crumb.to ? (
-        <Link key={"title" + index} to={crumb.to}>
-          <BrowseHeader>{crumb.title}</BrowseHeader>
-        </Link>
-      ) : (
-        <BrowseHeader>{crumb.title}</BrowseHeader>
+      crumb.to && (
+        <Flex align="center">
+          <Link
+            key={"title" + index}
+            to={crumb.to}
+            data-metabase-event={`${analyticsContext};Bread Crumb;Click`}
+          >
+            <Crumb>{crumb.title}</Crumb>
+          </Link>
+          {index < crumbs.length - 1 ? (
+            <Icon
+              key={"divider" + index}
+              name="chevronright"
+              color={colors["text-light"]}
+              mx={1}
+            />
+          ) : null}
+        </Flex>
       ),
-      index < crumbs.length - 1 ? (
-        <Icon key={"divider" + index} name="chevronright" mx={2} />
-      ) : null,
     ])}
   </Flex>
 );
diff --git a/frontend/src/metabase/components/BulkActionBar.jsx b/frontend/src/metabase/components/BulkActionBar.jsx
index 412f1f041dea556053b27d152b8100917ffdd449..335c642994d206d6f9f084072254f13ec6817028 100644
--- a/frontend/src/metabase/components/BulkActionBar.jsx
+++ b/frontend/src/metabase/components/BulkActionBar.jsx
@@ -1,5 +1,5 @@
 import React from "react";
-import { Box, Flex } from "grid-styled";
+import { Box } from "grid-styled";
 import Card from "metabase/components/Card";
 import { Motion, spring } from "react-motion";
 
@@ -22,18 +22,14 @@ const BulkActionBar = ({ children, showing }) => (
     }}
   >
     {({ opacity, translateY }) => (
-      <FixedBottomBar>
-        <Card
-          style={{
-            borderRadius: 0,
-            opacity,
-            transform: `translateY(${translateY}px)`,
-          }}
-        >
-          <Flex align="center" py={2} px={4}>
-            {children}
-          </Flex>
-        </Card>
+      <FixedBottomBar
+        style={{
+          borderRadius: 0,
+          opacity,
+          transform: `translateY(${translateY}px)`,
+        }}
+      >
+        <Card>{children}</Card>
       </FixedBottomBar>
     )}
   </Motion>
diff --git a/frontend/src/metabase/components/Calendar.css b/frontend/src/metabase/components/Calendar.css
index 672333441a30e84b2c80019d2c6417860bf00dde..861a91a4c59e6340193dcaf0d56247a126010060 100644
--- a/frontend/src/metabase/components/Calendar.css
+++ b/frontend/src/metabase/components/Calendar.css
@@ -8,9 +8,9 @@
 }
 
 .Calendar-day {
-  color: color(var(--base-grey) shade(30%));
+  color: var(--color-text-medium);
   position: relative;
-  border: 1px solid color(var(--base-grey) shade(20%) alpha(-50%));
+  border: 1px solid color(var(--color-border) shade(20%) alpha(-50%));
   border-radius: 0;
   border-bottom-width: 0;
   border-right-width: 0;
@@ -36,7 +36,7 @@
 }
 
 .Calendar-day:hover {
-  color: var(--purple-color);
+  color: var(--color-accent2);
 }
 
 .Calendar-day-name {
@@ -46,12 +46,12 @@
 .Calendar-day--selected,
 .Calendar-day--selected-end {
   color: white !important;
-  background-color: var(--purple-color);
+  background-color: var(--color-accent2);
   z-index: 1;
 }
 
 .Calendar-day--in-range {
-  background-color: #e3daeb;
+  background-color: var(--color-bg-medium);
 }
 
 .Calendar-day--selected:after,
@@ -63,7 +63,7 @@
   bottom: -1px;
   left: -2px;
   right: -2px;
-  border: 2px solid color(var(--purple-color) shade(25%));
+  border: 2px solid var(--color-accent2);
   border-radius: 4px;
   z-index: 2;
 }
@@ -77,20 +77,20 @@
 .Calendar-day--week-start.Calendar-day--in-range:after {
   border-top-left-radius: 4px;
   border-bottom-left-radius: 4px;
-  border-left-color: color(var(--purple-color) shade(25%));
+  border-left-color: var(--color-accent2);
 }
 
 .Calendar-day--week-end.Calendar-day--in-range:after {
   border-top-right-radius: 4px;
   border-bottom-right-radius: 4px;
-  border-right-color: color(var(--purple-color) shade(25%));
+  border-right-color: var(--color-accent2);
 }
 
 .circle-button {
   display: block;
   font-size: 20px;
-  color: color(var(--base-grey) shade(30%));
-  border: 2px solid color(var(--base-grey) shade(10%));
+  color: var(--color-text-medium);
+  border: 2px solid var(--color-border);
   border-radius: 99px;
   width: 24px;
   height: 24px;
@@ -102,8 +102,8 @@
 }
 
 .circle-button:hover {
-  color: var(--purple-color);
-  border-color: var(--purple-color);
+  color: var(--color-accent2);
+  border-color: var(--color-accent2);
 }
 
 .circle-button--top {
diff --git a/frontend/src/metabase/components/Card.info.js b/frontend/src/metabase/components/Card.info.js
new file mode 100644
index 0000000000000000000000000000000000000000..36aa39b8fc834f79c9ff89c1368aff9d0b60c99a
--- /dev/null
+++ b/frontend/src/metabase/components/Card.info.js
@@ -0,0 +1,27 @@
+import React from "react";
+import Card from "metabase/components/Card";
+export const component = Card;
+
+export const description = `
+A generic card component.
+`;
+
+const DemoContent = () => <div className="p4">Look, a card!</div>;
+
+export const examples = {
+  normal: (
+    <Card>
+      <DemoContent />
+    </Card>
+  ),
+  dark: (
+    <Card dark>
+      <DemoContent />
+    </Card>
+  ),
+  hoverable: (
+    <Card hoverable>
+      <DemoContent />
+    </Card>
+  ),
+};
diff --git a/frontend/src/metabase/components/Card.jsx b/frontend/src/metabase/components/Card.jsx
index d4e6603951497a54f0dbd5a384d74bdb2287d2fd..13b9acedd4ba53024bc2bd54efc20e71536cd3b8 100644
--- a/frontend/src/metabase/components/Card.jsx
+++ b/frontend/src/metabase/components/Card.jsx
@@ -1,17 +1,21 @@
 import styled from "styled-components";
 import { space } from "styled-system";
-import { normal } from "metabase/lib/colors";
+import colors, { alpha } from "metabase/lib/colors";
 
 const Card = styled.div`
-  ${space} background-color: ${props => (props.dark ? "#2e353b" : "white")};
-  border: 1px solid ${props => (props.dark ? "transparent" : "#f5f6f7")};
+  ${space} background-color: ${props =>
+      props.dark ? colors["text-dark"] : "white"};
+  border: 1px solid
+    ${props => (props.dark ? "transparent" : colors["bg-medium"])};
   ${props => props.dark && `color: white`};
   border-radius: 6px;
-  box-shadow: 0 1px 3px ${props => (props.dark ? "#65686b" : normal.grey1)};
+  box-shadow: 0 7px 20px ${props => colors["shadow"]};
+  transition: all 0.2s linear;
+  line-height: 24px;
   ${props =>
     props.hoverable &&
     `&:hover {
-    box-shadow: 0 2px 3px ${props.dark ? "#2e35b" : "#DCE1E4"};
+    box-shadow: 0 10px 22px ${alpha(colors["shadow"], 0.09)};
   }`};
 `;
 
diff --git a/frontend/src/metabase/components/ChannelSetupMessage.jsx b/frontend/src/metabase/components/ChannelSetupMessage.jsx
index 154984aeab12c1e37822a733887921023ffd148b..136d229ce52568416a1f5262d92ad75176c29d4a 100644
--- a/frontend/src/metabase/components/ChannelSetupMessage.jsx
+++ b/frontend/src/metabase/components/ChannelSetupMessage.jsx
@@ -38,7 +38,7 @@ export default class ChannelSetupMessage extends Component {
       let adminEmail = Settings.get("admin_email");
       content = (
         <div className="mb1">
-          <h4 className="text-grey-4">{t`Your admin's email address`}:</h4>
+          <h4 className="text-medium">{t`Your admin's email address`}:</h4>
           <a className="h2 link no-decoration" href={"mailto:" + adminEmail}>
             {adminEmail}
           </a>
diff --git a/frontend/src/metabase/components/CheckBox.jsx b/frontend/src/metabase/components/CheckBox.jsx
index b1ab71d20b52fad501086743b5b496fb3f218497..53a3fab28363afa07207f96971d25e9c6405024a 100644
--- a/frontend/src/metabase/components/CheckBox.jsx
+++ b/frontend/src/metabase/components/CheckBox.jsx
@@ -2,7 +2,7 @@ import React, { Component } from "react";
 import PropTypes from "prop-types";
 import Icon from "metabase/components/Icon";
 
-import { normal as defaultColors } from "metabase/lib/colors";
+import colors, { normal as defaultColors } from "metabase/lib/colors";
 
 export default class CheckBox extends Component {
   static propTypes = {
@@ -36,7 +36,7 @@ export default class CheckBox extends Component {
     const { checked, indeterminate, color, padding, size, noIcon } = this.props;
 
     const checkedColor = defaultColors[color];
-    const uncheckedColor = "#ddd";
+    const uncheckedColor = colors["text-light"];
 
     const checkboxStyle = {
       width: size,
diff --git a/frontend/src/metabase/components/CollectionEmptyState.jsx b/frontend/src/metabase/components/CollectionEmptyState.jsx
index f8965abc07b62334e0888265cb74beb08de341b9..4902ae6836307b47cab87751242e7959c58dc722 100644
--- a/frontend/src/metabase/components/CollectionEmptyState.jsx
+++ b/frontend/src/metabase/components/CollectionEmptyState.jsx
@@ -1,30 +1,34 @@
 import React from "react";
-import { Box } from "grid-styled";
 import { t } from "c-3po";
 import RetinaImage from "react-retina-image";
-import Subhead from "metabase/components/Subhead";
+import { withRouter } from "react-router";
 
-import { normal } from "metabase/lib/colors";
+import Link from "metabase/components/Link";
 
-const CollectionEmptyState = () => {
+import * as Urls from "metabase/lib/urls";
+
+import EmptyState from "metabase/components/EmptyState";
+
+const CollectionEmptyState = ({ params }) => {
   return (
-    <Box py={2}>
-      <Box mb={3}>
+    <EmptyState
+      title={t`This collection is empty, like a blank canvas`}
+      message={t`You can use collections to organize and group dashboards, questions and pulses for your team or yourself`}
+      illustrationElement={
         <RetinaImage
           src="app/img/collection-empty-state.png"
           className="block ml-auto mr-auto"
         />
-      </Box>
-      <Box className="text-centered">
-        <Subhead color={normal.grey2}>
-          {t`This collection is empty, like a blank canvas`}
-        </Subhead>
-        <p className="text-paragraph text-grey-2">
-          {t`You can use collections to organize and group dashboards, questions and pulses for your team or yourself`}
-        </p>
-      </Box>
-    </Box>
+      }
+      link={
+        <Link
+          className="link text-bold"
+          mt={2}
+          to={Urls.newCollection(params.collectionId)}
+        >{t`Create another collection`}</Link>
+      }
+    />
   );
 };
 
-export default CollectionEmptyState;
+export default withRouter(CollectionEmptyState);
diff --git a/frontend/src/metabase/components/CollectionItem.jsx b/frontend/src/metabase/components/CollectionItem.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..7c530fda382776c1be287b8566028ce875f0f16b
--- /dev/null
+++ b/frontend/src/metabase/components/CollectionItem.jsx
@@ -0,0 +1,87 @@
+import React from "react";
+import { Flex } from "grid-styled";
+
+import Card from "metabase/components/Card";
+import Ellipsified from "metabase/components/Ellipsified";
+import Icon from "metabase/components/Icon";
+import Link from "metabase/components/Link";
+
+import colors from "metabase/lib/colors";
+
+const ItemLink = props => (
+  <Link
+    to={`collection/${props.collection.id}`}
+    bg={
+      props.hovered
+        ? colors["brand"]
+        : props.highlighted ? colors["bg-light"] : colors["bg-medium"]
+    }
+    color={props.hovered ? "white" : colors["text-medium"]}
+    className="block rounded relative text-brand-hover"
+    data-metabase-event={props.event}
+    style={{
+      borderSize: 1,
+      borderColor: props.hovered
+        ? colors["brand"]
+        : props.highlighted ? colors["bg-medium"] : "transparent",
+      borderStyle: props.hovered
+        ? "solid"
+        : props.highlighted ? "dotted" : "solid",
+    }}
+    hover={{ color: colors["brand"] }}
+  >
+    {props.children}
+  </Link>
+);
+
+const ItemInfo = props => (
+  <h4 className="overflow-hidden">
+    <Ellipsified>{props.collection.name}</Ellipsified>
+  </h4>
+);
+
+const CollectionItem = props => {
+  const icon = (
+    <Icon
+      name={props.iconName}
+      mx={props.asCard ? 0 : 1}
+      color={props.asCard ? "white" : colors["bg-dark"]}
+    />
+  );
+
+  const content = (
+    <Flex
+      align="center"
+      py={props.asCard ? 1 : 2}
+      px={props.asCard ? 1 : 0}
+      key={`collection-${props.collection.id}`}
+    >
+      {props.asCard ? (
+        <Flex
+          align="center"
+          justify="center"
+          w="42px"
+          bg={colors["bg-dark"]}
+          style={{ height: 42, borderRadius: 6, flexShrink: 0 }}
+          mr={1}
+        >
+          {icon}
+        </Flex>
+      ) : (
+        icon
+      )}
+      <ItemInfo {...props} />
+    </Flex>
+  );
+  return (
+    <ItemLink {...props}>
+      {props.asCard ? <Card hoverable>{content}</Card> : content}
+    </ItemLink>
+  );
+};
+
+CollectionItem.defaultProps = {
+  iconName: "all",
+};
+
+export default CollectionItem;
diff --git a/frontend/src/metabase/components/CollectionLanding.jsx b/frontend/src/metabase/components/CollectionLanding.jsx
index 6426014d8677314a0079d187cca3726bfde9837c..6b7e11cd7dded818c82f2ef6a5c885114258a891 100644
--- a/frontend/src/metabase/components/CollectionLanding.jsx
+++ b/frontend/src/metabase/components/CollectionLanding.jsx
@@ -1,14 +1,16 @@
 import React from "react";
 import { Box, Flex } from "grid-styled";
-import { t } from "c-3po";
+import { t, msgid, ngettext } from "c-3po";
 import { connect } from "react-redux";
+import { withRouter } from "react-router";
 import _ from "underscore";
+import cx from "classnames";
+
 import listSelect from "metabase/hoc/ListSelect";
 import BulkActionBar from "metabase/components/BulkActionBar";
-import cx from "classnames";
 
 import * as Urls from "metabase/lib/urls";
-import { normal } from "metabase/lib/colors";
+import colors, { normal } from "metabase/lib/colors";
 
 import Button from "metabase/components/Button";
 import Card from "metabase/components/Card";
@@ -16,18 +18,22 @@ import Modal from "metabase/components/Modal";
 import StackedCheckBox from "metabase/components/StackedCheckBox";
 import EntityItem from "metabase/components/EntityItem";
 import { Grid, GridItem } from "metabase/components/Grid";
-import Icon from "metabase/components/Icon";
+import Icon, { IconWrapper } from "metabase/components/Icon";
 import Link from "metabase/components/Link";
-import CollectionEmptyState from "metabase/components/CollectionEmptyState";
 import EntityMenu from "metabase/components/EntityMenu";
-import Ellipsified from "metabase/components/Ellipsified";
 import VirtualizedList from "metabase/components/VirtualizedList";
 import BrowserCrumbs from "metabase/components/BrowserCrumbs";
+import ItemTypeFilterBar from "metabase/components/ItemTypeFilterBar";
+import CollectionEmptyState from "metabase/components/CollectionEmptyState";
+
+import Tooltip from "metabase/components/Tooltip";
 
 import CollectionMoveModal from "metabase/containers/CollectionMoveModal";
 import { entityObjectLoader } from "metabase/entities/containers/EntityObjectLoader";
 
-import { ROOT_COLLECTION } from "metabase/entities/collections";
+import CollectionList from "metabase/components/CollectionList";
+
+import { getUserIsAdmin } from "metabase/selectors/user";
 
 // drag-and-drop components
 import ItemDragSource from "metabase/containers/dnd/ItemDragSource";
@@ -36,74 +42,51 @@ import PinPositionDropTarget from "metabase/containers/dnd/PinPositionDropTarget
 import PinDropTarget from "metabase/containers/dnd/PinDropTarget";
 import ItemsDragLayer from "metabase/containers/dnd/ItemsDragLayer";
 
-const CollectionItem = ({ collection, color, iconName = "all" }) => (
-  <Link
-    to={`collection/${collection.id}`}
-    hover={{ color: normal.blue }}
-    color={color || normal.grey2}
-  >
-    <Flex align="center" py={1} key={`collection-${collection.id}`}>
-      <Icon name={iconName} mx={1} color="#93B3C9" />
-      <h4>
-        <Ellipsified>{collection.name}</Ellipsified>
-      </h4>
-    </Flex>
-  </Link>
+import EmptyState from "metabase/components/EmptyState";
+
+const ROW_HEIGHT = 72;
+const PAGE_PADDING = [2, 3, 4];
+
+const ANALYTICS_CONTEXT = "Collection Landing";
+
+const EmptyStateWrapper = ({ children }) => (
+  <Box p={5} w={1} h={"200px"}>
+    {children}
+  </Box>
 );
 
-@connect(({ currentUser }) => ({ currentUser }), null)
-class CollectionList extends React.Component {
-  render() {
-    const { collections, currentUser, isRoot } = this.props;
-    return (
-      <Box mb={2}>
-        <Box my={2}>
-          {isRoot && (
-            <Box className="relative">
-              <CollectionDropTarget
-                collection={{ id: currentUser.personal_collection_id }}
-              >
-                <CollectionItem
-                  collection={{
-                    name: t`My personal collection`,
-                    id: currentUser.personal_collection_id,
-                  }}
-                  iconName="star"
-                />
-              </CollectionDropTarget>
-            </Box>
-          )}
-          {isRoot &&
-            currentUser.is_superuser && (
-              <CollectionItem
-                collection={{
-                  name: t`Everyone else's personal collections`,
-                  // Bit of a hack. The route /collection/users lists
-                  // user collections but is not itself a colllection,
-                  // but using the fake id users here works
-                  id: "users",
-                }}
-                iconName="person"
-              />
-            )}
-        </Box>
-        {collections
-          .filter(c => c.id !== currentUser.personal_collection_id)
-          .map(collection => (
-            <Box key={collection.id} mb={1} className="relative">
-              <CollectionDropTarget collection={collection}>
-                <ItemDragSource item={collection}>
-                  <CollectionItem collection={collection} />
-                </ItemDragSource>
-              </CollectionDropTarget>
-            </Box>
-          ))}
-      </Box>
-    );
-  }
-}
+const DashboardEmptyState = () => (
+  <EmptyStateWrapper>
+    <EmptyState
+      message={t`Dashboards let you collect and share data in one place.`}
+      illustrationElement={<Icon name="dashboard" size={32} />}
+    />
+  </EmptyStateWrapper>
+);
 
-const ROW_HEIGHT = 72;
+const PulseEmptyState = () => (
+  <EmptyStateWrapper>
+    <EmptyState
+      message={t`Pulses let you send out the latest data to your team on a schedule via email or slack.`}
+      illustrationElement={<Icon name="pulse" size={32} />}
+    />
+  </EmptyStateWrapper>
+);
+
+const QuestionEmptyState = () => (
+  <EmptyStateWrapper>
+    <EmptyState
+      message={t`Questions are a saved look at your data.`}
+      illustrationElement={<Icon name="beaker" size={32} />}
+    />
+  </EmptyStateWrapper>
+);
+
+const EMPTY_STATES = {
+  dashboard: <DashboardEmptyState />,
+  pulse: <PulseEmptyState />,
+  card: <QuestionEmptyState />,
+};
 
 import { entityListLoader } from "metabase/entities/containers/EntityListLoader";
 
@@ -124,20 +107,59 @@ import { entityListLoader } from "metabase/entities/containers/EntityListLoader"
   );
   // sort the pinned items by collection_position
   pinned.sort((a, b) => a.collection_position - b.collection_position);
-  return { collections, pinned, unpinned };
+  return {
+    collections,
+    pinned,
+    unpinned,
+    isAdmin: getUserIsAdmin(state),
+  };
 })
 // only apply bulk actions to unpinned items
 @listSelect({
   listProp: "unpinned",
   keyForItem: item => `${item.model}:${item.id}`,
 })
+@withRouter
 class DefaultLanding extends React.Component {
   state = {
     moveItems: null,
   };
 
+  handleBulkArchive = async () => {
+    try {
+      await Promise.all(
+        this.props.selected.map(item => item.setArchived(true)),
+      );
+    } finally {
+      this.handleBulkActionSuccess();
+    }
+  };
+
+  handleBulkMoveStart = () => {
+    this.setState({ moveItems: this.props.selected });
+  };
+
+  handleBulkMove = async collection => {
+    try {
+      await Promise.all(
+        this.state.moveItems.map(item => item.setCollection(collection)),
+      );
+      this.setState({ moveItems: null });
+    } finally {
+      this.handleBulkActionSuccess();
+    }
+  };
+
+  handleBulkActionSuccess = () => {
+    // Clear the selection in listSelect
+    // Fixes an issue where things were staying selected when moving between
+    // different collection pages
+    this.props.onSelectNone();
+  };
+
   render() {
     const {
+      ancestors,
       collection,
       collectionId,
 
@@ -145,220 +167,333 @@ class DefaultLanding extends React.Component {
       pinned,
       unpinned,
 
+      isAdmin,
+      isRoot,
       selected,
       selection,
       onToggleSelected,
-      onSelectNone,
+      location,
     } = this.props;
     const { moveItems } = this.state;
 
-    // Call this when finishing a bulk action
-    const onBulkActionSuccess = () => {
-      // Clear the selection in listSelect
-      // Fixes an issue where things were staying selected when moving between
-      // different collection pages
-      onSelectNone();
-    };
+    const collectionWidth = unpinned.length > 0 ? [1, 1 / 3] : 1;
+    const itemWidth = unpinned.length > 0 ? [1, 2 / 3] : 0;
+    const collectionGridSize = unpinned.length > 0 ? 1 : [1, 1 / 4];
+
+    let unpinnedItems = unpinned;
 
-    // Show the
-    const showCollectionList =
-      collectionId === "root" || collections.length > 0;
+    if (location.query.type) {
+      unpinnedItems = unpinned.filter(u => u.model === location.query.type);
+    }
 
+    const collectionIsEmpty =
+      !unpinned.length > 0 && !collections.length > 0 && !pinned.length > 0;
+    const collectionHasPins = pinned.length > 0;
+    const collectionHasItems = unpinned.length > 0;
+
+    const showSidebar =
+      // if the user has write permissions or if there are collections then show the sidebar
+      (collection.can_write || collections.length > 0) &&
+      // there should also be at least one item, otherwise we have a different
+      // new collection CTA
+      !collectionIsEmpty;
     return (
-      <Flex>
-        {showCollectionList && (
-          <Box w={1 / 3} mr={3}>
+      <Box>
+        <Box>
+          <Flex
+            align="center"
+            pt={2}
+            pb={3}
+            px={4}
+            bg={pinned.length ? colors["bg-medium"] : null}
+          >
             <Box>
-              <h4>{t`Collections`}</h4>
+              <Box mb={1}>
+                <BrowserCrumbs
+                  analyticsContext={ANALYTICS_CONTEXT}
+                  crumbs={[
+                    ...ancestors.map(ancestor => ({
+                      title: (
+                        <CollectionDropTarget collection={ancestor} margin={8}>
+                          {ancestor.name}
+                        </CollectionDropTarget>
+                      ),
+                      to: Urls.collection(ancestor.id),
+                    })),
+                  ]}
+                />
+              </Box>
+              <Flex align="center">
+                <h1 style={{ fontWeight: 900 }}>{collection.name}</h1>
+                {collection.description && (
+                  <Tooltip tooltip={collection.description}>
+                    <Icon
+                      name="info"
+                      ml={1}
+                      mt="4px"
+                      color={colors["bg-dark"]}
+                      hover={{ color: colors["brand"] }}
+                    />
+                  </Tooltip>
+                )}
+              </Flex>
             </Box>
-            <CollectionList
-              collections={collections}
-              isRoot={collectionId === "root"}
-            />
-          </Box>
-        )}
-        <Box w={2 / 3}>
-          <Box>
-            {pinned.length === 0 && unpinned.length === 0 ? (
-              <CollectionEmptyState />
-            ) : (
-              <Box>
-                {pinned.length > 0 ? (
-                  <Box mb={2}>
-                    <Box mb={2}>
-                      <h4>{t`Pinned items`}</h4>
-                    </Box>
-                    <PinDropTarget
-                      pinIndex={
-                        pinned[pinned.length - 1].collection_position + 1
-                      }
-                      noDrop
-                      marginLeft={8}
-                      marginRight={8}
+
+            <Flex ml="auto">
+              {isAdmin &&
+                !collection.personal_owner_id && (
+                  <Tooltip
+                    tooltip={t`Edit the permissions for this collection`}
+                  >
+                    <Link
+                      to={Urls.collectionPermissions(this.props.collectionId)}
                     >
-                      <Grid>
-                        {pinned.map((item, index) => (
-                          <GridItem w={1 / 2} className="relative">
-                            <ItemDragSource item={item}>
-                              <PinnedItem
-                                key={`${item.type}:${item.id}`}
-                                index={index}
-                                item={item}
-                                collection={collection}
-                              />
-                              <PinPositionDropTarget
-                                pinIndex={item.collection_position}
-                                left
-                              />
-                              <PinPositionDropTarget
-                                pinIndex={item.collection_position + 1}
-                                right
-                              />
-                            </ItemDragSource>
-                          </GridItem>
-                        ))}
-                        {pinned.length % 2 === 1 ? (
-                          <GridItem w={1 / 2} className="relative">
-                            <PinPositionDropTarget
-                              pinIndex={
-                                pinned[pinned.length - 1].collection_position +
-                                1
-                              }
-                            />
-                          </GridItem>
-                        ) : null}
-                      </Grid>
-                    </PinDropTarget>
-                  </Box>
-                ) : (
-                  <PinDropTarget pinIndex={1} hideUntilDrag>
-                    {({ hovered }) => (
-                      <div
-                        className={cx(
-                          "p2 flex layout-centered",
-                          hovered ? "text-brand" : "text-grey-2",
-                        )}
-                      >
-                        <Icon name="pin" mr={1} />
-                        {t`Drag something here to pin it to the top`}
-                      </div>
-                    )}
-                  </PinDropTarget>
+                      <IconWrapper>
+                        <Icon name="lock" />
+                      </IconWrapper>
+                    </Link>
+                  </Tooltip>
                 )}
-                <Flex align="center" mb={2}>
-                  {pinned.length > 0 && (
-                    <Box>
-                      <h4>{t`Saved here`}</h4>
-                    </Box>
-                  )}
-                </Flex>
-                {unpinned.length > 0 ? (
-                  <PinDropTarget pinIndex={null} margin={8}>
-                    <Card
-                      mb={selected.length > 0 ? 5 : 2}
-                      style={{
-                        position: "relative",
-                        height: ROW_HEIGHT * unpinned.length,
-                      }}
-                    >
-                      <VirtualizedList
-                        items={unpinned}
-                        rowHeight={ROW_HEIGHT}
-                        renderItem={({ item, index }) => (
-                          <ItemDragSource item={item} selection={selection}>
-                            <NormalItem
+              {collection &&
+                collection.can_write &&
+                !collection.personal_owner_id && (
+                  <CollectionEditMenu
+                    collectionId={collectionId}
+                    isAdmin={isAdmin}
+                    isRoot={isRoot}
+                  />
+                )}
+              <Box ml={1}>
+                <CollectionBurgerMenu />
+              </Box>
+            </Flex>
+          </Flex>
+          <Box>
+            <Box>
+              {collectionHasPins ? (
+                <Box px={PAGE_PADDING} pt={2} pb={3} bg={colors["bg-medium"]}>
+                  <CollectionSectionHeading>{t`Pins`}</CollectionSectionHeading>
+                  <PinDropTarget
+                    pinIndex={pinned[pinned.length - 1].collection_position + 1}
+                    noDrop
+                    marginLeft={8}
+                    marginRight={8}
+                  >
+                    <Grid>
+                      {pinned.map((item, index) => (
+                        <GridItem
+                          w={[1, 1 / 3]}
+                          className="relative"
+                          key={index}
+                        >
+                          <ItemDragSource item={item} collection={collection}>
+                            <PinnedItem
                               key={`${item.type}:${item.id}`}
+                              index={index}
                               item={item}
                               collection={collection}
-                              selection={selection}
-                              onToggleSelected={onToggleSelected}
-                              onMove={moveItems => this.setState({ moveItems })}
+                            />
+                            <PinPositionDropTarget
+                              pinIndex={item.collection_position}
+                              left
+                            />
+                            <PinPositionDropTarget
+                              pinIndex={item.collection_position + 1}
+                              right
                             />
                           </ItemDragSource>
-                        )}
-                      />
-                    </Card>
+                        </GridItem>
+                      ))}
+                      {pinned.length % 2 === 1 ? (
+                        <GridItem w={1 / 4} className="relative">
+                          <PinPositionDropTarget
+                            pinIndex={
+                              pinned[pinned.length - 1].collection_position + 1
+                            }
+                          />
+                        </GridItem>
+                      ) : null}
+                    </Grid>
                   </PinDropTarget>
-                ) : (
+                </Box>
+              ) : (
+                <PinDropTarget pinIndex={1} hideUntilDrag>
+                  {({ hovered }) => (
+                    <div
+                      className={cx(
+                        "p2 flex layout-centered",
+                        hovered ? "text-brand" : "text-light",
+                      )}
+                    >
+                      <Icon name="pin" mr={1} />
+                      {t`Drag something here to pin it to the top`}
+                    </div>
+                  )}
+                </PinDropTarget>
+              )}
+              <Box pt={[1, 2]} px={[2, 4]}>
+                <Grid>
+                  {showSidebar && (
+                    <GridItem w={collectionWidth}>
+                      <Box pr={2} className="relative">
+                        <Box py={2}>
+                          <CollectionSectionHeading>
+                            {t`Collections`}
+                          </CollectionSectionHeading>
+                        </Box>
+                        <CollectionList
+                          analyticsContext={ANALYTICS_CONTEXT}
+                          currentCollection={collection}
+                          collections={collections}
+                          isRoot={collectionId === "root"}
+                          w={collectionGridSize}
+                        />
+                      </Box>
+                    </GridItem>
+                  )}
+                  {collectionHasItems && (
+                    <GridItem w={itemWidth}>
+                      <Box>
+                        <ItemTypeFilterBar
+                          analyticsContext={ANALYTICS_CONTEXT}
+                        />
+                        <Card mt={1} className="relative">
+                          {unpinnedItems.length > 0 ? (
+                            <PinDropTarget pinIndex={null} margin={8}>
+                              <Box
+                                style={{
+                                  position: "relative",
+                                  height: ROW_HEIGHT * unpinnedItems.length,
+                                }}
+                              >
+                                <VirtualizedList
+                                  items={unpinnedItems}
+                                  rowHeight={ROW_HEIGHT}
+                                  renderItem={({ item, index }) => (
+                                    <Box className="relative">
+                                      <ItemDragSource
+                                        item={item}
+                                        selection={selection}
+                                        collection={collection}
+                                      >
+                                        <NormalItem
+                                          key={`${item.type}:${item.id}`}
+                                          item={item}
+                                          collection={collection}
+                                          selection={selection}
+                                          onToggleSelected={onToggleSelected}
+                                          onMove={moveItems =>
+                                            this.setState({ moveItems })
+                                          }
+                                        />
+                                      </ItemDragSource>
+                                    </Box>
+                                  )}
+                                />
+                              </Box>
+                            </PinDropTarget>
+                          ) : (
+                            <Box>
+                              {location.query.type &&
+                                EMPTY_STATES[location.query.type]}
+                              <PinDropTarget
+                                pinIndex={null}
+                                hideUntilDrag
+                                margin={10}
+                              >
+                                {({ hovered }) => (
+                                  <div
+                                    className={cx(
+                                      "m2 flex layout-centered",
+                                      hovered ? "text-brand" : "text-light",
+                                    )}
+                                  >
+                                    {t`Drag here to un-pin`}
+                                  </div>
+                                )}
+                              </PinDropTarget>
+                            </Box>
+                          )}
+                        </Card>
+                      </Box>
+                    </GridItem>
+                  )}
+                </Grid>
+                {unpinned.length === 0 && (
                   <PinDropTarget pinIndex={null} hideUntilDrag margin={10}>
                     {({ hovered }) => (
-                      <div
-                        className={cx(
-                          "m2 flex layout-centered",
-                          hovered ? "text-brand" : "text-grey-2",
-                        )}
+                      <Flex
+                        align="center"
+                        justify="center"
+                        py={2}
+                        m={2}
+                        color={
+                          hovered ? colors["brand"] : colors["text-medium"]
+                        }
                       >
                         {t`Drag here to un-pin`}
-                      </div>
+                      </Flex>
                     )}
                   </PinDropTarget>
                 )}
+
+                {collectionIsEmpty && (
+                  <Flex align="center" justify="center" w={1}>
+                    <CollectionEmptyState />
+                  </Flex>
+                )}
               </Box>
-            )}
+            </Box>
             <BulkActionBar showing={selected.length > 0}>
-              <Flex align="center" w="100%">
-                {showCollectionList && (
-                  <Box w={1 / 3}>
-                    <span className="hidden">spacer</span>
-                  </Box>
-                )}
-                <Flex w={2 / 3} mx={showCollectionList ? 3 : 0} align="center">
-                  <Box ml={showCollectionList ? 3 : 2}>
-                    <SelectionControls {...this.props} />
-                  </Box>
-                  <BulkActionControls
-                    onArchive={
-                      _.all(selected, item => item.setArchived)
-                        ? async () => {
-                            try {
-                              await Promise.all(
-                                selected.map(item => item.setArchived(true)),
-                              );
-                            } finally {
-                              onBulkActionSuccess();
-                            }
-                          }
-                        : null
-                    }
-                    onMove={
-                      _.all(selected, item => item.setCollection)
-                        ? () => {
-                            this.setState({ moveItems: selected });
-                          }
-                        : null
-                    }
-                  />
-                  <Box ml="auto">{t`${selected.length} items selected`}</Box>
-                </Flex>
-              </Flex>
+              {/* NOTE: these padding and grid sizes must be carefully matched
+                   to the main content above to ensure the bulk checkbox lines up */}
+              <Box px={[2, 4]} py={1}>
+                <Grid>
+                  <GridItem w={collectionWidth} />
+                  <GridItem w={itemWidth} px={[1, 2]}>
+                    <Flex align="center" justify="center" px={2}>
+                      <SelectionControls {...this.props} />
+                      <BulkActionControls
+                        onArchive={
+                          _.all(selected, item => item.setArchived)
+                            ? this.handleBulkArchive
+                            : null
+                        }
+                        onMove={
+                          _.all(selected, item => item.setCollection)
+                            ? this.handleBulkMoveStart
+                            : null
+                        }
+                      />
+                      <Box ml="auto">
+                        {ngettext(
+                          msgid`${selected.length} item selected`,
+                          `${selected.length} items selected`,
+                          selected.length,
+                        )}
+                      </Box>
+                    </Flex>
+                  </GridItem>
+                </Grid>
+              </Box>
             </BulkActionBar>
           </Box>
         </Box>
-        {moveItems &&
-          moveItems.length > 0 && (
-            <Modal>
-              <CollectionMoveModal
-                title={
-                  moveItems.length > 1
-                    ? t`Move ${moveItems.length} items?`
-                    : `Move "${moveItems[0].getName()}"?`
-                }
-                onClose={() => this.setState({ moveItems: null })}
-                onMove={async collection => {
-                  try {
-                    await Promise.all(
-                      moveItems.map(item => item.setCollection(collection)),
-                    );
-                    this.setState({ moveItems: null });
-                  } finally {
-                    onBulkActionSuccess();
-                  }
-                }}
-              />
-            </Modal>
-          )}
+        {!_.isEmpty(moveItems) && (
+          <Modal>
+            <CollectionMoveModal
+              title={
+                moveItems.length > 1
+                  ? t`Move ${moveItems.length} items?`
+                  : t`Move "${moveItems[0].getName()}"?`
+              }
+              onClose={() => this.setState({ moveItems: null })}
+              onMove={this.handleBulkMove}
+            />
+          </Modal>
+        )}
         <ItemsDragLayer selected={selected} />
-      </Flex>
+      </Box>
     );
   }
 }
@@ -370,8 +505,13 @@ export const NormalItem = ({
   onToggleSelected,
   onMove,
 }) => (
-  <Link to={item.getUrl()}>
+  <Link
+    to={item.getUrl()}
+    data-metabase-event={`${ANALYTICS_CONTEXT};Item Click;${item.model}`}
+  >
     <EntityItem
+      analyticsContext={ANALYTICS_CONTEXT}
+      variant="list"
       showSelect={selection.size > 0}
       selectable
       item={item}
@@ -409,6 +549,7 @@ const PinnedItem = ({ item, index, collection }) => (
     to={item.getUrl()}
     className="hover-parent hover--visibility"
     hover={{ color: normal.blue }}
+    data-metabase-event={`${ANALYTICS_CONTEXT};Pinned Item;Click;${item.model}`}
   >
     <Card hoverable p={3}>
       <Icon name={item.getIcon()} color={item.getColor()} size={28} mb={2} />
@@ -419,6 +560,9 @@ const PinnedItem = ({ item, index, collection }) => (
             <Box
               ml="auto"
               className="hover-child"
+              data-metabase-event={`${ANALYTICS_CONTEXT};Pinned Item;Unpin;${
+                item.model
+              }`}
               onClick={ev => {
                 ev.preventDefault();
                 item.setPinned(false);
@@ -439,8 +583,15 @@ const BulkActionControls = ({ onArchive, onMove }) => (
       medium
       disabled={!onArchive}
       onClick={onArchive}
+      data-metabase-event={`${ANALYTICS_CONTEXT};Bulk Actions;Archive Items`}
     >{t`Archive`}</Button>
-    <Button ml={1} medium disabled={!onMove} onClick={onMove}>{t`Move`}</Button>
+    <Button
+      ml={1}
+      medium
+      disabled={!onMove}
+      onClick={onMove}
+      data-metabase-event={`${ANALYTICS_CONTEXT};Bulk Actions;Move Items`}
+    >{t`Move`}</Button>
   </Box>
 );
 
@@ -449,139 +600,77 @@ const SelectionControls = ({
   deselected,
   onSelectAll,
   onSelectNone,
+  size = 18,
 }) =>
   deselected.length === 0 ? (
-    <StackedCheckBox checked onChange={onSelectNone} />
+    <StackedCheckBox checked onChange={onSelectNone} size={size} />
   ) : selected.length === 0 ? (
-    <StackedCheckBox onChange={onSelectAll} />
+    <StackedCheckBox onChange={onSelectAll} size={size} />
   ) : (
-    <StackedCheckBox checked indeterminate onChange={onSelectAll} />
+    <StackedCheckBox checked indeterminate onChange={onSelectAll} size={size} />
   );
 
 @entityObjectLoader({
   entityType: "collections",
   entityId: (state, props) => props.params.collectionId,
+  reload: true,
 })
 class CollectionLanding extends React.Component {
   render() {
     const { object: currentCollection, params: { collectionId } } = this.props;
     const isRoot = collectionId === "root";
 
-    // effective_ancestors doesn't include root collection so add it (unless this is the root collection, of course)
     const ancestors =
-      !isRoot && currentCollection && currentCollection.effective_ancestors
-        ? [ROOT_COLLECTION, ...currentCollection.effective_ancestors]
-        : [];
+      (currentCollection && currentCollection.effective_ancestors) || [];
 
     return (
-      <Box mx={4}>
-        <Box>
-          <Flex align="center">
-            <BrowserCrumbs
-              crumbs={[
-                ...ancestors.map(({ id, name }) => ({
-                  title: (
-                    <CollectionDropTarget collection={{ id }} margin={8}>
-                      {name}
-                    </CollectionDropTarget>
-                  ),
-                  to: Urls.collection(id),
-                })),
-                { title: currentCollection.name },
-              ]}
-            />
-
-            <Flex ml="auto">
-              {currentCollection &&
-                currentCollection.can_write && (
-                  <Box ml={1}>
-                    <NewObjectMenu collectionId={collectionId} />
-                  </Box>
-                )}
-              {currentCollection &&
-                currentCollection.can_write &&
-                !currentCollection.personal_owner_id && (
-                  <Box ml={1}>
-                    <CollectionEditMenu
-                      collectionId={collectionId}
-                      isRoot={isRoot}
-                    />
-                  </Box>
-                )}
-              <Box ml={1}>
-                <CollectionBurgerMenu />
-              </Box>
-            </Flex>
-          </Flex>
-        </Box>
-        <Box>
-          <DefaultLanding
-            collection={currentCollection}
-            collectionId={collectionId}
-          />
-          {
-            // Need to have this here so the child modals will show up
-            this.props.children
-          }
-        </Box>
+      <Box>
+        <DefaultLanding
+          isRoot={isRoot}
+          ancestors={ancestors}
+          collection={currentCollection}
+          collectionId={collectionId}
+        />
+        {
+          // Need to have this here so the child modals will show up
+          this.props.children
+        }
       </Box>
     );
   }
 }
 
-const NewObjectMenu = ({ collectionId }) => (
-  <EntityMenu
-    items={[
-      {
-        title: t`New dashboard`,
-        icon: "dashboard",
-        link: Urls.newDashboard(collectionId),
-      },
-      {
-        title: t`New pulse`,
-        icon: "pulse",
-        link: Urls.newPulse(collectionId),
-      },
-      {
-        title: t`New collection`,
-        icon: "all",
-        link: Urls.newCollection(collectionId),
-      },
-    ]}
-    triggerIcon="add"
-  />
+const CollectionSectionHeading = ({ children }) => (
+  <h5
+    className="text-uppercase"
+    style={{ color: colors["text-medium"], fontWeight: 900 }}
+  >
+    {children}
+  </h5>
 );
 
-const CollectionEditMenu = ({ isRoot, collectionId }) => (
-  <EntityMenu
-    items={[
-      ...(!isRoot
-        ? [
-            {
-              title: t`Edit this collection`,
-              icon: "editdocument",
-              link: `/collections/${collectionId}`,
-            },
-          ]
-        : []),
-      {
-        title: t`Edit permissions`,
-        icon: "lock",
-        link: `/collection/${collectionId}/permissions`,
-      },
-      ...(!isRoot
-        ? [
-            {
-              title: t`Archive this collection`,
-              icon: "viewArchive",
-              link: `/collection/${collectionId}/archive`,
-            },
-          ]
-        : []),
-    ]}
-    triggerIcon="pencil"
-  />
-);
+const CollectionEditMenu = ({ isRoot, isAdmin, collectionId }) => {
+  const items = [];
+  if (!isRoot) {
+    items.push({
+      title: t`Edit this collection`,
+      icon: "editdocument",
+      link: `/collection/${collectionId}/edit`,
+      event: `${ANALYTICS_CONTEXT};Edit Menu;Edit Collection Click`,
+    });
+  }
+  if (!isRoot) {
+    items.push({
+      title: t`Archive this collection`,
+      icon: "viewArchive",
+      link: `/collection/${collectionId}/archive`,
+      event: `${ANALYTICS_CONTEXT};Edit Menu;Archive Collection`,
+    });
+  }
+  return items.length > 0 ? (
+    <EntityMenu items={items} triggerIcon="pencil" />
+  ) : null;
+};
 
 const CollectionBurgerMenu = () => (
   <EntityMenu
@@ -590,6 +679,7 @@ const CollectionBurgerMenu = () => (
         title: t`View the archive`,
         icon: "viewArchive",
         link: `/archive`,
+        event: `${ANALYTICS_CONTEXT};Burger Menu;View Archive Click`,
       },
     ]}
     triggerIcon="burger"
diff --git a/frontend/src/metabase/components/CollectionList.jsx b/frontend/src/metabase/components/CollectionList.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..e2f80768b5d477b3c3e214c9cecb8d4158f63c33
--- /dev/null
+++ b/frontend/src/metabase/components/CollectionList.jsx
@@ -0,0 +1,122 @@
+import React from "react";
+import { t } from "c-3po";
+import { Box, Flex } from "grid-styled";
+import { connect } from "react-redux";
+
+import * as Urls from "metabase/lib/urls";
+
+import CollectionItem from "metabase/components/CollectionItem";
+import { normal } from "metabase/lib/colors";
+import { Grid, GridItem } from "metabase/components/Grid";
+import Icon from "metabase/components/Icon";
+import Link from "metabase/components/Link";
+
+import CollectionDropTarget from "metabase/containers/dnd/CollectionDropTarget";
+import ItemDragSource from "metabase/containers/dnd/ItemDragSource";
+
+import { PERSONAL_COLLECTIONS } from "metabase/entities/collections";
+
+@connect(({ currentUser }) => ({ currentUser }), null)
+class CollectionList extends React.Component {
+  render() {
+    const {
+      analyticsContext,
+      collections,
+      currentUser,
+      currentCollection,
+      isRoot,
+      w,
+      asCards,
+    } = this.props;
+    return (
+      <Box className="relative">
+        <Grid>
+          {collections
+            .filter(c => c.id !== currentUser.personal_collection_id)
+            .map(collection => (
+              <GridItem w={w} key={collection.id}>
+                <CollectionDropTarget collection={collection}>
+                  {({ highlighted, hovered }) => (
+                    <ItemDragSource
+                      item={collection}
+                      collection={currentCollection}
+                    >
+                      <CollectionItem
+                        collection={collection}
+                        highlighted={highlighted}
+                        hovered={hovered}
+                        event={`${analyticsContext};Collection List;Collection click`}
+                        asCard={asCards}
+                      />
+                    </ItemDragSource>
+                  )}
+                </CollectionDropTarget>
+              </GridItem>
+            ))}
+          {isRoot && (
+            <GridItem w={w} className="relative">
+              <CollectionDropTarget
+                collection={{ id: currentUser.personal_collection_id }}
+              >
+                {({ highlighted, hovered }) => (
+                  <CollectionItem
+                    collection={{
+                      name: t`My personal collection`,
+                      id: currentUser.personal_collection_id,
+                    }}
+                    iconName="star"
+                    highlighted={highlighted}
+                    hovered={hovered}
+                    event={`${analyticsContext};Collection List;Personal collection click`}
+                    asCard={asCards}
+                  />
+                )}
+              </CollectionDropTarget>
+            </GridItem>
+          )}
+          {isRoot &&
+            currentUser.is_superuser && (
+              <GridItem w={w}>
+                <CollectionItem
+                  collection={{
+                    name: PERSONAL_COLLECTIONS.name,
+                    // Bit of a hack. The route /collection/users lists
+                    // user collections but is not itself a colllection,
+                    // but using the fake id users here works
+                    id: "users",
+                  }}
+                  iconName="person"
+                  event={`${analyticsContext};Collection List;All user collecetions click`}
+                  asCard={asCards}
+                />
+              </GridItem>
+            )}
+          {currentCollection &&
+            currentCollection.can_write && (
+              <GridItem w={w}>
+                <Link
+                  to={Urls.newCollection(currentCollection.id)}
+                  color={normal.grey2}
+                  hover={{ color: normal.blue }}
+                  p={w === 1 ? [1, 2] : 0}
+                  data-metabase-event={`${analyticsContext};Collection List; New Collection Click`}
+                >
+                  <Flex align="center" py={1}>
+                    <Icon name="add" mr={1} bordered />
+                    <h4>{t`New collection`}</h4>
+                  </Flex>
+                </Link>
+              </GridItem>
+            )}
+        </Grid>
+      </Box>
+    );
+  }
+}
+
+CollectionList.defaultProps = {
+  w: [1, 1 / 2, 1 / 4],
+  asCards: false,
+};
+
+export default CollectionList;
diff --git a/frontend/src/metabase/components/ColorPicker.jsx b/frontend/src/metabase/components/ColorPicker.jsx
index 28676d41ebccc3764ec86ca717e1440f055d6f18..5b9d89845229ad8859099bfdbd34597c1acf27b6 100644
--- a/frontend/src/metabase/components/ColorPicker.jsx
+++ b/frontend/src/metabase/components/ColorPicker.jsx
@@ -20,7 +20,6 @@ const ColorSquare = ({ color, size }) => (
 
 class ColorPicker extends Component {
   static defaultProps = {
-    colors: [...Object.values(normal)],
     size: DEFAULT_COLOR_SQUARE_SIZE,
     triggerSize: DEFAULT_COLOR_SQUARE_SIZE,
     padding: 4,
@@ -35,7 +34,8 @@ class ColorPicker extends Component {
   };
 
   render() {
-    const { colors, onChange, padding, size, triggerSize, value } = this.props;
+    const { onChange, padding, size, triggerSize, value } = this.props;
+    const colors = this.props.colors || Object.values(normal).slice(0, 9);
     return (
       <div className="inline-block">
         <PopoverWithTrigger
diff --git a/frontend/src/metabase/components/ColumnarSelector.css b/frontend/src/metabase/components/ColumnarSelector.css
index 26033e2b7376a9e6635456b524d84f610d0fffdf..fb4a782fc28a4bf599c0ad00997fa216463ed3f9 100644
--- a/frontend/src/metabase/components/ColumnarSelector.css
+++ b/frontend/src/metabase/components/ColumnarSelector.css
@@ -1,6 +1,6 @@
 .ColumnarSelector {
   display: flex;
-  background-color: #fcfcfc;
+  background-color: var(--color-bg-white);
   font-weight: 700;
 }
 
@@ -16,7 +16,7 @@
 }
 
 .ColumnarSelector-title {
-  color: color(var(--base-grey) shade(30%));
+  color: var(--color-text-medium);
   text-transform: uppercase;
   font-size: 10px;
   font-weight: 700;
@@ -30,7 +30,7 @@
 
 .ColumnarSelector-description {
   margin-top: 0.5em;
-  color: color(var(--base-grey) shade(30%));
+  color: var(--color-text-medium);
   max-width: 270px;
 }
 
@@ -45,24 +45,24 @@
 
 .ColumnarSelector-row:not(.ColumnarSelector-row--disabled):hover,
 .ColumnarSelector-row:not(.ColumnarSelector-row--disabled):hover .Icon {
-  background-color: var(--brand-color) !important;
+  background-color: var(--color-brand) !important;
   color: white !important;
 }
 
 .ColumnarSelector-row:not(.ColumnarSelector-row--disabled):hover
   .ColumnarSelector-description {
-  color: rgba(255, 255, 255, 0.5);
+  color: color(var(--color-text-white) alpha(-50%));
 }
 
 .ColumnarSelector-row--selected {
   color: inherit !important;
   background: white;
-  border-top: var(--border-size) var(--border-style) var(--border-color);
-  border-bottom: var(--border-size) var(--border-style) var(--border-color);
+  border-top: var(--border-size) var(--border-style) var(--color-border);
+  border-bottom: var(--border-size) var(--border-style) var(--color-border);
 }
 
 .ColumnarSelector-row--disabled {
-  color: var(--grey-3);
+  color: var(--color-text-medium);
 }
 
 .ColumnarSelector-row .Icon-check {
@@ -81,7 +81,7 @@
 /* only apply if there's more than one, aka the last is not the first */
 .ColumnarSelector-column:last-child:not(:first-child) {
   background-color: white;
-  border-left: var(--border-size) var(--border-style) var(--border-color);
+  border-left: var(--border-size) var(--border-style) var(--color-border);
   position: relative;
   left: -1px;
 }
@@ -90,5 +90,5 @@
   background: inherit;
   border-top: none;
   border-bottom: none;
-  color: var(--brand-color);
+  color: var(--color-brand);
 }
diff --git a/frontend/src/metabase/components/CopyWidget.jsx b/frontend/src/metabase/components/CopyWidget.jsx
index 88b15c1654a7e822fee24ca44e4ec6938dc672eb..3e8921d4c192e92fa0123fe68c753df8812643a4 100644
--- a/frontend/src/metabase/components/CopyWidget.jsx
+++ b/frontend/src/metabase/components/CopyWidget.jsx
@@ -16,7 +16,7 @@ export default class CopyWidget extends Component {
     return (
       <div className="flex">
         <input
-          className="flex-full p1 flex align-center text-grey-4 text-bold no-focus border-top border-left border-bottom border-med rounded-left"
+          className="flex-full p1 flex align-center text-medium text-bold no-focus border-top border-left border-bottom border-med rounded-left"
           style={{ borderRight: "none" }}
           type="text"
           value={value}
diff --git a/frontend/src/metabase/components/CreateDashboardModal.jsx b/frontend/src/metabase/components/CreateDashboardModal.jsx
index 303ab0ac32de3731f4da0c01b66211ca788ac2a4..ae1c4333bceaef63873f18f278864152ba610890 100644
--- a/frontend/src/metabase/components/CreateDashboardModal.jsx
+++ b/frontend/src/metabase/components/CreateDashboardModal.jsx
@@ -1,149 +1,45 @@
 import React, { Component } from "react";
 import PropTypes from "prop-types";
-import { t } from "c-3po";
 import { connect } from "react-redux";
 import { withRouter } from "react-router";
+import { push } from "react-router-redux";
 
-import FormField from "metabase/components/form/FormField.jsx";
-import ModalContent from "metabase/components/ModalContent.jsx";
+import * as Urls from "metabase/lib/urls";
 
-import Button from "metabase/components/Button.jsx";
-import CollectionSelect from "metabase/containers/CollectionSelect.jsx";
+import Collections from "metabase/entities/collections";
+import DashboardForm from "metabase/containers/DashboardForm";
 
-import Dashboards from "metabase/entities/dashboards";
+const mapStateToProps = (state, props) => ({
+  initialCollectionId: Collections.selectors.getInitialCollectionId(
+    state,
+    props,
+  ),
+});
 
 const mapDispatchToProps = {
-  createDashboard: Dashboards.actions.create,
+  onChangeLocation: push,
 };
 
-@connect(null, mapDispatchToProps)
 @withRouter
+@connect(mapStateToProps, mapDispatchToProps)
 export default class CreateDashboardModal extends Component {
-  constructor(props, context) {
-    super(props, context);
-    this.createNewDash = this.createNewDash.bind(this);
-    this.setDescription = this.setDescription.bind(this);
-    this.setName = this.setName.bind(this);
-
-    console.log(props.params);
-    this.state = {
-      name: null,
-      description: null,
-      errors: null,
-      // collectionId in the url starts off as a string, but the select will
-      // compare it to the integer ID on colleciton objects
-      collection_id: parseInt(props.params.collectionId),
-    };
-  }
-
   static propTypes = {
-    createDashboard: PropTypes.func.isRequired,
     onClose: PropTypes.func,
   };
 
-  setName(event) {
-    this.setState({ name: event.target.value });
-  }
-
-  setDescription(event) {
-    this.setState({ description: event.target.value });
-  }
-
-  createNewDash(event) {
-    event.preventDefault();
-
-    let name = this.state.name && this.state.name.trim();
-    let description = this.state.description && this.state.description.trim();
-
-    // populate a new Dash object
-    let newDash = {
-      name: name && name.length > 0 ? name : null,
-      description: description && description.length > 0 ? description : null,
-      collection_id: this.state.collection_id,
-    };
-
-    this.props.createDashboard(newDash, { redirect: true });
-    this.props.onClose();
-  }
-
   render() {
-    let formError;
-    if (this.state.errors) {
-      let errorMessage = t`Server error encountered`;
-      if (this.state.errors.data && this.state.errors.data.message) {
-        errorMessage = this.state.errors.data.message;
-      }
-
-      // TODO: timeout display?
-      formError = <span className="text-error px2">{errorMessage}</span>;
-    }
-
-    let name = this.state.name && this.state.name.trim();
-
-    let formReady = name !== null && name !== "";
-
+    const { initialCollectionId, onClose, onChangeLocation } = this.props;
     return (
-      <ModalContent
-        id="CreateDashboardModal"
-        title={t`Create dashboard`}
-        footer={[
-          formError,
-          <Button
-            mr={1}
-            onClick={() => this.props.onClose()}
-          >{t`Cancel`}</Button>,
-          <Button
-            primary={formReady}
-            disabled={!formReady}
-            onClick={this.createNewDash}
-          >{t`Create`}</Button>,
-        ]}
-        onClose={this.props.onClose}
-      >
-        <form className="Modal-form" onSubmit={this.createNewDash}>
-          <div>
-            <FormField
-              name="name"
-              displayName={t`Name`}
-              formError={this.state.errors}
-            >
-              <input
-                className="Form-input full"
-                name="name"
-                placeholder={t`What is the name of your dashboard?`}
-                value={this.state.name}
-                onChange={this.setName}
-                autoFocus
-              />
-            </FormField>
-
-            <FormField
-              name="description"
-              displayName={t`Description`}
-              formError={this.state.errors}
-            >
-              <input
-                className="Form-input full"
-                name="description"
-                placeholder={t`It's optional but oh, so helpful`}
-                value={this.state.description}
-                onChange={this.setDescription}
-              />
-            </FormField>
-
-            <FormField
-              displayName={t`Which collection should this go in?`}
-              fieldName="collection_id"
-              errors={this.state.errors}
-            >
-              <CollectionSelect
-                value={this.state.collection_id}
-                onChange={collection_id => this.setState({ collection_id })}
-              />
-            </FormField>
-          </div>
-        </form>
-      </ModalContent>
+      <DashboardForm
+        dashboard={{ collection_id: initialCollectionId }}
+        onClose={onClose}
+        onSaved={dashboard => {
+          onChangeLocation(Urls.dashboard(dashboard.id));
+          if (onClose) {
+            onClose();
+          }
+        }}
+      />
     );
   }
 }
diff --git a/frontend/src/metabase/components/DatabaseDetailsForm.jsx b/frontend/src/metabase/components/DatabaseDetailsForm.jsx
index ff989adccb33dc3dc126568fb96dcc6a0fec89aa..00a25753b4f54338a8eadac46a2438d30df7b2c2 100644
--- a/frontend/src/metabase/components/DatabaseDetailsForm.jsx
+++ b/frontend/src/metabase/components/DatabaseDetailsForm.jsx
@@ -226,6 +226,30 @@ export default class DatabaseDetailsForm extends Component {
     } else if (isTunnelField(field) && !this.state.details["tunnel-enabled"]) {
       // don't show tunnel fields if tunnel isn't enabled
       return null;
+    } else if (field.name === "use-jvm-timezone") {
+      let on =
+        this.state.details["use-jvm-timezone"] == undefined
+          ? false
+          : this.state.details["use-jvm-timezone"];
+      return (
+        <FormField key={field.name} fieldName={field.name}>
+          <div className="flex align-center Form-offset">
+            <div className="Grid-cell--top">
+              <Toggle
+                value={on}
+                onChange={val => this.onChange("use-jvm-timezone", val)}
+              />
+            </div>
+            <div className="px2">
+              <h3>{t`Use the Java Virtual Machine (JVM) timezone`}</h3>
+              <div style={{ maxWidth: "40rem" }} className="pt1">
+                {t`We suggest you leave this off unless you're doing manual timezone casting in
+                                many or most of your queries with this data.`}
+              </div>
+            </div>
+          </div>
+        </FormField>
+      );
     } else if (field.name === "let-user-control-scheduling") {
       let on =
         this.state.details["let-user-control-scheduling"] == undefined
diff --git a/frontend/src/metabase/components/DeleteModalWithConfirm.jsx b/frontend/src/metabase/components/DeleteModalWithConfirm.jsx
index 4d1d6b8be791747f2999360e9e9bb38efb7ed333..00ecf8ad7e41ce5cbb8eb11e61883989c0a2cb10 100644
--- a/frontend/src/metabase/components/DeleteModalWithConfirm.jsx
+++ b/frontend/src/metabase/components/DeleteModalWithConfirm.jsx
@@ -23,6 +23,7 @@ export default class DeleteModalWithConfirm extends Component {
     confirmItems: PropTypes.array.isRequired,
     onClose: PropTypes.func.isRequired,
     onDelete: PropTypes.func.isRequired,
+    buttonText: PropTypes.string,
   };
 
   async onDelete() {
@@ -31,7 +32,7 @@ export default class DeleteModalWithConfirm extends Component {
   }
 
   render() {
-    const { title, objectType, confirmItems } = this.props;
+    const { title, objectType, confirmItems, buttonText } = this.props;
     const { checked } = this.state;
     let confirmed = confirmItems.reduce(
       (acc, item, index) => acc && checked[index],
@@ -76,7 +77,7 @@ export default class DeleteModalWithConfirm extends Component {
             })}
             onClick={this.onDelete}
           >
-            {t`Delete this ${objectType}`}
+            {buttonText || t`Delete this ${objectType}`}
           </button>
         </div>
       </ModalContent>
diff --git a/frontend/src/metabase/components/DirectionalButton.jsx b/frontend/src/metabase/components/DirectionalButton.jsx
index e17dbb4ad9618c61e2228bcab53ff906c39ad1b6..ad18f6df6d67f55458e254fb33f3848cc63e5ef0 100644
--- a/frontend/src/metabase/components/DirectionalButton.jsx
+++ b/frontend/src/metabase/components/DirectionalButton.jsx
@@ -1,13 +1,15 @@
 import React from "react";
 import Icon from "metabase/components/Icon";
 
+import colors from "metabase/lib/colors";
+
 const DirectionalButton = ({ direction = "back", onClick }) => (
   <div
-    className="shadowed cursor-pointer text-brand-hover text-grey-4 flex align-center circle p2 bg-white transition-background transition-color"
+    className="shadowed cursor-pointer text-brand-hover text-medium flex align-center circle p2 bg-white transition-background transition-color"
     onClick={onClick}
     style={{
-      border: "1px solid #DCE1E4",
-      boxShadow: "0 2px 4px 0 #DCE1E4",
+      border: `1px solid ${colors["border"]}`,
+      boxShadow: `0 2px 4px 0 ${colors["shadow"]}`,
     }}
   >
     <Icon name={`${direction}Arrow`} />
diff --git a/frontend/src/metabase/components/DownloadButton.jsx b/frontend/src/metabase/components/DownloadButton.jsx
index a3b8278b3aeb2504903a4064082a5cb924b0cb6a..54ce9421e0ae5d9aa8e05c8f82a41490fb3e84b6 100644
--- a/frontend/src/metabase/components/DownloadButton.jsx
+++ b/frontend/src/metabase/components/DownloadButton.jsx
@@ -1,11 +1,27 @@
 import React from "react";
 import PropTypes from "prop-types";
+import { Box, Flex } from "grid-styled";
 
-import Button from "metabase/components/Button.jsx";
+import colors from "metabase/lib/colors";
+import { extractQueryParams } from "metabase/lib/urls";
+
+import Icon from "metabase/components/Icon";
+import Text from "metabase/components/Text";
+
+function colorForType(type) {
+  switch (type) {
+    case "csv":
+      return colors["accent7"];
+    case "xlsx":
+      return colors["accent1"];
+    case "json":
+      return colors["bg-dark"];
+    default:
+      return colors["brand"];
+  }
+}
 
 const DownloadButton = ({
-  className,
-  style,
   children,
   method,
   url,
@@ -13,39 +29,43 @@ const DownloadButton = ({
   extensions,
   ...props
 }) => (
-  <form className={className} style={style} method={method} action={url}>
-    {params &&
-      Object.entries(params).map(([name, value]) => (
-        <input key={name} type="hidden" name={name} value={value} />
-      ))}
-    <Button
-      onClick={e => {
-        if (window.OSX) {
-          // prevent form from being submitted normally
-          e.preventDefault();
-          // download using the API provided by the OS X app
-          window.OSX.download(method, url, params, extensions);
-        }
-      }}
-      {...props}
-    >
-      {children}
-    </Button>
-  </form>
+  <Box>
+    <form method={method} action={url}>
+      {params && extractQueryParams(params).map(getInput)}
+      <Flex
+        is="button"
+        className="text-white-hover bg-brand-hover rounded cursor-pointer full hover-parent hover--inherit"
+        align="center"
+        px={1}
+        onClick={e => {
+          if (window.OSX) {
+            // prevent form from being submitted normally
+            e.preventDefault();
+            // download using the API provided by the OS X app
+            window.OSX.download(method, url, params, extensions);
+          }
+        }}
+        {...props}
+      >
+        <Icon name={children} size={32} mr={1} color={colorForType(children)} />
+        <Text className="text-bold">.{children}</Text>
+      </Flex>
+    </form>
+  </Box>
+);
+
+const getInput = ([name, value]) => (
+  <input type="hidden" name={name} value={value} />
 );
 
 DownloadButton.propTypes = {
-  className: PropTypes.string,
-  style: PropTypes.object,
   url: PropTypes.string.isRequired,
   method: PropTypes.string,
   params: PropTypes.object,
-  icon: PropTypes.string,
   extensions: PropTypes.array,
 };
 
 DownloadButton.defaultProps = {
-  icon: "downarrow",
   method: "POST",
   params: {},
   extensions: [],
diff --git a/frontend/src/metabase/components/EmptyState.jsx b/frontend/src/metabase/components/EmptyState.jsx
index 672e550968dec1b69bea415af5ccce6c74b6b4ff..d8f83b8dc0eba04446742091b1c98718522bb153 100644
--- a/frontend/src/metabase/components/EmptyState.jsx
+++ b/frontend/src/metabase/components/EmptyState.jsx
@@ -1,83 +1,76 @@
 /* @flow */
 import React from "react";
-import { Link } from "react-router";
-import cx from "classnames";
-/*
- * EmptyState is a component that can
- *  1) introduce a new section of Metabase to a user and encourage the user to take an action
- *  2) indicate an empty result after a user-triggered search/query
- */
+import { Box, Flex } from "grid-styled";
 
-import Icon from "metabase/components/Icon.jsx";
+import Button from "metabase/components/Button";
+import Icon from "metabase/components/Icon";
+import Link from "metabase/components/Link";
+import Text from "metabase/components/Text";
 
 type EmptyStateProps = {
-  message: string | React$Element<any>,
+  message: React$Element<any>,
   title?: string,
-  icon?: string,
-  image?: string,
-  imageHeight?: string, // for reducing ui flickering when the image is loading
-  imageClassName?: string,
   action?: string,
   link?: string,
+  illustrationElement: React$Element<any>,
   onActionClick?: () => void,
-  smallDescription?: boolean,
 };
 
+// Don't break existing empty states
+// TODO - remove these and update empty states with proper usage of illustrationElement
+const LegacyIcon = props =>
+  props.icon ? <Icon name={props.icon} size={40} /> : null;
+const LegacyImage = props =>
+  props.image ? (
+    <img
+      src={`${props.image}.png`}
+      width="300px"
+      height={props.imageHeight}
+      alt={props.message}
+      srcSet={`${props.image}@2x.png 2x`}
+      className={props.imageClassName}
+    />
+  ) : null;
+
 const EmptyState = ({
   title,
   message,
-  icon,
-  image,
-  imageHeight,
-  imageClassName,
   action,
   link,
+  illustrationElement,
   onActionClick,
-  smallDescription = false,
+  ...rest
 }: EmptyStateProps) => (
-  <div
-    className="text-centered text-brand-light my2"
-    style={smallDescription ? {} : { width: "350px" }}
-  >
-    {title && <h2 className="text-brand mb4">{title}</h2>}
-    {icon && <Icon name={icon} size={40} />}
-    {image && (
-      <img
-        src={`${image}.png`}
-        width="300px"
-        height={imageHeight}
-        alt={message}
-        srcSet={`${image}@2x.png 2x`}
-        className={imageClassName}
-      />
-    )}
-    <div className="flex justify-center">
-      <h2
-        className={cx("text-grey-2 text-normal mt2 mb4", {
-          "text-paragraph": smallDescription,
-        })}
-        style={{ lineHeight: "1.5em" }}
-      >
-        {message}
-      </h2>
-    </div>
-    {action &&
-      link && (
-        <Link
-          to={link}
-          className="Button Button--primary mt4"
-          target={link.startsWith("http") ? "_blank" : ""}
-        >
-          {action}
-        </Link>
-      )}
-    {action &&
-      onActionClick && (
-        <a onClick={onActionClick} className="Button Button--primary mt4">
-          {action}
-        </a>
-      )}
-  </div>
+  <Box>
+    <Flex justify="center" flexDirection="column" align="center">
+      {illustrationElement && <Box mb={[2, 3]}>{illustrationElement}</Box>}
+      <Box>
+        <LegacyIcon {...rest} />
+        <LegacyImage {...rest} />
+      </Box>
+      {title && <h2>{title}</h2>}
+      {message && <Text color="medium">{message}</Text>}
+    </Flex>
+    {/* TODO - we should make this children or some other more flexible way to
+      add actions
+      */}
+    <Flex mt={2}>
+      <Flex align="center" ml="auto" mr="auto">
+        {action &&
+          link && (
+            <Link to={link} target={link.startsWith("http") ? "_blank" : ""}>
+              <Button primary>{action}</Button>
+            </Link>
+          )}
+        {action &&
+          onActionClick && (
+            <Button onClick={onActionClick} primary>
+              {action}
+            </Button>
+          )}
+      </Flex>
+    </Flex>
+  </Box>
 );
 
 export default EmptyState;
diff --git a/frontend/src/metabase/components/EntityItem.jsx b/frontend/src/metabase/components/EntityItem.jsx
index ec0171b4c2a0f85e245cf37b8d1eedb17b15fdfa..b17278d09ff181883d2797a4484096d93bacf9ce 100644
--- a/frontend/src/metabase/components/EntityItem.jsx
+++ b/frontend/src/metabase/components/EntityItem.jsx
@@ -1,7 +1,8 @@
 import React from "react";
 import { t } from "c-3po";
-
+import cx from "classnames";
 import { Flex } from "grid-styled";
+
 import EntityMenu from "metabase/components/EntityMenu";
 import Swapper from "metabase/components/Swapper";
 import IconWrapper from "metabase/components/IconWrapper";
@@ -9,21 +10,19 @@ import CheckBox from "metabase/components/CheckBox";
 import Ellipsified from "metabase/components/Ellipsified";
 import Icon from "metabase/components/Icon";
 
-import { normal } from "metabase/lib/colors";
+import colors from "metabase/lib/colors";
 
 const EntityItemWrapper = Flex.extend`
-  border-bottom: 1px solid #f8f9fa;
+  border-bottom: 1px solid ${colors["bg-medium"]};
   /* TODO - figure out how to use the prop instead of this? */
   align-items: center;
   &:hover {
-    color: ${normal.blue};
-  }
-  &:last-child {
-    border-bottom: none;
+    color: ${colors["brand"]};
   }
 `;
 
 const EntityItem = ({
+  analyticsContext,
   name,
   iconName,
   iconColor,
@@ -35,30 +34,56 @@ const EntityItem = ({
   selected,
   onToggleSelected,
   selectable,
+  variant,
+  item,
 }) => {
   const actions = [
     onPin && {
       title: t`Pin this item`,
       icon: "pin",
       action: onPin,
+      event: `${analyticsContext};Entity Item;Pin Item;${item.model}`,
     },
     onMove && {
       title: t`Move this item`,
       icon: "move",
       action: onMove,
+      event: `${analyticsContext};Entity Item;Move Item;${item.model}`,
     },
     onArchive && {
       title: t`Archive`,
       icon: "archive",
       action: onArchive,
+      event: `${analyticsContext};Entity Item;Archive Item;${item.model}`,
     },
   ].filter(action => action);
 
+  let spacing;
+
+  switch (variant) {
+    case "list":
+      spacing = {
+        px: 2,
+        py: 2,
+      };
+      break;
+    default:
+      spacing = {
+        py: 2,
+      };
+      break;
+  }
+
   return (
-    <EntityItemWrapper py={2} px={2} className="hover-parent hover--visibility">
+    <EntityItemWrapper
+      {...spacing}
+      className={cx("hover-parent hover--visibility", {
+        "bg-light-hover": variant === "list",
+      })}
+    >
       <IconWrapper
         p={1}
-        mr={1}
+        mr={2}
         align="center"
         justify="center"
         onClick={
@@ -73,26 +98,20 @@ const EntityItem = ({
         {selectable ? (
           <Swapper
             startSwapped={selected}
-            defaultElement={<Icon name={iconName} color={iconColor} />}
-            swappedElement={<CheckBox checked={selected} />}
+            defaultElement={
+              <Icon name={iconName} color={iconColor} size={18} />
+            }
+            swappedElement={<CheckBox checked={selected} size={18} />}
           />
         ) : (
-          <Icon name={iconName} color={iconColor} />
+          <Icon name={iconName} color={iconColor} size={18} />
         )}
       </IconWrapper>
-      <h3>
+      <h3 className="overflow-hidden">
         <Ellipsified>{name}</Ellipsified>
       </h3>
 
       <Flex ml="auto" align="center" onClick={e => e.preventDefault()}>
-        {(onFavorite || isFavorite) && (
-          <Icon
-            name={isFavorite ? "star" : "staroutline"}
-            mr={1}
-            className={isFavorite ? "text-gold" : "hover-child"}
-            onClick={onFavorite}
-          />
-        )}
         {actions.length > 0 && (
           <EntityMenu
             className="hover-child"
diff --git a/frontend/src/metabase/components/EntityLayout.js b/frontend/src/metabase/components/EntityLayout.js
index 05a415ba746111af274e6b545336e93c001a97a5..af41b3dab7250814a48b8a1af409af73bd0b4f48 100644
--- a/frontend/src/metabase/components/EntityLayout.js
+++ b/frontend/src/metabase/components/EntityLayout.js
@@ -2,6 +2,8 @@ import React from "react";
 import { Box, Flex } from "grid-styled";
 import Subhead from "metabase/components/Subhead";
 
+import colors from "metabase/lib/colors";
+
 export const Wrapper = ({ children }) => (
   <Box w="80%" ml="auto" mr="auto">
     {children}
@@ -10,11 +12,11 @@ export const Wrapper = ({ children }) => (
 
 export const Canvas = ({ children }) => (
   <Box
-    bg="#FCFDFD"
+    bg={colors["bg-white"]}
     p={2}
     style={{
-      borderTop: "#F4F5F6",
-      borderBottom: "#F5F5F6",
+      borderTop: colors["border"],
+      borderBottom: colors["border"],
     }}
   >
     {children}
diff --git a/frontend/src/metabase/components/EntityMenu.jsx b/frontend/src/metabase/components/EntityMenu.jsx
index 3cb0d540698c8dbcbc66c34d35f0ba4404895c35..88e4c858d57d53f1d1b5346cdf373f8566402f9a 100644
--- a/frontend/src/metabase/components/EntityMenu.jsx
+++ b/frontend/src/metabase/components/EntityMenu.jsx
@@ -18,6 +18,7 @@ type Props = {
   items: Array<EntityMenuOption>,
   triggerIcon: string,
   className?: string,
+  tooltip?: string,
 };
 
 class EntityMenu extends Component {
@@ -47,7 +48,7 @@ class EntityMenu extends Component {
   };
 
   render() {
-    const { items, triggerIcon, className } = this.props;
+    const { items, triggerIcon, className, tooltip } = this.props;
     const { open, menuItemContent } = this.state;
     return (
       <div className={cx("relative", className)}>
@@ -55,6 +56,7 @@ class EntityMenu extends Component {
           icon={triggerIcon}
           onClick={this.toggleMenu}
           open={open}
+          tooltip={tooltip}
         />
         <Popover
           isOpen={open}
@@ -113,6 +115,7 @@ class EntityMenu extends Component {
                               <EntityMenuItem
                                 icon={item.icon}
                                 title={item.title}
+                                externalLink={item.externalLink}
                                 action={
                                   item.action &&
                                   (() => {
@@ -120,7 +123,9 @@ class EntityMenu extends Component {
                                     this.toggleMenu();
                                   })
                                 }
+                                event={item.event && item.event}
                                 link={item.link}
+                                onClose={() => this.toggleMenu()}
                               />
                             </li>
                           );
diff --git a/frontend/src/metabase/components/EntityMenuItem.jsx b/frontend/src/metabase/components/EntityMenuItem.jsx
index 4c8ece386e02d8c422eb0ae95b23e3145b10f1a0..3623da268a963c97b22c7acafae1bd093625a533 100644
--- a/frontend/src/metabase/components/EntityMenuItem.jsx
+++ b/frontend/src/metabase/components/EntityMenuItem.jsx
@@ -4,11 +4,13 @@ import { Link } from "react-router";
 
 import Icon from "metabase/components/Icon";
 
+import colors from "metabase/lib/colors";
+
 const itemClasses = cxs({
   display: "flex",
   alignItems: "center",
   cursor: "pointer",
-  color: "#616D75",
+  color: colors["text-medium"],
   paddingLeft: "1.45em",
   paddingRight: "1.45em",
   paddingTop: "0.85em",
@@ -16,14 +18,14 @@ const itemClasses = cxs({
   textDecoration: "none",
   transition: "all 300ms linear",
   ":hover": {
-    color: "#509ee3",
+    color: colors["brand"],
   },
   "> .Icon": {
-    color: "#BCC5CA",
+    color: colors["text-light"],
     marginRight: "0.65em",
   },
   ":hover > .Icon": {
-    color: "#509ee3",
+    color: colors["brand"],
     transition: "all 300ms linear",
   },
   // icon specific tweaks
@@ -39,26 +41,40 @@ const itemClasses = cxs({
   "> .Icon.Icon-download": {
     transform: `translateY(1px)`,
   },
-  // the history icon is wider so it needs adjustement to center it with other
+  // the history icon is wider so it needs adjustment to center it with other
   // icons
   "> .Icon.Icon-history": {
     transform: `translateX(-2px)`,
   },
 });
 
-const LinkMenuItem = ({ children, link }) => (
-  <Link className={itemClasses} to={link}>
+const LinkMenuItem = ({ children, link, onClose, event, externalLink }) => (
+  <Link
+    className={itemClasses}
+    to={link}
+    target={externalLink ? "_blank" : null}
+    onClick={onClose}
+    data-metabase-event={event}
+  >
     {children}
   </Link>
 );
 
-const ActionMenuItem = ({ children, action }) => (
-  <div className={itemClasses} onClick={action}>
+const ActionMenuItem = ({ children, action, event }) => (
+  <div className={itemClasses} onClick={action} data-metabase-event={event}>
     {children}
   </div>
 );
 
-const EntityMenuItem = ({ action, title, icon, link }) => {
+const EntityMenuItem = ({
+  action,
+  title,
+  icon,
+  link,
+  onClose,
+  event,
+  externalLink,
+}) => {
   if (link && action) {
     console.warn(
       "EntityMenuItem Error: You cannot specify both action and link props",
@@ -72,10 +88,23 @@ const EntityMenuItem = ({ action, title, icon, link }) => {
   ];
 
   if (link) {
-    return <LinkMenuItem link={link}>{content}</LinkMenuItem>;
+    return (
+      <LinkMenuItem
+        link={link}
+        externalLink={externalLink}
+        onClose={onClose}
+        event={event}
+      >
+        {content}
+      </LinkMenuItem>
+    );
   }
   if (action) {
-    return <ActionMenuItem action={action}>{content}</ActionMenuItem>;
+    return (
+      <ActionMenuItem action={action} event={event}>
+        {content}
+      </ActionMenuItem>
+    );
   }
 };
 
diff --git a/frontend/src/metabase/components/EntityMenuTrigger.jsx b/frontend/src/metabase/components/EntityMenuTrigger.jsx
index 4c3d38eeebe44a4e08548b1339bf819138dfaf01..5eeff30745387ab89a727f5ab05b043611e5d827 100644
--- a/frontend/src/metabase/components/EntityMenuTrigger.jsx
+++ b/frontend/src/metabase/components/EntityMenuTrigger.jsx
@@ -1,36 +1,20 @@
 import React from "react";
-import Icon from "metabase/components/Icon";
-import cxs from "cxs";
 
-const EntityMenuTrigger = ({ icon, onClick, open }) => {
-  const interactionColor = "#F2F4F5";
-  const classes = cxs({
-    display: "flex",
-    alignItems: "center",
-    justifyContent: "center",
-    width: 40,
-    height: 40,
-    borderRadius: 99,
-    cursor: "pointer",
-    color: open ? "#509ee3" : "inherit",
-    backgroundColor: open ? interactionColor : "transparent",
-    ":hover": {
-      backgroundColor: interactionColor,
-      color: "#509ee3",
-      transition: "all 300ms linear",
-    },
-    // special cases for certain icons
-    // Icon-share has a taller viewvbox than most so to optically center
-    // the icon we need to translate it upwards
-    "> .Icon.Icon-share": {
-      transform: `translateY(-2px)`,
-    },
-  });
+import Icon, { IconWrapper } from "metabase/components/Icon";
+import Tooltip from "metabase/components/Tooltip";
 
-  return (
-    <div onClick={onClick} className={classes}>
-      <Icon name={icon} className="m1" />
-    </div>
+const EntityMenuTrigger = ({ icon, onClick, open, tooltip }) => {
+  const trigger = (
+    <IconWrapper onClick={onClick}>
+      <Icon name={icon} m={1} />
+    </IconWrapper>
+  );
+  return tooltip ? (
+    <Tooltip tooltip={tooltip} isEnabled={!open}>
+      {trigger}
+    </Tooltip>
+  ) : (
+    trigger
   );
 };
 
diff --git a/frontend/src/metabase/components/EntityPage.jsx b/frontend/src/metabase/components/EntityPage.jsx
index 944ae6427d9f8f601930c082d7bb8bf43a8428e7..3a15c0ed5184d9696bed7a17fe703cb491a5a627 100644
--- a/frontend/src/metabase/components/EntityPage.jsx
+++ b/frontend/src/metabase/components/EntityPage.jsx
@@ -13,6 +13,8 @@ import QuestionAndResultLoader from "metabase/containers/QuestionAndResultLoader
 
 import RelatedItems from "metabase/components/RelatedItems";
 
+import colors from "metabase/lib/colors";
+
 class EntityPage extends Component {
   render() {
     return (
@@ -32,7 +34,7 @@ class EntityPage extends Component {
             <div key="entity">
               <Box
                 className="border-bottom hover-parent hover--visibility relative"
-                style={{ backgroundColor: "#FCFDFD", height: "65vh" }}
+                style={{ backgroundColor: colors["bg-white"], height: "65vh" }}
               >
                 <Box className="hover-child absolute top right">
                   {!loading && (
@@ -65,7 +67,10 @@ class EntityPage extends Component {
                       <Box
                         p={2}
                         mt={4}
-                        style={{ border: "1px solid #ddd", borderRadius: 6 }}
+                        style={{
+                          border: `1px solid ${colors["border"]}`,
+                          borderRadius: 6,
+                        }}
                       >
                         <Box>
                           <h3>Ways to view this</h3>
diff --git a/frontend/src/metabase/components/EntitySegments.jsx b/frontend/src/metabase/components/EntitySegments.jsx
index efb32696a81a2c3a9dd1be1cc5dfcf733f990e23..733a215d74e0ff8ba68090260a06506d644b8585 100644
--- a/frontend/src/metabase/components/EntitySegments.jsx
+++ b/frontend/src/metabase/components/EntitySegments.jsx
@@ -19,7 +19,7 @@ const EntitySegments = ({ question }) => {
       {segments.map(segment => {
         const link = question
           .query()
-          .addFilter(["SEGMENT", segment.id])
+          .addFilter(["segment", segment.id])
           .question()
           .getUrl();
         return <Link to={link}>{segment.name}</Link>;
diff --git a/frontend/src/metabase/components/ErrorDetails.jsx b/frontend/src/metabase/components/ErrorDetails.jsx
index e493754382da666aee6af00795f53a78ee7856fd..82a04ca2c19b444b4d959304dc82c3e79a8cdfef 100644
--- a/frontend/src/metabase/components/ErrorDetails.jsx
+++ b/frontend/src/metabase/components/ErrorDetails.jsx
@@ -26,7 +26,7 @@ export default class ErrorDetails extends React.Component {
           <h2>{t`Here's the full error message`}</h2>
           <div
             style={{ fontFamily: "monospace" }}
-            className="QueryError2-detailBody bordered rounded bg-grey-0 text-bold p2 mt1"
+            className="QueryError2-detailBody bordered rounded bg-light text-bold p2 mt1"
           >
             {/* ensure we don't try to render anything except a string */}
             {typeof details === "string"
diff --git a/frontend/src/metabase/components/ExplicitSize.jsx b/frontend/src/metabase/components/ExplicitSize.jsx
index fbf065ad95be80e5ee768f85583109dd4debf686..912c516a1b24214a314fa6503d9795f525aed2c9 100644
--- a/frontend/src/metabase/components/ExplicitSize.jsx
+++ b/frontend/src/metabase/components/ExplicitSize.jsx
@@ -3,7 +3,7 @@ import ReactDOM from "react-dom";
 
 import ResizeObserver from "resize-observer-polyfill";
 
-export default ComposedComponent =>
+export default measureClass => ComposedComponent =>
   class extends Component {
     static displayName = "ExplicitSize[" +
       (ComposedComponent.displayName || ComposedComponent.name) +
@@ -17,6 +17,17 @@ export default ComposedComponent =>
       };
     }
 
+    _getElement() {
+      const element = ReactDOM.findDOMNode(this);
+      if (measureClass) {
+        const elements = element.getElementsByClassName(measureClass);
+        if (elements.length > 0) {
+          return elements[0];
+        }
+      }
+      return element;
+    }
+
     componentDidMount() {
       // media query listener, ensure re-layout when printing
       if (window.matchMedia) {
@@ -24,11 +35,11 @@ export default ComposedComponent =>
         this._mql.addListener(this._updateSize);
       }
 
-      const element = ReactDOM.findDOMNode(this);
+      const element = this._getElement();
       if (element) {
         // resize observer, ensure re-layout when container element changes size
         this._ro = new ResizeObserver((entries, observer) => {
-          const element = ReactDOM.findDOMNode(this);
+          const element = this._getElement();
           for (const entry of entries) {
             if (entry.target === element) {
               this._updateSize();
@@ -56,7 +67,7 @@ export default ComposedComponent =>
     }
 
     _updateSize = () => {
-      const element = ReactDOM.findDOMNode(this);
+      const element = this._getElement();
       if (element) {
         const { width, height } = element.getBoundingClientRect();
         if (this.state.width !== width || this.state.height !== height) {
diff --git a/frontend/src/metabase/components/ExplorePane.jsx b/frontend/src/metabase/components/ExplorePane.jsx
index dfd1753c6092dcbc105463bc2ef05786f605e092..8f338d7ad1009c1b9da249f49a04665b95ef23aa 100644
--- a/frontend/src/metabase/components/ExplorePane.jsx
+++ b/frontend/src/metabase/components/ExplorePane.jsx
@@ -8,6 +8,8 @@ import MetabotLogo from "metabase/components/MetabotLogo";
 import Select, { Option } from "metabase/components/Select";
 import { Grid, GridItem } from "metabase/components/Grid";
 import Card from "metabase/components/Card";
+import { Flex } from "grid-styled";
+import colors from "metabase/lib/colors";
 
 import { t } from "c-3po";
 import _ from "underscore";
@@ -71,7 +73,7 @@ export class ExplorePane extends React.Component {
     }
 
     return (
-      <div className="pt4 pb2">
+      <div>
         {title && (
           <div className="flex align-center mb2">
             {withMetabot && <MetabotLogo />}
@@ -89,10 +91,8 @@ export class ExplorePane extends React.Component {
         )}
         {schemaNames &&
           schemaNames.length > 1 && (
-            <div className="px4 inline-block mb4">
-              <div className="pb1 text-paragraph">
-                Here's the schema I looked at:
-              </div>
+            <div className="flex align-center ml-auto">
+              <div className="mr1">{t`Based on the schema`}</div>
               <Select
                 value={schemaName}
                 onChange={e =>
@@ -119,7 +119,7 @@ export class ExplorePane extends React.Component {
         )}
         {hasMore && (
           <div
-            className="border-top cursor-pointer text-brand-hover flex layout-centered text-grey-2 px2 pt2 mt4"
+            className="border-top cursor-pointer text-brand-hover flex layout-centered text-light px2 pt2 mt4"
             onClick={() => this.setState({ visibleItems: visibleItems + 4 })}
           >
             <Icon name="chevrondown" size={20} />
@@ -156,20 +156,22 @@ export const ExploreList = ({
 );
 
 export const ExploreOption = ({ option }: { option: Candidate }) => (
-  <Link to={option.url} className="flex align-center text-bold no-decoration">
-    <div
-      className="bg-grey-0 flex align-center rounded mr2 p2 justify-center text-gold"
-      style={{ width: 48, height: 48 }}
+  <Link
+    to={option.url}
+    className="flex align-center no-decoration text-medium text-brand-hover"
+  >
+    <Flex
+      align="center"
+      justify="center"
+      bg={colors["accent4"]}
+      w="42px"
+      style={{ borderRadius: 6, height: 42 }}
+      mr={1}
     >
-      <Icon name="bolt" size={24} className="flex-no-shrink" />
-    </div>
+      <Icon name="bolt" size={20} className="flex-no-shrink text-white" />
+    </Flex>
     <div>
-      <div className="link">{option.title}</div>
-      {option.description && (
-        <div className="text-grey-4 text-small" style={{ marginTop: "0.25em" }}>
-          {option.description}
-        </div>
-      )}
+      {t`A look at your`} <span className="text-bold">{option.title}</span>
     </div>
   </Link>
 );
diff --git a/frontend/src/metabase/components/Favorites.jsx b/frontend/src/metabase/components/Favorites.jsx
deleted file mode 100644
index ff94846ba83c500f018f05d0571cf19084b5318a..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/components/Favorites.jsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import React from "react";
-import { Box } from "grid-styled";
-import { Link } from "react-router";
-import Subhead from "metabase/components/Subhead";
-
-class FavoritesLoader extends React.Component {
-  state = {
-    favorites: null,
-    loading: false,
-    error: null,
-  };
-
-  componentWillMount() {
-    this._loadFavorites();
-  }
-
-  async _loadFavorites() {
-    try {
-      this.setState({ loading: true });
-    } catch (error) {
-      this.setState({ error, loading: false });
-    }
-  }
-
-  render() {
-    const { children } = this.props;
-    const { favorites, loading, error } = this.state;
-    return children && children({ favorites, loading, error });
-  }
-}
-
-const Favorites = () => (
-  <Box>
-    <Subhead>Favorites</Subhead>
-    <FavoritesLoader>
-      {({ favorites, loading, error }) => {
-        if (loading) {
-          return <Box>Loading...</Box>;
-        }
-        return favorites.map(favorite => <Link>{favorite.name}</Link>);
-      }}
-    </FavoritesLoader>
-  </Box>
-);
-
-export default Favorites;
diff --git a/frontend/src/metabase/components/FieldSet.jsx b/frontend/src/metabase/components/FieldSet.jsx
index 1aa0b25174f84a12db846c14c37256b5439451ee..e0accef80a85722dfd2b13aa407556a33e59f460 100644
--- a/frontend/src/metabase/components/FieldSet.jsx
+++ b/frontend/src/metabase/components/FieldSet.jsx
@@ -20,7 +20,7 @@ export default function FieldSet({
   return (
     <fieldset className={cx(className, fieldSetClassName)}>
       {legend && (
-        <legend className="h5 text-bold text-uppercase px1 text-nowrap text-grey-4">
+        <legend className="h5 text-bold text-uppercase px1 text-nowrap text-medium">
           {legend}
         </legend>
       )}
diff --git a/frontend/src/metabase/components/GenericError.jsx b/frontend/src/metabase/components/GenericError.jsx
deleted file mode 100644
index 0be78663ab6bd38a468ae85c2cd5de861b1d41b7..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/components/GenericError.jsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import React from "react";
-import { t } from "c-3po";
-
-import ErrorMessage from "metabase/components/ErrorMessage";
-import ErrorDetails from "metabase/components/ErrorDetails";
-
-const GenericError = ({
-  title = t`Something's gone wrong`,
-  message = t`We've run into an error. You can try refreshing the page, or just go back.`,
-  details = null,
-}) => (
-  <div className="flex flex-column layout-centered full-height">
-    <ErrorMessage type="serverError" title={title} message={message} />
-    <ErrorDetails className="pt2" details={details} centered />
-  </div>
-);
-
-export default GenericError;
diff --git a/frontend/src/metabase/components/Grid.jsx b/frontend/src/metabase/components/Grid.jsx
index dff56b703c94e2aec7dc66d5d263ad3ad5908615..234be85b0fa8796b7b649294c7624543e698dee5 100644
--- a/frontend/src/metabase/components/Grid.jsx
+++ b/frontend/src/metabase/components/Grid.jsx
@@ -14,7 +14,7 @@ GridItem.defaultProps = {
 };
 
 export const Grid = ({ children }) => (
-  <Flex wrap mx={-1}>
+  <Flex mx={-1} style={{ flexWrap: "wrap" }}>
     {children}
   </Flex>
 );
diff --git a/frontend/src/metabase/components/Header.jsx b/frontend/src/metabase/components/Header.jsx
index c062d59fc9d7792866f388a3414e2a2082aae627..9fec288df3056e2a8182d2a123bfea89ba1f5366 100644
--- a/frontend/src/metabase/components/Header.jsx
+++ b/frontend/src/metabase/components/Header.jsx
@@ -1,6 +1,7 @@
 import React, { Component } from "react";
 import ReactDOM from "react-dom";
 
+import CollectionBadge from "metabase/questions/components/CollectionBadge";
 import InputBlurChange from "metabase/components/InputBlurChange.jsx";
 import HeaderModal from "metabase/components/HeaderModal.jsx";
 import TitleAndDescription from "metabase/components/TitleAndDescription.jsx";
@@ -77,6 +78,7 @@ export default class Header extends Component {
   }
 
   render() {
+    const { item } = this.props;
     let titleAndDescription;
     if (this.props.isEditingInfo) {
       titleAndDescription = (
@@ -155,8 +157,14 @@ export default class Header extends Component {
           ref="header"
         >
           <div className="Entity py3">
-            {titleAndDescription}
+            <span className="inline-block mb1">{titleAndDescription}</span>
             {attribution}
+            {!this.props.isEditingInfo && (
+              <CollectionBadge
+                collectionId={item.collection_id}
+                analyticsContext={this.props.analyticsContext}
+              />
+            )}
           </div>
 
           <div className="flex align-center flex-align-right">
diff --git a/frontend/src/metabase/components/HistoryModal.jsx b/frontend/src/metabase/components/HistoryModal.jsx
index 08aca01df27dd7bd97132fd456b82f6c76b69b61..c80ec711a47faa44ca28b5b225326483a546b888 100644
--- a/frontend/src/metabase/components/HistoryModal.jsx
+++ b/frontend/src/metabase/components/HistoryModal.jsx
@@ -74,55 +74,53 @@ export default class HistoryModal extends Component {
   }
 
   render() {
-    let { revisions } = this.props;
+    const { revisions } = this.props;
+    const cellClassName = "p1 border-bottom";
+
     return (
       <ModalContent
         title={t`Revision history`}
         onClose={() => this.props.onClose()}
       >
-        <LoadingAndErrorWrapper
-          className="flex flex-full flex-basis-auto"
-          loading={!revisions}
-          error={this.state.error}
-        >
+        <LoadingAndErrorWrapper loading={!revisions} error={this.state.error}>
           {() => (
-            <div className="pb4 flex-full">
-              <div className="border-bottom flex px4 py1 text-uppercase text-grey-3 text-bold h5">
-                <span className="flex-half">{t`When`}</span>
-                <span className="flex-half">{t`Who`}</span>
-                <span className="flex-full">{t`What`}</span>
-              </div>
-              <div className="px2 scroll-y">
+            <table className="full">
+              <thead>
+                <tr>
+                  <th className={cellClassName}>{t`When`}</th>
+                  <th className={cellClassName}>{t`Who`}</th>
+                  <th className={cellClassName}>{t`What`}</th>
+                  <th className={cellClassName} />
+                </tr>
+              </thead>
+              <tbody>
                 {revisions.map((revision, index) => (
-                  <div
-                    key={revision.id}
-                    className="border-row-divider flex py1 px2"
-                  >
-                    <span className="flex-half">
+                  <tr key={revision.id}>
+                    <td className={cellClassName}>
                       {formatDate(revision.timestamp)}
-                    </span>
-                    <span className="flex-half">
+                    </td>
+                    <td className={cellClassName}>
                       {revision.user.common_name}
-                    </span>
-                    <span className="flex-full flex">
+                    </td>
+                    <td className={cellClassName}>
                       <span>{this.revisionDescription(revision)}</span>
-                      {index !== 0 ? (
-                        <div className="flex-align-right pl1">
-                          <ActionButton
-                            actionFn={() => this.revert(revision)}
-                            className="Button Button--small Button--danger text-uppercase"
-                            normalText={t`Revert`}
-                            activeText={t`Reverting…`}
-                            failedText={t`Revert failed`}
-                            successText={t`Reverted`}
-                          />
-                        </div>
-                      ) : null}
-                    </span>
-                  </div>
+                    </td>
+                    <td className={cellClassName}>
+                      {index !== 0 && (
+                        <ActionButton
+                          actionFn={() => this.revert(revision)}
+                          className="Button Button--small Button--danger text-uppercase"
+                          normalText={t`Revert`}
+                          activeText={t`Reverting…`}
+                          failedText={t`Revert failed`}
+                          successText={t`Reverted`}
+                        />
+                      )}
+                    </td>
+                  </tr>
                 ))}
-              </div>
-            </div>
+              </tbody>
+            </table>
           )}
         </LoadingAndErrorWrapper>
       </ModalContent>
diff --git a/frontend/src/metabase/components/Icon.jsx b/frontend/src/metabase/components/Icon.jsx
index cc1d98c3be865daa17055e24bcf9d53dd5bbbc01..d8957a8b7498f4fc59c6b21c22bcb70a17864810 100644
--- a/frontend/src/metabase/components/Icon.jsx
+++ b/frontend/src/metabase/components/Icon.jsx
@@ -5,11 +5,38 @@ import RetinaImage from "react-retina-image";
 import styled from "styled-components";
 import { color, space, hover } from "styled-system";
 import cx from "classnames";
+import colors from "metabase/lib/colors";
 
 import { loadIcon } from "metabase/icon_paths";
+import { stripLayoutProps } from "metabase/lib/utils";
 
 import Tooltipify from "metabase/hoc/Tooltipify";
 
+const interactionColor = colors["bg-medium"];
+
+export const IconWrapper = styled.div`
+  ${space} display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 40px;
+  height: 40px;
+  border-radius: 99px;
+  cursor: pointer;
+  color: ${props => (props.open ? colors["brand"] : "inherit")};
+  background-color: ${props => (props.open ? interactionColor : "transparent")};
+  &:hover {
+    background-color: ${interactionColor};
+    color: ${colors["brand"]};
+    transition: all 300ms linear;
+  }
+  // special cases for certain icons
+  // Icon-share has a taller viewvbox than most so to optically center
+  // the icon we need to translate it upwards
+  "> .icon.icon-share": {
+    transform: translateY(-2px);
+  }
+`;
+
 class BaseIcon extends Component {
   static props: {
     name: string,
@@ -56,7 +83,7 @@ class BaseIcon extends Component {
       return <svg {...props} dangerouslySetInnerHTML={{ __html: icon.svg }} />;
     } else {
       return (
-        <svg {...props}>
+        <svg {...stripLayoutProps(props)}>
           <path d={icon.path} />
         </svg>
       );
diff --git a/frontend/src/metabase/components/IconWrapper.jsx b/frontend/src/metabase/components/IconWrapper.jsx
index 28cf3fec5b813ac5c6a157ccd0693b8f6accddcf..5deb01190c3255c5a2317ce3c1f0ac6a6daff805 100644
--- a/frontend/src/metabase/components/IconWrapper.jsx
+++ b/frontend/src/metabase/components/IconWrapper.jsx
@@ -1,7 +1,8 @@
 import { Flex } from "grid-styled";
+import colors from "metabase/lib/colors";
 
 const IconWrapper = Flex.extend`
-  background: #f4f5f6;
+  background: ${colors["bg-medium"]};
   border-radius: 6px;
 `;
 
diff --git a/frontend/src/metabase/components/ItemTypeFilterBar.jsx b/frontend/src/metabase/components/ItemTypeFilterBar.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..91bbb0333901fb858ab9f49589784a6183bf702b
--- /dev/null
+++ b/frontend/src/metabase/components/ItemTypeFilterBar.jsx
@@ -0,0 +1,87 @@
+import React from "react";
+import { Flex } from "grid-styled";
+import { t } from "c-3po";
+import { withRouter } from "react-router";
+
+import Icon from "metabase/components/Icon";
+import Link from "metabase/components/Link";
+
+import colors from "metabase/lib/colors";
+
+export const FILTERS = [
+  {
+    name: t`Everything`,
+    filter: null,
+    icon: "list",
+  },
+  {
+    name: t`Dashboards`,
+    filter: "dashboard",
+    icon: "dashboard",
+  },
+  {
+    name: t`Questions`,
+    filter: "card",
+    icon: "beaker",
+  },
+  {
+    name: t`Pulses`,
+    filter: "pulse",
+    icon: "pulse",
+  },
+];
+
+const ItemTypeFilterBar = props => {
+  const { location, analyticsContext } = props;
+  return (
+    <Flex align="center" className="border-bottom mt1">
+      {props.filters.map(f => {
+        let isActive = location && location.query.type === f.filter;
+
+        if (!location.query.type && !f.filter) {
+          isActive = true;
+        }
+
+        const color = isActive ? colors.brand : "inherit";
+
+        return (
+          <Link
+            to={{
+              pathname: location.pathname,
+              query: { ...location.query, type: f.filter },
+            }}
+            color={color}
+            hover={{ color: colors.brand }}
+            className="flex-full flex align-center justify-center sm-block text-brand-hover text-medium"
+            mr={[0, 2]}
+            key={f.filter}
+            py={1}
+            data-metabase-event={`${analyticsContext};Item Filter;${f.name}`}
+            style={{
+              borderBottom: `2px solid ${
+                isActive ? colors.brand : "transparent"
+              }`,
+            }}
+          >
+            <Icon name={f.icon} className="sm-hide" size={20} />
+            <h5
+              className="text-uppercase hide sm-show"
+              style={{
+                color: isActive ? colors.brand : "inherit",
+                fontWeight: 900,
+              }}
+            >
+              {f.name}
+            </h5>
+          </Link>
+        );
+      })}
+    </Flex>
+  );
+};
+
+ItemTypeFilterBar.defaultProps = {
+  filters: FILTERS,
+};
+
+export default withRouter(ItemTypeFilterBar);
diff --git a/frontend/src/metabase/components/Link.jsx b/frontend/src/metabase/components/Link.jsx
index 34cf81f5c560d635f4cf25c93e17d57ba34fb1c2..15cafa2f180d8ee0ccda4c8bdc6d04615b305f92 100644
--- a/frontend/src/metabase/components/Link.jsx
+++ b/frontend/src/metabase/components/Link.jsx
@@ -2,9 +2,14 @@ import React from "react";
 import { Link as ReactRouterLink } from "react-router";
 import styled from "styled-components";
 import { display, color, hover, space } from "styled-system";
+import { stripLayoutProps } from "metabase/lib/utils";
 
 const BaseLink = ({ to, className, children, ...props }) => (
-  <ReactRouterLink to={to} className={className || "link"} {...props}>
+  <ReactRouterLink
+    to={to}
+    className={className || "link"}
+    {...stripLayoutProps(props)}
+  >
     {children}
   </ReactRouterLink>
 );
diff --git a/frontend/src/metabase/components/List.css b/frontend/src/metabase/components/List.css
index 91ae511cd6bd3b6837ec60d319f4e14ef1cc0528..02bd56af9a7d2a6ae24c0575201541d3addba67b 100644
--- a/frontend/src/metabase/components/List.css
+++ b/frontend/src/metabase/components/List.css
@@ -1,8 +1,8 @@
 :root {
-  --title-color: #606e7b;
-  --subtitle-color: #aab7c3;
-  --muted-color: #deeaf1;
-  --blue-color: #2d86d4;
+  --title-color: var(--color-text-medium);
+  --subtitle-color: var(--color-text-medium);
+  --muted-color: var(--color-text-light);
+  --blue-color: var(--color-brand);
 }
 
 :local(.list) {
@@ -32,7 +32,7 @@
   composes: flex flex-full pb2 border-bottom from "style";
   align-items: center;
   height: 100%;
-  border-color: #edf5fb;
+  border-color: var(--color-border);
 }
 
 :local(.headerLink) {
@@ -61,7 +61,7 @@
   max-width: 550px;
   padding-top: 20px;
   padding-bottom: 20px;
-  border-color: #edf5fb;
+  border-color: var(--color-border);
 }
 
 :local(.itemTitle) {
diff --git a/frontend/src/metabase/components/ListFilterWidget.jsx b/frontend/src/metabase/components/ListFilterWidget.jsx
deleted file mode 100644
index ed6152a68455eec282a0370ac4598b6ae6e63737..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/components/ListFilterWidget.jsx
+++ /dev/null
@@ -1,65 +0,0 @@
-/* @flow */
-
-// An UI element that is normally right-aligned and showing a currently selected filter together with chevron.
-// Clicking the element will trigger a popover showing all available filter options.
-
-import React, { Component } from "react";
-import Icon from "metabase/components/Icon";
-import PopoverWithTrigger from "./PopoverWithTrigger";
-
-export type ListFilterWidgetItem = {
-  id: string,
-  name: string,
-  icon: string,
-};
-
-export default class ListFilterWidget extends Component {
-  props: {
-    items: ListFilterWidgetItem[],
-    activeItem: ListFilterWidgetItem,
-    onChange: ListFilterWidgetItem => void,
-  };
-
-  popoverRef: PopoverWithTrigger;
-  iconRef: Icon;
-
-  render() {
-    const { items, activeItem, onChange } = this.props;
-    return (
-      <PopoverWithTrigger
-        ref={p => (this.popoverRef = p)}
-        triggerClasses="block ml-auto flex-no-shrink"
-        targetOffsetY={10}
-        triggerElement={
-          <div className="ml2 flex align-center text-brand">
-            <span className="text-bold">{activeItem && activeItem.name}</span>
-            <Icon
-              ref={i => (this.iconRef = i)}
-              className="ml1"
-              name="chevrondown"
-              width="12"
-              height="12"
-            />
-          </div>
-        }
-        target={() => this.iconRef}
-      >
-        <ol className="text-brand mt2 mb1">
-          {items.map((item, index) => (
-            <li
-              key={index}
-              className="cursor-pointer flex align-center brand-hover px2 py1 mb1"
-              onClick={() => {
-                onChange(item);
-                this.popoverRef.close();
-              }}
-            >
-              <Icon className="mr1 text-light-blue" name={item.icon} />
-              <h4 className="List-item-title">{item.name}</h4>
-            </li>
-          ))}
-        </ol>
-      </PopoverWithTrigger>
-    );
-  }
-}
diff --git a/frontend/src/metabase/components/ListSearchField.jsx b/frontend/src/metabase/components/ListSearchField.jsx
index a3dfd9d9f72886fc4374cd5a8ac6a61fc94498b4..42bd36d679153ef8f78c828887a9799a6aad8fcb 100644
--- a/frontend/src/metabase/components/ListSearchField.jsx
+++ b/frontend/src/metabase/components/ListSearchField.jsx
@@ -13,7 +13,7 @@ export default class ListSearchField extends Component {
   };
 
   static defaultProps = {
-    className: "bordered rounded text-grey-2 flex flex-full align-center",
+    className: "bordered rounded text-light flex flex-full align-center",
     inputClassName: "p1 h4 input--borderless text-default flex-full",
     placeholder: t`Find...`,
     searchText: "",
diff --git a/frontend/src/metabase/components/LoadingAndErrorWrapper.jsx b/frontend/src/metabase/components/LoadingAndErrorWrapper.jsx
index 5226f417b1f28300fbf40b91926bc040479e22c1..b20891c97f5e3edc9908d5b896bac6e9f522feb5 100644
--- a/frontend/src/metabase/components/LoadingAndErrorWrapper.jsx
+++ b/frontend/src/metabase/components/LoadingAndErrorWrapper.jsx
@@ -115,7 +115,7 @@ export default class LoadingAndErrorWrapper extends Component {
       <div className={this.props.className} style={this.props.style}>
         {error ? (
           <div className={contentClassName}>
-            <h2 className="text-normal text-grey-2 ie-wrap-content-fix">
+            <h2 className="text-normal text-light ie-wrap-content-fix">
               {this.getErrorMessage()}
             </h2>
           </div>
@@ -123,7 +123,7 @@ export default class LoadingAndErrorWrapper extends Component {
           <div className={contentClassName}>
             {loadingScenes && loadingScenes[sceneIndex]}
             {!loadingScenes && showSpinner && <LoadingSpinner />}
-            <h2 className="text-normal text-grey-2 mt1">
+            <h2 className="text-normal text-light mt1">
               {loadingMessages[messageIndex]}
             </h2>
           </div>
diff --git a/frontend/src/metabase/components/ModalContent.jsx b/frontend/src/metabase/components/ModalContent.jsx
index 8f6abe52a28413f6d17a86e9596d3d09229a5cf3..93598ba58402d4b92b9dcd5b216814b9d74aa570 100644
--- a/frontend/src/metabase/components/ModalContent.jsx
+++ b/frontend/src/metabase/components/ModalContent.jsx
@@ -42,7 +42,7 @@ export default class ModalContent extends Component {
       >
         {onClose && (
           <Icon
-            className="text-grey-2 text-grey-4-hover cursor-pointer absolute m2 p2 top right"
+            className="text-light text-medium-hover cursor-pointer absolute m2 p2 top right"
             name="close"
             size={fullPageModal ? 24 : 16}
             onClick={onClose}
diff --git a/frontend/src/metabase/components/NewsletterForm.jsx b/frontend/src/metabase/components/NewsletterForm.jsx
index 8fa100920c2c6f78cde7ab4a7f8fdc9caeeedbf0..2518534306fdc1f80f5543fdadefa3cd518ffa35 100644
--- a/frontend/src/metabase/components/NewsletterForm.jsx
+++ b/frontend/src/metabase/components/NewsletterForm.jsx
@@ -4,6 +4,7 @@ import PropTypes from "prop-types";
 import ReactDOM from "react-dom";
 import { t } from "c-3po";
 import Icon from "metabase/components/Icon.jsx";
+import colors from "metabase/lib/colors";
 
 export default class NewsletterForm extends Component {
   constructor(props, context) {
@@ -18,7 +19,7 @@ export default class NewsletterForm extends Component {
 
       input: {
         fontSize: "1.1rem",
-        color: "#676C72",
+        color: colors["text-dark"],
         width: "350px",
       },
 
@@ -62,7 +63,7 @@ export default class NewsletterForm extends Component {
           style={this.styles.label}
           className="absolute text-centered left right"
         >
-          <div className="px3 bg-white h5 text-bold text-grey-4 text-uppercase inline-block">
+          <div className="px3 bg-white h5 text-bold text-medium text-uppercase inline-block">
             <Icon className="mr1 float-left" name="mail" size={16} />
             <span
               className="inline-block"
@@ -73,7 +74,10 @@ export default class NewsletterForm extends Component {
 
         <div className="MB-Newsletter sm-float-right">
           <div>
-            <div style={{ color: "#878E95" }} className="text-grey-4 h3 pb3">
+            <div
+              style={{ color: colors["text-medium"] }}
+              className="text-medium h3 pb3"
+            >
               {t`Get infrequent emails about new releases and feature updates.`}
             </div>
 
diff --git a/frontend/src/metabase/components/NotFound.jsx b/frontend/src/metabase/components/NotFound.jsx
deleted file mode 100644
index 94dfb7803a56defbf9626e9aa18fe9f8d087daee..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/components/NotFound.jsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import React, { Component } from "react";
-import { Link } from "react-router";
-import { t } from "c-3po";
-import * as Urls from "metabase/lib/urls";
-
-// TODO: port to ErrorMessage for more consistent style
-
-export default class NotFound extends Component {
-  render() {
-    return (
-      <div className="layout-centered flex full">
-        <div className="p4 text-bold">
-          <h1 className="text-brand text-light mb3">{t`We're a little lost...`}</h1>
-          <p className="h4 mb1">
-            {t`The page you asked for couldn't be found`}.
-          </p>
-          <p className="h4">{t`You might've been tricked by a ninja, but in all likelihood, you were just given a bad link.`}</p>
-          <p className="h4 my4">{t`You can always:`}</p>
-          <div className="flex align-center">
-            <Link to={Urls.question()} className="Button Button--primary">
-              <div className="p1">{t`Ask a new question.`}</div>
-            </Link>
-            <span className="mx2">{t`or`}</span>
-            <a
-              className="Button Button--withIcon"
-              target="_blank"
-              href="https://giphy.com/tv/search/kitten"
-            >
-              <div className="p1 flex align-center relative">
-                <span className="h2">😸</span>
-                <span className="ml1">{t`Take a kitten break.`}</span>
-              </div>
-            </a>
-          </div>
-        </div>
-      </div>
-    );
-  }
-}
diff --git a/frontend/src/metabase/components/PasswordReveal.jsx b/frontend/src/metabase/components/PasswordReveal.jsx
index 1340433575df7d345721ba5d327c5d9950c9f1a5..b238467cd2a588f88a19801ec9a29f2a79845f19 100644
--- a/frontend/src/metabase/components/PasswordReveal.jsx
+++ b/frontend/src/metabase/components/PasswordReveal.jsx
@@ -2,6 +2,7 @@
 import React, { Component } from "react";
 import CopyButton from "metabase/components/CopyButton";
 import { t } from "c-3po";
+import colors from "metabase/lib/colors";
 
 type State = {
   visible: boolean,
@@ -15,14 +16,14 @@ const styles = {
   input: {
     fontSize: "1.2rem",
     letterSpacing: "2",
-    color: "#676C72",
+    color: colors["text-dark"],
     outline: "none",
   },
 };
 
 const Label = () => (
   <div style={{ top: -12 }} className="absolute text-centered left right">
-    <span className="px1 bg-white h6 text-bold text-grey-3 text-uppercase">
+    <span className="px1 bg-white h6 text-bold text-medium text-uppercase">
       {t`Temporary Password`}
     </span>
   </div>
@@ -47,7 +48,7 @@ export default class PasswordReveal extends Component {
           <input
             ref="input"
             style={styles.input}
-            className="text-grey-2 text-normal mr3 borderless"
+            className="text-light text-normal mr3 borderless"
             value={password}
             onClick={({ target }) =>
               target.setSelectionRange(0, target.value.length)
diff --git a/frontend/src/metabase/components/Popover.css b/frontend/src/metabase/components/Popover.css
index 568be868d48641ea1245d40b74d31e70bcdda766..29a864e425a515184cfd53da6179f42a00b5514f 100644
--- a/frontend/src/metabase/components/Popover.css
+++ b/frontend/src/metabase/components/Popover.css
@@ -16,9 +16,9 @@
 }
 
 .PopoverBody.PopoverBody--withBackground {
-  border: 1px solid #ddd;
-  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
-  background-color: #fff;
+  border: 1px solid var(--color-border);
+  box-shadow: 0 4px 10px var(--color-shadow);
+  background-color: var(--color-bg-white);
   border-radius: 4px;
   overflow: hidden;
 }
@@ -34,7 +34,7 @@
 .PopoverBody.PopoverBody--tooltip {
   color: white;
   font-weight: bold;
-  background-color: rgb(76, 71, 71);
+  background-color: var(--color-bg-black);
   border: none;
   pointer-events: none;
 }
@@ -62,7 +62,7 @@
 
 .PopoverHeader {
   display: flex;
-  border-bottom: 1px solid var(--border-color);
+  border-bottom: 1px solid var(--color-border);
   min-width: 400px;
 }
 
@@ -76,7 +76,7 @@
   text-transform: uppercase;
   font-size: 0.8em;
   font-weight: 700;
-  color: color(var(--base-grey) shade(30%));
+  color: var(--color-text-medium);
   border-bottom: 2px solid transparent;
 }
 
@@ -105,19 +105,19 @@
 /* create a slightly larger arrow on the right for border purposes */
 .PopoverHeader-item--withArrow:before {
   right: -16px;
-  border-left-color: #ddd;
+  border-left-color: var(--color-border);
 }
 
 /* create a smaller inset arrow on the right */
 .PopoverHeader-item--withArrow:after {
   right: -15px;
-  border-left-color: #fff;
+  border-left-color: var(--color-bg-white);
 }
 
 /* create a slightly larger arrow on the top for border purposes */
 .tether-element-attached-top .PopoverBody--withArrow:before {
   top: -20px;
-  border-bottom-color: #ddd;
+  border-bottom-color: var(--color-border);
 }
 .tether-element-attached-top .PopoverBody--tooltip:before {
   border-bottom: none;
@@ -126,16 +126,16 @@
 /* create a smaller inset arrow on the top */
 .tether-element-attached-top .PopoverBody--withArrow:after {
   top: -18px;
-  border-bottom-color: #fff;
+  border-bottom-color: var(--color-bg-white);
 }
 .tether-element-attached-top .PopoverBody--tooltip:after {
-  border-bottom-color: rgb(76, 71, 71);
+  border-bottom-color: var(--color-bg-black);
 }
 
 /* create a slightly larger arrow on the bottom for border purposes */
 .tether-element-attached-bottom .PopoverBody--withArrow:before {
   bottom: -20px;
-  border-top-color: #ddd;
+  border-top-color: var(--color-border);
 }
 .tether-element-attached-bottom .PopoverBody--tooltip:before {
   border-top: none;
@@ -144,10 +144,10 @@
 /* create a smaller inset arrow on the bottom */
 .tether-element-attached-bottom .PopoverBody--withArrow:after {
   bottom: -18px;
-  border-top-color: #fff;
+  border-top-color: var(--color-bg-white);
 }
 .tether-element-attached-bottom .PopoverBody--tooltip:after {
-  border-top-color: rgb(76, 71, 71);
+  border-top-color: var(--color-bg-black);
 }
 
 /* if the tether element is attached right, move our arrows right */
diff --git a/frontend/src/metabase/components/Popover.jsx b/frontend/src/metabase/components/Popover.jsx
index 2d87309b3cbe3615cd3d86764e35aad7764e01b9..7fbc048fc691933b2ad0f4f75ac6557b50794a23 100644
--- a/frontend/src/metabase/components/Popover.jsx
+++ b/frontend/src/metabase/components/Popover.jsx
@@ -148,7 +148,9 @@ export default class Popover extends Component {
         >
           {typeof this.props.children === "function"
             ? this.props.children(childProps)
-            : React.Children.count(this.props.children) === 1
+            : React.Children.count(this.props.children) === 1 &&
+              // NOTE: workaround for https://github.com/facebook/react/issues/12136
+              !Array.isArray(this.props.children)
               ? React.cloneElement(
                   React.Children.only(this.props.children),
                   childProps,
diff --git a/frontend/src/metabase/components/ProgressBar.jsx b/frontend/src/metabase/components/ProgressBar.jsx
index 613a93c5137424b0ceeafa01bca4bf3151594aa6..e59fb71796710172aff4dbd9858cd0e195a5a50c 100644
--- a/frontend/src/metabase/components/ProgressBar.jsx
+++ b/frontend/src/metabase/components/ProgressBar.jsx
@@ -2,7 +2,7 @@
 import React, { Component } from "react";
 import cxs from "cxs";
 
-import { normal } from "metabase/lib/colors";
+import colors from "metabase/lib/colors";
 
 type Props = {
   percentage: number,
@@ -15,7 +15,7 @@ export default class ProgressBar extends Component {
 
   static defaultProps = {
     animated: false,
-    color: normal.blue,
+    color: colors["brand"],
   };
 
   render() {
@@ -48,7 +48,7 @@ export default class ProgressBar extends Component {
         left: 0,
         width: `${width / 4}%`,
         height: "100%",
-        backgroundColor: "rgba(0, 0, 0, 0.12)",
+        backgroundColor: colors["bg-black"],
         animation: animated ? "progress-bar 1.5s linear infinite" : "none",
       },
     });
diff --git a/frontend/src/metabase/components/QuestionIcon.jsx b/frontend/src/metabase/components/QuestionIcon.jsx
deleted file mode 100644
index 9856a926aabdc6f3359c64d521a58aa252e7eb51..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/components/QuestionIcon.jsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import React from "react";
-
-import Icon from "metabase/components/Icon";
-
-import Visualizations from "metabase/visualizations";
-
-const QuestionIcon = ({ question, ...props }) => (
-  <Icon
-    {...props}
-    name={(Visualizations.get(question.display) || {}).iconName || "unknown"}
-  />
-);
-
-export default QuestionIcon;
diff --git a/frontend/src/metabase/components/QuestionSavedModal.jsx b/frontend/src/metabase/components/QuestionSavedModal.jsx
index 4af499f8af042b87067287d0169d1690d56df344..3e256abdbafbe9a53e749b83fb2c10dc63e9020c 100644
--- a/frontend/src/metabase/components/QuestionSavedModal.jsx
+++ b/frontend/src/metabase/components/QuestionSavedModal.jsx
@@ -18,7 +18,7 @@ export default class QuestionSavedModal extends Component {
         onClose={this.props.onClose}
         className="Modal-content Modal-content--small NewForm"
       >
-        <div className="Form-inputs mb4">
+        <div>
           <button
             className="Button Button--primary"
             onClick={this.props.addToDashboardFn}
diff --git a/frontend/src/metabase/components/Radio.info.js b/frontend/src/metabase/components/Radio.info.js
index f9d165e45bf86b65274724157fe2c0cc81ed8d71..bbc53a33e911219ccbd87535cb6f0ed5e1fda543 100644
--- a/frontend/src/metabase/components/Radio.info.js
+++ b/frontend/src/metabase/components/Radio.info.js
@@ -14,6 +14,7 @@ const PROPS = {
 
 export const examples = {
   default: <Radio {...PROPS} />,
+  underlined: <Radio {...PROPS} underlined />,
   "show buttons": <Radio {...PROPS} showButtons />,
-  vertical: <Radio {...PROPS} isVertical />,
+  vertical: <Radio {...PROPS} vertical />,
 };
diff --git a/frontend/src/metabase/components/Radio.jsx b/frontend/src/metabase/components/Radio.jsx
index 53843c39b7b6f5767e6a2cc91542926cd2d77dc7..2b511a600e7a5786f4d74b69bfec86be3f446a95 100644
--- a/frontend/src/metabase/components/Radio.jsx
+++ b/frontend/src/metabase/components/Radio.jsx
@@ -1,6 +1,8 @@
 import React, { Component } from "react";
 import PropTypes from "prop-types";
 
+import colors from "metabase/lib/colors";
+
 import cx from "classnames";
 import _ from "underscore";
 
@@ -12,15 +14,18 @@ export default class Radio extends Component {
     optionNameFn: PropTypes.func,
     optionValueFn: PropTypes.func,
     optionKeyFn: PropTypes.func,
-    isVertical: PropTypes.bool,
+    vertical: PropTypes.bool,
+    underlined: PropTypes.bool,
     showButtons: PropTypes.bool,
+    py: PropTypes.number,
   };
 
   static defaultProps = {
     optionNameFn: option => option.name,
     optionValueFn: option => option.value,
     optionKeyFn: option => option.value,
-    isVertical: false,
+    vertical: false,
+    underlined: false,
   };
 
   constructor(props, context) {
@@ -36,45 +41,56 @@ export default class Radio extends Component {
       optionNameFn,
       optionValueFn,
       optionKeyFn,
-      isVertical,
+      vertical,
+      underlined,
       className,
+      py,
     } = this.props;
     // show buttons for vertical only by default
     const showButtons =
-      this.props.showButtons != undefined ? this.props.showButtons : isVertical;
+      this.props.showButtons != undefined ? this.props.showButtons : vertical;
     return (
       <ul
         className={cx(className, "flex", {
-          "flex-column": isVertical,
+          "flex-column": vertical,
           "text-bold h3": !showButtons,
         })}
       >
-        {options.map(option => (
-          <li
-            key={optionKeyFn(option)}
-            className={cx("flex align-center cursor-pointer mt1 mr2", {
-              "text-brand-hover": !showButtons,
-            })}
-            onClick={e => onChange(optionValueFn(option))}
-          >
-            <input
-              className="Form-radio"
-              type="radio"
-              name={this._id}
-              value={optionValueFn(option)}
-              checked={value === optionValueFn(option)}
-              id={this._id + "-" + optionKeyFn(option)}
-            />
-            {showButtons && (
-              <label htmlFor={this._id + "-" + optionKeyFn(option)} />
-            )}
-            <span
-              className={cx({ "text-brand": value === optionValueFn(option) })}
+        {options.map(option => {
+          const selected = value === optionValueFn(option);
+
+          return (
+            <li
+              key={optionKeyFn(option)}
+              className={cx(
+                "flex align-center cursor-pointer mr3",
+                { "text-brand-hover": !showButtons },
+                py != undefined ? `py${py}` : underlined ? "py2" : "pt1",
+              )}
+              style={{
+                borderBottom: underlined ? `3px solid transparent` : undefined,
+                borderColor:
+                  selected && underlined ? colors["brand"] : "transparent",
+              }}
+              onClick={e => onChange(optionValueFn(option))}
             >
-              {optionNameFn(option)}
-            </span>
-          </li>
-        ))}
+              <input
+                className="Form-radio"
+                type="radio"
+                name={this._id}
+                value={optionValueFn(option)}
+                checked={selected}
+                id={this._id + "-" + optionKeyFn(option)}
+              />
+              {showButtons && (
+                <label htmlFor={this._id + "-" + optionKeyFn(option)} />
+              )}
+              <span className={cx({ "text-brand": selected })}>
+                {optionNameFn(option)}
+              </span>
+            </li>
+          );
+        })}
       </ul>
     );
   }
diff --git a/frontend/src/metabase/components/SchedulePicker.jsx b/frontend/src/metabase/components/SchedulePicker.jsx
index 62e61392ee5458db1820c0433ecf4143b4820db7..80843cbaa72dd9b136aacdebe903fe684a1716ad 100644
--- a/frontend/src/metabase/components/SchedulePicker.jsx
+++ b/frontend/src/metabase/components/SchedulePicker.jsx
@@ -205,7 +205,7 @@ export default class SchedulePicker extends Component {
           className="h4 text-bold bg-white"
         />
         {textBeforeSendTime && (
-          <div className="mt2 h4 text-bold text-grey-3 border-top pt2">
+          <div className="mt2 h4 text-bold text-medium border-top pt2">
             {textBeforeSendTime} {hour === 0 ? 12 : hour}:00{" "}
             {amPm ? "PM" : "AM"} {timezone}, {t`your Metabase timezone`}.
           </div>
diff --git a/frontend/src/metabase/components/SearchHeader.jsx b/frontend/src/metabase/components/SearchHeader.jsx
index 902cb36a2731000abb0654d4d4ddbf99bc168f7f..e2191f793deb506827467645688dd0b3d662a0c5 100644
--- a/frontend/src/metabase/components/SearchHeader.jsx
+++ b/frontend/src/metabase/components/SearchHeader.jsx
@@ -28,7 +28,7 @@ const SearchHeader = ({
       searchText !== "" && (
         <Icon
           name="close"
-          className="cursor-pointer text-grey-2"
+          className="cursor-pointer text-light"
           size={18}
           onClick={resetSearchText}
         />
diff --git a/frontend/src/metabase/components/Select.jsx b/frontend/src/metabase/components/Select.jsx
index 037164d5df09ac6e20d3ca99e2331679f56f153c..e481b6b426e5c48e23ee979304ff5694e4d1de23 100644
--- a/frontend/src/metabase/components/Select.jsx
+++ b/frontend/src/metabase/components/Select.jsx
@@ -8,6 +8,7 @@ import { t } from "c-3po";
 import ColumnarSelector from "metabase/components/ColumnarSelector.jsx";
 import Icon from "metabase/components/Icon.jsx";
 import PopoverWithTrigger from "metabase/components/PopoverWithTrigger.jsx";
+import SelectButton from "./SelectButton";
 
 import cx from "classnames";
 import _ from "underscore";
@@ -207,27 +208,6 @@ class BrowserSelect extends Component {
   }
 }
 
-export const SelectButton = ({ hasValue, children }) => (
-  <div
-    className={
-      "AdminSelect border-med flex align-center " +
-      (!hasValue ? " text-grey-3" : "")
-    }
-  >
-    <span className="AdminSelect-content mr1">{children}</span>
-    <Icon
-      className="AdminSelect-chevron flex-align-right"
-      name="chevrondown"
-      size={12}
-    />
-  </div>
-);
-
-SelectButton.propTypes = {
-  hasValue: PropTypes.bool,
-  children: PropTypes.any,
-};
-
 export class Option extends Component {
   static propTypes = {
     children: PropTypes.any,
@@ -333,7 +313,7 @@ class LegacySelect extends Component {
       <div
         className={cx(
           "flex align-center",
-          !value && (!values || values.length === 0) ? " text-grey-2" : "",
+          !value && (!values || values.length === 0) ? " text-light" : "",
         )}
       >
         {values && values.length !== 0 ? (
diff --git a/frontend/src/metabase/components/SelectButton.jsx b/frontend/src/metabase/components/SelectButton.jsx
index 876bb74645242757266cab3bccaa67db50300251..20dd188605b3e5d69da61812d8e676dc7ec0de1a 100644
--- a/frontend/src/metabase/components/SelectButton.jsx
+++ b/frontend/src/metabase/components/SelectButton.jsx
@@ -6,10 +6,11 @@ import Icon from "metabase/components/Icon.jsx";
 
 import cx from "classnames";
 
-const SelectButton = ({ className, children, hasValue = true }) => (
+const SelectButton = ({ className, style, children, hasValue = true }) => (
   <div
+    style={style}
     className={cx(className, "AdminSelect flex align-center", {
-      "text-grey-3": !hasValue,
+      "text-medium": !hasValue,
     })}
   >
     <span className="AdminSelect-content mr1">{children}</span>
@@ -23,6 +24,7 @@ const SelectButton = ({ className, children, hasValue = true }) => (
 
 SelectButton.propTypes = {
   className: PropTypes.string,
+  style: PropTypes.object,
   children: PropTypes.any,
   hasValue: PropTypes.any,
 };
diff --git a/frontend/src/metabase/components/ShrinkableList.jsx b/frontend/src/metabase/components/ShrinkableList.jsx
index 0b16343ea45f3d3a51be8b67eae2f703f48631e4..7c70844fad7a4d1234472016a45a9baec6b66cd3 100644
--- a/frontend/src/metabase/components/ShrinkableList.jsx
+++ b/frontend/src/metabase/components/ShrinkableList.jsx
@@ -16,7 +16,7 @@ type State = {
   isShrunk: ?boolean,
 };
 
-@ExplicitSize
+@ExplicitSize()
 export default class ShrinkableList extends Component {
   props: Props;
   state: State = {
diff --git a/frontend/src/metabase/components/Sidebar.css b/frontend/src/metabase/components/Sidebar.css
index 1f0303641d7aa22cfc7058d155a27276897830fa..f24b2b89e5375b8cf726ca854a60aa15964de121 100644
--- a/frontend/src/metabase/components/Sidebar.css
+++ b/frontend/src/metabase/components/Sidebar.css
@@ -15,9 +15,9 @@
 :local(.sidebar) {
   composes: py2 from "style";
   width: 345px;
-  background-color: rgb(248, 252, 253);
-  border-right: 1px solid rgb(223, 238, 245);
-  color: #606e7b;
+  background-color: var(--color-bg-light);
+  border-right: 1px solid var(--color-border);
+  color: var(--color-text-medium);
 }
 
 :local(.sidebar) a {
@@ -39,7 +39,7 @@
   composes: transition-color from "style";
   composes: transition-background from "style";
   font-size: 1em;
-  color: #cfe4f5;
+  color: var(--color-text-light);
 }
 
 :local(.item) :local(.icon) {
@@ -57,8 +57,8 @@
 :local(.sectionTitle.selected),
 :local(.item):hover,
 :local(.sectionTitle):hover {
-  background-color: #e3f0f9;
-  color: #2d86d4;
+  background-color: var(--color-bg-medium);
+  color: var(--color-brand);
 }
 
 :local(.divider) {
@@ -69,7 +69,7 @@
 
 :local(.name) {
   composes: ml2 text-bold from "style";
-  color: #9caebe;
+  color: var(--color-text-medium);
   text-overflow: ellipsis;
   white-space: nowrap;
   overflow-x: hidden;
@@ -77,7 +77,7 @@
 
 :local(.item):hover :local(.name),
 :local(.item.selected) :local(.name) {
-  color: #2d86d4;
+  color: var(--color-brand);
 }
 
 :local(.icon) {
diff --git a/frontend/src/metabase/components/SidebarLayout.jsx b/frontend/src/metabase/components/SidebarLayout.jsx
index a017796c72c5313f1dbc186f5661282187c5b360..f81e627a75bb23cad383ac31aaa316192a7db523 100644
--- a/frontend/src/metabase/components/SidebarLayout.jsx
+++ b/frontend/src/metabase/components/SidebarLayout.jsx
@@ -2,6 +2,8 @@
 import React from "react";
 import PropTypes from "prop-types";
 
+import fitViewport from "metabase/hoc/FitViewPort";
+
 const SidebarLayout = ({ className, style, sidebar, children }) => (
   <div
     className={className}
@@ -10,7 +12,7 @@ const SidebarLayout = ({ className, style, sidebar, children }) => (
     {React.cloneElement(
       sidebar,
       {
-        style: { flexShrink: 0 },
+        style: { flexShrink: 0, alignSelf: "stretch" },
         className: "scroll-y scroll-show scroll--light scroll-show--hover",
       },
       sidebar.props.children,
@@ -39,4 +41,4 @@ SidebarLayout.propTypes = {
   children: PropTypes.element.isRequired,
 };
 
-export default SidebarLayout;
+export default fitViewport(SidebarLayout);
diff --git a/frontend/src/metabase/components/SortableItemList.css b/frontend/src/metabase/components/SortableItemList.css
deleted file mode 100644
index 5b0335d1ec6ccceaa00339b852f81708dd998722..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/components/SortableItemList.css
+++ /dev/null
@@ -1,3 +0,0 @@
-.SortableItemList-list {
-  overflow-y: auto;
-}
diff --git a/frontend/src/metabase/components/SortableItemList.jsx b/frontend/src/metabase/components/SortableItemList.jsx
deleted file mode 100644
index 17149dc2bfc931980266c591c822c19f95375f69..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/components/SortableItemList.jsx
+++ /dev/null
@@ -1,100 +0,0 @@
-import React, { Component } from "react";
-import PropTypes from "prop-types";
-
-import "./SortableItemList.css";
-import { t } from "c-3po";
-import Icon from "metabase/components/Icon.jsx";
-import Radio from "metabase/components/Radio.jsx";
-
-import moment from "moment";
-
-export default class SortableItemList extends Component {
-  constructor(props, context) {
-    super(props, context);
-    this.state = {
-      sort: props.initialSort || "Last Modified",
-    };
-  }
-
-  static propTypes = {
-    items: PropTypes.array.isRequired,
-    clickItemFn: PropTypes.func,
-    showIcons: PropTypes.bool,
-  };
-
-  onClickItem(item) {
-    if (this.props.onClickItemFn) {
-      this.props.onClickItemFn(item);
-    }
-  }
-
-  render() {
-    let items;
-    if (this.state.sort === "Last Modified") {
-      items = this.props.items
-        .slice()
-        .sort((a, b) => b.updated_at - a.updated_at);
-    } else if (this.state.sort === "Alphabetical Order") {
-      items = this.props.items
-        .slice()
-        .sort((a, b) =>
-          a.name.toLowerCase().localeCompare(b.name.toLowerCase()),
-        );
-    }
-
-    return (
-      <div className="SortableItemList">
-        <div className="flex align-center px2 pb3 border-bottom">
-          <h5 className="text-bold text-uppercase text-grey-3 ml2 mt1 mr2">
-            {t`Sort by`}
-          </h5>
-          <Radio
-            value={this.state.sort}
-            options={[
-              "Last Modified",
-              /*"Most Popular",*/ "Alphabetical Order",
-            ]}
-            onChange={sort => this.setState({ sort })}
-            optionNameFn={o => o}
-            optionValueFn={o => o}
-            optionKeyFn={o => o}
-          />
-        </div>
-
-        <ul className="SortableItemList-list px2 pb2">
-          {items.map(item => (
-            <li key={item.id} className="border-row-divider">
-              <a
-                className="no-decoration flex p2"
-                onClick={() => this.onClickItem(item)}
-              >
-                <div className="flex align-center flex-full mr2">
-                  {this.props.showIcons ? (
-                    <div className="mr2">
-                      <Icon name={"illustration-" + item.display} size={48} />
-                    </div>
-                  ) : null}
-                  <div className="text-brand-hover">
-                    <h3 className="mb1">{item.name}</h3>
-                    <h4 className="text-grey-3">
-                      {item.description || t`No description yet`}
-                    </h4>
-                  </div>
-                </div>
-                {item.creator &&
-                  item.updated_at && (
-                    <div className="flex-align-right text-right text-grey-3">
-                      <div className="mb1">
-                        Saved by {item.creator.common_name}
-                      </div>
-                      <div>Modified {moment(item.updated_at).fromNow()}</div>
-                    </div>
-                  )}
-              </a>
-            </li>
-          ))}
-        </ul>
-      </div>
-    );
-  }
-}
diff --git a/frontend/src/metabase/components/StepIndicators.jsx b/frontend/src/metabase/components/StepIndicators.jsx
index 993f4be37a65712ddeea1c7df200b94dfcfe0fbe..f705ef5886ebac73c6bd58717d5bbb732f80a30d 100644
--- a/frontend/src/metabase/components/StepIndicators.jsx
+++ b/frontend/src/metabase/components/StepIndicators.jsx
@@ -1,7 +1,7 @@
 /* @flow */
 import React from "react";
 
-import { normal } from "metabase/lib/colors";
+import colors from "metabase/lib/colors";
 
 type Props = {
   activeDotColor?: string,
@@ -12,7 +12,7 @@ type Props = {
 };
 
 const StepIndicators = ({
-  activeDotColor = normal.blue,
+  activeDotColor = colors["brand"],
   currentStep = 0,
   dotSize = 8,
   goToStep,
@@ -30,7 +30,7 @@ const StepIndicators = ({
           marginLeft: 2,
           marginRight: 2,
           backgroundColor:
-            index + 1 === currentStep ? activeDotColor : "#D8D8D8",
+            index + 1 === currentStep ? activeDotColor : colors["text-light"],
           transition: "background 600ms ease-in",
         }}
         key={index}
diff --git a/frontend/src/metabase/components/Swapper.jsx b/frontend/src/metabase/components/Swapper.jsx
index 5e617c3d2d50f2d9151059ddb1d854fc3ff78051..9942015babc5a94c0be349ce4532da0c29b1cac4 100644
--- a/frontend/src/metabase/components/Swapper.jsx
+++ b/frontend/src/metabase/components/Swapper.jsx
@@ -28,6 +28,7 @@ class Swapper extends React.Component {
         onMouseEnter={() => this._onMouseEnter()}
         onMouseLeave={() => this._onMouseLeave()}
         className="block relative"
+        style={{ lineHeight: 1 }}
       >
         <Motion
           defaultStyle={{
@@ -39,10 +40,7 @@ class Swapper extends React.Component {
         >
           {({ scale }) => {
             return (
-              <span
-                className=""
-                style={{ display: "block", transform: `scale(${scale})` }}
-              >
+              <span style={{ display: "block", transform: `scale(${scale})` }}>
                 {defaultElement}
               </span>
             );
diff --git a/frontend/src/metabase/components/TermWithDefinition.jsx b/frontend/src/metabase/components/TermWithDefinition.jsx
deleted file mode 100644
index 3c081499c434c51392c0602e4704634168fd4ddb..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/components/TermWithDefinition.jsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import React from "react";
-import cxs from "cxs";
-import Tooltip from "metabase/components/Tooltip";
-
-const termStyles = cxs({
-  textDecoration: "none",
-  borderBottom: "1px dotted #DCE1E4",
-});
-export const TermWithDefinition = ({ children, definition, link }) => (
-  <Tooltip tooltip={definition}>
-    {link ? (
-      <a href={link} className={termStyles} target="_blank">
-        {children}
-      </a>
-    ) : (
-      <span className={termStyles}>{children}</span>
-    )}
-  </Tooltip>
-);
diff --git a/frontend/src/metabase/components/Text.jsx b/frontend/src/metabase/components/Text.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..6bc72e697ad1b409462570e255bc8ab3a165b709
--- /dev/null
+++ b/frontend/src/metabase/components/Text.jsx
@@ -0,0 +1,15 @@
+import styled from "styled-components";
+import { space } from "system-components";
+import colors from "metabase/lib/colors";
+
+const Text = styled.p`
+  ${space};
+  color: ${props => colors[`text-${props.color}`]};
+`;
+
+Text.defaultProps = {
+  className: "text-paragraph",
+  color: "dark",
+};
+
+export default Text;
diff --git a/frontend/src/metabase/components/TitleAndDescription.jsx b/frontend/src/metabase/components/TitleAndDescription.jsx
index 277447ad8fc411c88817698c7909478e2cf3a675..729362e1522ef78ec26a93499ebf6448d53cc42c 100644
--- a/frontend/src/metabase/components/TitleAndDescription.jsx
+++ b/frontend/src/metabase/components/TitleAndDescription.jsx
@@ -13,7 +13,7 @@ type Attributes = {
 };
 const TitleAndDescription = ({ title, description, className }: Attributes) => (
   <div className={cx("flex align-center", className)}>
-    <h2 className="mr1">{title}</h2>
+    <h2 className="h2 mr1">{title}</h2>
     {description && (
       <Tooltip tooltip={description} maxWidth={"22em"}>
         <Icon name="info" style={{ marginTop: 3 }} />
diff --git a/frontend/src/metabase/components/Toggle.css b/frontend/src/metabase/components/Toggle.css
index 8bcda78577d880ee322c591b509f8ba79bb5902d..ec2f3bf83318251a30d0824a9c88909308292ca4 100644
--- a/frontend/src/metabase/components/Toggle.css
+++ b/frontend/src/metabase/components/Toggle.css
@@ -1,13 +1,13 @@
 :local(.toggle) {
   position: relative;
   display: inline-block;
-  color: var(--brand-color);
+  color: var(--color-brand);
   box-sizing: border-box;
   width: 48px;
   height: 24px;
   border-radius: 99px;
-  border: 1px solid #eaeaea;
-  background-color: #f7f7f7;
+  border: 1px solid var(--color-border);
+  background-color: var(--color-bg-medium);
   transition: all 0.3s;
 }
 
@@ -23,18 +23,27 @@
   position: absolute;
   top: 1px;
   left: 1px;
-  background-color: #d9d9d9;
+  background-color: white;
   transition: all 0.3s;
+  box-shadow: 2px 2px 6px var(--color-shadow);
 }
 
 :local(.toggle.selected):after {
-  content: "";
-  width: 20px;
-  height: 20px;
-  border-radius: 99px;
-  position: absolute;
-  top: 1px;
   left: 25px;
 
   background-color: white;
 }
+
+:local(.toggle.small) {
+  width: 28px;
+  height: 17px;
+}
+
+:local(.toggle.small):after {
+  width: 13px;
+  height: 13px;
+}
+
+:local(.toggle.small.selected):after {
+  left: 12px;
+}
diff --git a/frontend/src/metabase/components/Toggle.jsx b/frontend/src/metabase/components/Toggle.jsx
index 2d8c9367fe7a39861b8316555e76ed0982218407..4b39406f4886ad8003dac7bd8ea8822e159cc9df 100644
--- a/frontend/src/metabase/components/Toggle.jsx
+++ b/frontend/src/metabase/components/Toggle.jsx
@@ -14,6 +14,7 @@ export default class Toggle extends Component {
   static propTypes = {
     value: PropTypes.bool.isRequired,
     onChange: PropTypes.func,
+    small: PropTypes.bool,
   };
 
   onClick() {
@@ -25,13 +26,15 @@ export default class Toggle extends Component {
   render() {
     return (
       <a
-        className={
-          cx(styles.toggle, "no-decoration", {
+        className={cx(
+          styles.toggle,
+          "no-decoration",
+          {
             [styles.selected]: this.props.value,
-          }) +
-          " " +
-          (this.props.className || "")
-        }
+            [styles.small]: this.props.small,
+          },
+          this.props.className,
+        )}
         style={{ color: this.props.color || null }}
         onClick={this.props.onChange ? this.onClick : null}
       />
diff --git a/frontend/src/metabase/components/ToggleLarge.jsx b/frontend/src/metabase/components/ToggleLarge.jsx
index 964b9d624feb626978100ca8d9c6d11ca5acc5d1..96307e6cac8781e9e3d8620f1f7392ad4fbb9714 100644
--- a/frontend/src/metabase/components/ToggleLarge.jsx
+++ b/frontend/src/metabase/components/ToggleLarge.jsx
@@ -11,7 +11,7 @@ const ToggleLarge = ({
   textRight,
 }) => (
   <div
-    className={cx(className, "bg-grey-1 flex relative text-bold", {
+    className={cx(className, "bg-medium flex relative text-bold", {
       "cursor-pointer": onChange,
     })}
     style={{ borderRadius: 8, ...style }}
diff --git a/frontend/src/metabase/components/TokenField.jsx b/frontend/src/metabase/components/TokenField.jsx
index f7f3608ca257fb73ceaf985662e3b35d56b58246..983c126dbf3a1f36011b23a86acaf0cea5f1cc2c 100644
--- a/frontend/src/metabase/components/TokenField.jsx
+++ b/frontend/src/metabase/components/TokenField.jsx
@@ -188,6 +188,10 @@ export default class TokenField extends Component {
     return typeof labelKey === "function" ? labelKey(option) : option[labelKey];
   }
 
+  _key(option: Option) {
+    return JSON.stringify(this._value(option));
+  }
+
   _isLastFreeformValue(inputValue: string) {
     const { value, parseFreeformValue, updateOnInputChange } = this.props;
     if (parseFreeformValue && updateOnInputChange) {
@@ -556,14 +560,14 @@ export default class TokenField extends Component {
           <li
             key={index}
             className={cx(
-              `mt1 ml1 py1 pl2 rounded bg-grey-05`,
+              `mt1 ml1 py1 pl2 rounded bg-medium`,
               multi ? "pr1" : "pr2",
             )}
           >
             <span className="text-bold">{valueRenderer(v)}</span>
             {multi && (
               <a
-                className="text-grey-3 text-default-hover px1"
+                className="text-medium text-default-hover px1"
                 onClick={e => {
                   this.removeValue(v);
                   e.preventDefault();
@@ -603,7 +607,7 @@ export default class TokenField extends Component {
           onMouseLeave={() => this.setState({ listIsHovered: false })}
         >
           {filteredOptions.map(option => (
-            <li className="mr1" key={this._value(option)}>
+            <li className="mr1" key={this._key(option)}>
               <div
                 ref={
                   this._valueIsEqual(selectedOptionValue, this._value(option))
@@ -612,9 +616,9 @@ export default class TokenField extends Component {
                 }
                 className={cx(
                   `py1 pl1 pr2 block rounded text-bold text-${color}-hover inline-block full cursor-pointer`,
-                  `bg-grey-0-hover`,
+                  `bg-light-hover`,
                   {
-                    [`text-${color} bg-grey-0`]:
+                    [`text-${color} bg-light`]:
                       !this.state.listIsHovered &&
                       this._valueIsEqual(
                         selectedOptionValue,
diff --git a/frontend/src/metabase/components/Tooltip.jsx b/frontend/src/metabase/components/Tooltip.jsx
index fb73b365384b2684889c28794d05596c783e38d5..615a54c5f5074785718b40f4815ed4a6bcf9e31c 100644
--- a/frontend/src/metabase/components/Tooltip.jsx
+++ b/frontend/src/metabase/components/Tooltip.jsx
@@ -4,6 +4,31 @@ import ReactDOM from "react-dom";
 
 import TooltipPopover from "./TooltipPopover.jsx";
 
+// TOOLTIP_STACK and related functions are to ensure only the most recent tooltip is visible
+
+let TOOLTIP_STACK = [];
+
+function pushTooltip(component) {
+  // if for some reason the tooltip is already in the stack (it shouldn't be) remove it and we'll add it again as if it wasn't
+  TOOLTIP_STACK = TOOLTIP_STACK.filter(t => t !== component);
+  // close all other tooltips
+  TOOLTIP_STACK.filter(t => t.state.isOpen).forEach(t =>
+    t.setState({ isOpen: false }),
+  );
+  // add this tooltip
+  TOOLTIP_STACK.push(component);
+}
+
+function popTooltip(component) {
+  // remove the tooltip from the stack
+  TOOLTIP_STACK = TOOLTIP_STACK.filter(t => t !== component);
+  // reopen the top tooltip, if any
+  const top = TOOLTIP_STACK[TOOLTIP_STACK.length - 1];
+  if (top && !top.state.isOpen) {
+    top.setState({ isOpen: true });
+  }
+}
+
 export default class Tooltip extends Component {
   constructor(props, context) {
     super(props, context);
@@ -15,10 +40,15 @@ export default class Tooltip extends Component {
   }
 
   static propTypes = {
+    // the tooltip to show
     tooltip: PropTypes.node,
+    // the element to be tooltipped
     children: PropTypes.element.isRequired,
+    // Can be used to show / hide the tooltip based on outside conditions
+    // like a menu being open
     isEnabled: PropTypes.bool,
     verticalAttachments: PropTypes.array,
+    // Whether the tooltip should be shown
     isOpen: PropTypes.bool,
   };
 
@@ -65,6 +95,7 @@ export default class Tooltip extends Component {
   }
 
   componentWillUnmount() {
+    popTooltip(this);
     let elem = ReactDOM.findDOMNode(this);
     elem.removeEventListener("mouseenter", this._onMouseEnter, false);
     elem.removeEventListener("mouseleave", this._onMouseLeave, false);
@@ -75,10 +106,12 @@ export default class Tooltip extends Component {
   }
 
   _onMouseEnter = e => {
+    pushTooltip(this);
     this.setState({ isOpen: true, isHovered: true });
   };
 
   _onMouseLeave = e => {
+    popTooltip(this);
     this.setState({ isOpen: false, isHovered: false });
   };
 
diff --git a/frontend/src/metabase/components/Triggerable.jsx b/frontend/src/metabase/components/Triggerable.jsx
index 2d4b9342e179e783f45558a1bb18aaa0847b77ef..14ca1ff2d17b321c16c64fcb849a6b0552d2c4ad 100644
--- a/frontend/src/metabase/components/Triggerable.jsx
+++ b/frontend/src/metabase/components/Triggerable.jsx
@@ -107,6 +107,7 @@ export default ComposedComponent =>
         triggerClasses,
         triggerStyle,
         triggerClassesOpen,
+        triggerClassesClose,
       } = this.props;
       const { isOpen } = this.state;
 
@@ -142,6 +143,7 @@ export default ComposedComponent =>
           className={cx(
             triggerClasses,
             isOpen && triggerClassesOpen,
+            !isOpen && triggerClassesClose,
             "no-decoration",
             {
               "cursor-default": this.props.disabled,
diff --git a/frontend/src/metabase/components/Unauthorized.jsx b/frontend/src/metabase/components/Unauthorized.jsx
deleted file mode 100644
index 38810585ebb728d4d1e1e11eee550fbd4d6acae4..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/components/Unauthorized.jsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import React, { Component } from "react";
-import { t } from "c-3po";
-import { Flex } from "grid-styled";
-
-import fitViewPort from "metabase/hoc/FitViewPort";
-
-import Icon from "metabase/components/Icon";
-
-// TODO: port to ErrorMessage for more consistent style
-
-@fitViewPort
-export default class Unauthorized extends Component {
-  render() {
-    return (
-      <Flex
-        className={this.props.fitClassNames}
-        flexDirection="column"
-        align="center"
-        justifyContent="center"
-        color=""
-      >
-        <Icon name="key" size={100} />
-        <h1 className="mt4">{t`Sorry, you don’t have permission to see that.`}</h1>
-      </Flex>
-    );
-  }
-}
diff --git a/frontend/src/metabase/components/UserAvatar.jsx b/frontend/src/metabase/components/UserAvatar.jsx
index 22600d03ee3cdddc205ec8a9923df4196ac75947..78471a747d601c8377dcd6632ed12eaf9514e086 100644
--- a/frontend/src/metabase/components/UserAvatar.jsx
+++ b/frontend/src/metabase/components/UserAvatar.jsx
@@ -19,6 +19,7 @@ export default class UserAvatar extends Component {
   static propTypes = {
     background: PropTypes.string,
     user: PropTypes.object.isRequired,
+    transparent: PropTypes.bool,
   };
 
   static defaultProps = {
@@ -51,7 +52,11 @@ export default class UserAvatar extends Component {
     return (
       <div
         className={cx(classes)}
-        style={{ ...this.styles, ...this.props.style }}
+        style={{
+          ...this.styles,
+          ...this.props.style,
+          ...(this.props.transparent ? { background: "transparent" } : {}),
+        }}
       >
         {this.userInitials()}
       </div>
diff --git a/frontend/src/metabase/components/__mocks__/ExplicitSize.jsx b/frontend/src/metabase/components/__mocks__/ExplicitSize.jsx
index 91e740969979b01f4c19ef9b0fc2f439cb76c1c3..9d289eeffaef006916f18177b972ba7622eb9b64 100644
--- a/frontend/src/metabase/components/__mocks__/ExplicitSize.jsx
+++ b/frontend/src/metabase/components/__mocks__/ExplicitSize.jsx
@@ -1,7 +1,7 @@
 /* eslint-disable react/display-name */
 import React from "react";
 
-const ExplicitSize = ComposedComponent => props => (
+const ExplicitSize = measureClass => ComposedComponent => props => (
   <ComposedComponent width={1000} height={1000} {...props} />
 );
 
diff --git a/frontend/src/metabase/components/form/FormField.jsx b/frontend/src/metabase/components/form/FormField.jsx
index 12b130e8e016e9ac9f31ad14d62927f0185cb664..45b1d16b88629e8add0d40205d980c830bd10773 100644
--- a/frontend/src/metabase/components/form/FormField.jsx
+++ b/frontend/src/metabase/components/form/FormField.jsx
@@ -12,8 +12,12 @@ export default class FormField extends Component {
     visited: PropTypes.bool,
     active: PropTypes.bool,
 
+    hidden: PropTypes.bool,
     displayName: PropTypes.string,
-    children: PropTypes.element,
+    children: PropTypes.oneOfType([
+      PropTypes.arrayOf(PropTypes.node),
+      PropTypes.node,
+    ]),
 
     // legacy
     fieldName: PropTypes.string,
@@ -21,7 +25,7 @@ export default class FormField extends Component {
   };
 
   render() {
-    const { displayName, offset, formError, children } = this.props;
+    const { displayName, offset, formError, children, hidden } = this.props;
     const name = this.props.name || this.props.fieldName;
 
     let error = this.props.error || getIn(formError, ["data", "errors", name]);
@@ -34,6 +38,7 @@ export default class FormField extends Component {
       <div
         className={cx("Form-field", {
           "Form--fieldError": !!error,
+          hide: hidden,
         })}
       >
         {displayName && (
diff --git a/frontend/src/metabase/components/form/FormWidget.jsx b/frontend/src/metabase/components/form/FormWidget.jsx
index 77e82001ca8f241acda96bff733d5d8fcfcff53e..ab96fcf344ada5f33fc88507b4fcd4b6cb68ffd4 100644
--- a/frontend/src/metabase/components/form/FormWidget.jsx
+++ b/frontend/src/metabase/components/form/FormWidget.jsx
@@ -5,6 +5,8 @@ import FormTextAreaWidget from "./widgets/FormTextAreaWidget";
 import FormPasswordWidget from "./widgets/FormPasswordWidget";
 import FormColorWidget from "./widgets/FormColorWidget";
 import FormSelectWidget from "./widgets/FormSelectWidget";
+import FormCollectionWidget from "./widgets/FormCollectionWidget";
+import FormHiddenWidget from "./widgets/FormHiddenWidget";
 
 const WIDGETS = {
   input: FormInputWidget,
@@ -12,6 +14,8 @@ const WIDGETS = {
   color: FormColorWidget,
   password: FormPasswordWidget,
   select: FormSelectWidget,
+  collection: FormCollectionWidget,
+  hidden: FormHiddenWidget,
 };
 
 const FormWidget = ({ type, ...props }) => {
diff --git a/frontend/src/metabase/components/form/StandardForm.jsx b/frontend/src/metabase/components/form/StandardForm.jsx
index dfc1c45431525953e8d348d80e7e85445d9806f5..d13f1cc01555ea5d4ff5528894970566bee933bd 100644
--- a/frontend/src/metabase/components/form/StandardForm.jsx
+++ b/frontend/src/metabase/components/form/StandardForm.jsx
@@ -6,6 +6,7 @@ import FormMessage from "metabase/components/form/FormMessage";
 
 import Button from "metabase/components/Button";
 
+import { t } from "c-3po";
 import cx from "classnames";
 import { getIn } from "icepick";
 
@@ -19,15 +20,16 @@ const StandardForm = ({
   handleSubmit,
   resetForm,
 
-  form,
+  formDef: form,
   className,
-  resetButton = true,
+  resetButton = false,
   newForm = true,
+  onClose = null,
 
   ...props
 }) => (
   <form onSubmit={handleSubmit} className={cx(className, { NewForm: newForm })}>
-    <div className="m1">
+    <div>
       {form.fields(values).map(formField => {
         const nameComponents = formField.name.split(".");
         const field = getIn(fields, nameComponents);
@@ -39,6 +41,7 @@ const StandardForm = ({
             }
             offset={!newForm}
             {...field}
+            hidden={formField.type === "hidden"}
           >
             <FormWidget field={field} offset={!newForm} {...formField} />
             {!newForm && <span className="Form-charm" />}
@@ -46,25 +49,34 @@ const StandardForm = ({
         );
       })}
     </div>
-    <div className={cx("m1", { "Form-offset": !newForm })}>
-      <Button
-        type="submit"
-        primary
-        disabled={submitting || invalid}
-        className="mr1"
-      >
-        {values.id != null ? "Update" : "Create"}
-      </Button>
-      {resetButton && (
+    <div className={cx("flex", { "Form-offset": !newForm })}>
+      <div className="ml-auto flex align-center">
+        {onClose && (
+          <Button
+            type="button"
+            className="mr1"
+            onClick={onClose}
+          >{t`Cancel`}</Button>
+        )}
         <Button
-          type="button"
-          disabled={submitting || !dirty}
-          onClick={resetForm}
+          type="submit"
+          primary={!(submitting || invalid)}
+          disabled={submitting || invalid}
+          className="mr1"
         >
-          Reset
+          {values.id != null ? t`Update` : t`Create`}
         </Button>
-      )}
-      {error && <FormMessage message={error} formError />}
+        {resetButton && (
+          <Button
+            type="button"
+            disabled={submitting || !dirty}
+            onClick={resetForm}
+          >
+            {t`Reset`}
+          </Button>
+        )}
+        {error && <FormMessage message={error} formError />}
+      </div>
     </div>
   </form>
 );
diff --git a/frontend/src/metabase/components/form/widgets/FormCollectionWidget.jsx b/frontend/src/metabase/components/form/widgets/FormCollectionWidget.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..cd66d10e87652ab6df48fe6c1517781cbc663947
--- /dev/null
+++ b/frontend/src/metabase/components/form/widgets/FormCollectionWidget.jsx
@@ -0,0 +1,7 @@
+import React from "react";
+
+import CollectionSelect from "metabase/containers/CollectionSelect";
+
+const FormCollectionWidget = ({ field }) => <CollectionSelect {...field} />;
+
+export default FormCollectionWidget;
diff --git a/frontend/src/metabase/components/form/widgets/FormColorWidget.jsx b/frontend/src/metabase/components/form/widgets/FormColorWidget.jsx
index 91d93194dded04c016bdaa41a18120c6f8cd13bf..5c8cd379214dc97f11db7b7b3b5bdb69479755ab 100644
--- a/frontend/src/metabase/components/form/widgets/FormColorWidget.jsx
+++ b/frontend/src/metabase/components/form/widgets/FormColorWidget.jsx
@@ -3,9 +3,15 @@ import React from "react";
 import ColorPicker from "metabase/components/ColorPicker";
 import cx from "classnames";
 
-const FormColorWidget = ({ field, offset }) => (
+const FormColorWidget = ({ field, offset, initial }) => (
   <div className={cx({ "Form-offset": offset })}>
-    <ColorPicker {...field} />
+    <ColorPicker
+      {...field}
+      value={
+        // if the field has a value use that, otherwise use the initial
+        field.value || initial()
+      }
+    />
   </div>
 );
 
diff --git a/frontend/src/metabase/components/form/widgets/FormHiddenWidget.jsx b/frontend/src/metabase/components/form/widgets/FormHiddenWidget.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..75e412d396c26f1e313075d9416140d871c3d5d3
--- /dev/null
+++ b/frontend/src/metabase/components/form/widgets/FormHiddenWidget.jsx
@@ -0,0 +1,9 @@
+import React from "react";
+
+import { formDomOnlyProps } from "metabase/lib/redux";
+
+const FormHiddenWidget = ({ type = "hidden", field }) => (
+  <input type={type} {...formDomOnlyProps(field)} />
+);
+
+export default FormHiddenWidget;
diff --git a/frontend/src/metabase/containers/AddToDashSelectDashModal.jsx b/frontend/src/metabase/containers/AddToDashSelectDashModal.jsx
index 4fd911835d4c7f376949225bbe5b69b38b194437..f7aeeea0e32ada70829ea53cc93502a83480b6ea 100644
--- a/frontend/src/metabase/containers/AddToDashSelectDashModal.jsx
+++ b/frontend/src/metabase/containers/AddToDashSelectDashModal.jsx
@@ -1,30 +1,20 @@
 /* @flow  */
 
 import React, { Component } from "react";
-import { connect } from "react-redux";
+import { t } from "c-3po";
+import { Flex } from "grid-styled";
 
-import CreateDashboardModal from "metabase/components/CreateDashboardModal.jsx";
-import Icon from "metabase/components/Icon.jsx";
+import Icon from "metabase/components/Icon";
+import Link from "metabase/components/Link";
 import ModalContent from "metabase/components/ModalContent.jsx";
-import SortableItemList from "metabase/components/SortableItemList.jsx";
-import * as Urls from "metabase/lib/urls";
+import DashboardForm from "metabase/containers/DashboardForm.jsx";
+import DashboardPicker from "metabase/containers/DashboardPicker";
 
-import Dashboards from "metabase/entities/dashboards";
+import * as Urls from "metabase/lib/urls";
 
-import { t } from "c-3po";
 import type { Dashboard, DashboardId } from "metabase/meta/types/Dashboard";
 import type { Card } from "metabase/meta/types/Card";
 
-const mapStateToProps = state => ({
-  dashboards: Dashboards.selectors.getList(state),
-});
-
-const mapDispatchToProps = {
-  fetchDashboards: Dashboards.actions.fetchList,
-  createDashboard: Dashboards.actions.create,
-};
-
-@connect(mapStateToProps, mapDispatchToProps)
 export default class AddToDashSelectDashModal extends Component {
   state = {
     shouldCreateDashboard: false,
@@ -35,15 +25,9 @@ export default class AddToDashSelectDashModal extends Component {
     onClose: () => void,
     onChangeLocation: string => void,
     // via connect:
-    dashboards: Dashboard[],
-    fetchDashboards: () => any,
     createDashboard: Dashboard => any,
   };
 
-  componentWillMount() {
-    this.props.fetchDashboards();
-  }
-
   addToDashboard = (dashboardId: DashboardId) => {
     // we send the user over to the chosen dashboard in edit mode with the current card added
     this.props.onChangeLocation(
@@ -51,53 +35,32 @@ export default class AddToDashSelectDashModal extends Component {
     );
   };
 
-  createDashboard = async (newDashboard: Dashboard) => {
-    try {
-      const action = await this.props.createDashboard(newDashboard);
-      this.addToDashboard(action.payload.result);
-    } catch (e) {
-      console.log("createDashboard failed", e);
-    }
-  };
-
   render() {
-    if (this.props.dashboards === null) {
-      return <div />;
-    } else if (
-      this.props.dashboards.length === 0 ||
-      this.state.shouldCreateDashboard === true
-    ) {
+    if (this.state.shouldCreateDashboard) {
       return (
-        <CreateDashboardModal
-          createDashboard={this.createDashboard}
-          onClose={this.props.onClose}
+        <DashboardForm
+          dashboard={{ collection_id: this.props.card.collection_id }}
+          onSaved={dashboard => this.addToDashboard(dashboard.id)}
+          onClose={() => this.setState({ shouldCreateDashboard: false })}
         />
       );
     } else {
       return (
         <ModalContent
           id="AddToDashSelectDashModal"
-          title={t`Add Question to Dashboard`}
+          title={t`Add this question to a dashboard`}
           onClose={this.props.onClose}
         >
-          <div className="flex flex-column">
-            <div
-              className="link flex-align-right px4 cursor-pointer"
-              onClick={() => this.setState({ shouldCreateDashboard: true })}
-            >
-              <div
-                className="mt1 flex align-center absolute"
-                style={{ right: 40 }}
-              >
-                <Icon name="add" size={16} />
-                <h3 className="ml1">{t`Add to new dashboard`}</h3>
-              </div>
-            </div>
-            <SortableItemList
-              items={this.props.dashboards}
-              onClickItemFn={dashboard => this.addToDashboard(dashboard.id)}
-            />
-          </div>
+          <DashboardPicker onChange={this.addToDashboard} />
+          <Link
+            mt={1}
+            onClick={() => this.setState({ shouldCreateDashboard: true })}
+          >
+            <Flex align="center" className="text-brand" py={2}>
+              <Icon name="add" mx={1} bordered />
+              <h4>{t`Create a new dashboard`}</h4>
+            </Flex>
+          </Link>
         </ModalContent>
       );
     }
diff --git a/frontend/src/metabase/containers/CollectionMoveModal.jsx b/frontend/src/metabase/containers/CollectionMoveModal.jsx
index 2ee3045f31e948878a67742f8db3d652d3322d76..8792b8b6bbada1f33815c94efaad6c84a3592939 100644
--- a/frontend/src/metabase/containers/CollectionMoveModal.jsx
+++ b/frontend/src/metabase/containers/CollectionMoveModal.jsx
@@ -1,7 +1,6 @@
 import React from "react";
 import PropTypes from "prop-types";
 
-import _ from "underscore";
 import { t } from "c-3po";
 
 import { Flex, Box } from "grid-styled";
@@ -9,7 +8,6 @@ import Subhead from "metabase/components/Subhead";
 import Button from "metabase/components/Button";
 import Icon from "metabase/components/Icon";
 
-import CollectionListLoader from "metabase/containers/CollectionListLoader";
 import CollectionPicker from "metabase/containers/CollectionPicker";
 
 class CollectionMoveModal extends React.Component {
@@ -24,10 +22,7 @@ class CollectionMoveModal extends React.Component {
       //  null = root collection
       //  number = non-root collection id
       //
-      selectedCollection:
-        props.initialCollectionId === undefined
-          ? undefined
-          : { id: props.initialCollectionId },
+      selectedCollectionId: props.initialCollectionId,
       // whether the move action has started
       // TODO: use this loading and error state in the UI
       moving: false,
@@ -43,7 +38,7 @@ class CollectionMoveModal extends React.Component {
   };
 
   render() {
-    const { selectedCollection } = this.state;
+    const { selectedCollectionId } = this.state;
 
     return (
       <Box p={3}>
@@ -55,29 +50,24 @@ class CollectionMoveModal extends React.Component {
             onClick={() => this.props.onClose()}
           />
         </Flex>
-        <CollectionListLoader>
-          {({ collections, loading, error }) => (
-            <CollectionPicker
-              value={selectedCollection && selectedCollection.id}
-              onChange={id =>
-                this.setState({
-                  selectedCollection:
-                    id == null ? null : _.find(collections, { id }),
-                })
-              }
-              collections={collections}
-            />
-          )}
-        </CollectionListLoader>
+        <CollectionPicker
+          value={selectedCollectionId}
+          onChange={selectedCollectionId =>
+            this.setState({ selectedCollectionId })
+          }
+        />
         <Flex mt={2}>
           <Button
             primary
             className="ml-auto"
-            disabled={selectedCollection === undefined}
+            disabled={
+              selectedCollectionId === undefined ||
+              selectedCollectionId === this.props.initialCollectionId
+            }
             onClick={() => {
               try {
                 this.setState({ moving: true });
-                this.props.onMove(selectedCollection);
+                this.props.onMove({ id: selectedCollectionId });
               } catch (e) {
                 this.setState({ error: e });
               } finally {
diff --git a/frontend/src/metabase/containers/CollectionName.jsx b/frontend/src/metabase/containers/CollectionName.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..02ce10a6478bcfd82611e3ec33c5f08461340249
--- /dev/null
+++ b/frontend/src/metabase/containers/CollectionName.jsx
@@ -0,0 +1,22 @@
+import React from "react";
+
+import { entityObjectLoader } from "metabase/entities/containers/EntityObjectLoader";
+import { ROOT_COLLECTION } from "metabase/entities/collections";
+
+const CollectionNameLoader = entityObjectLoader({
+  entityType: "collections",
+  properties: ["name"],
+  loadingAndErrorWrapper: false,
+})(({ object }) => <span>{object && object.name}</span>);
+
+const CollectionName = ({ collectionId }) => {
+  if (collectionId === undefined || isNaN(collectionId)) {
+    return null;
+  } else if (collectionId === "root" || collectionId === null) {
+    return <span>{ROOT_COLLECTION.name}</span>;
+  } else {
+    return <CollectionNameLoader entityId={collectionId} />;
+  }
+};
+
+export default CollectionName;
diff --git a/frontend/src/metabase/containers/CollectionPicker.jsx b/frontend/src/metabase/containers/CollectionPicker.jsx
index 4c4ea07d64a9d8ff169ef902c958425706065cf1..a1a528a309bf0a1a52dde08d033135e9e5577959 100644
--- a/frontend/src/metabase/containers/CollectionPicker.jsx
+++ b/frontend/src/metabase/containers/CollectionPicker.jsx
@@ -1,106 +1,22 @@
 import React from "react";
 import PropTypes from "prop-types";
-import cx from "classnames";
 
-import { Flex, Box } from "grid-styled";
-import Icon from "metabase/components/Icon";
-import Breadcrumbs from "metabase/components/Breadcrumbs";
-
-import { getExpandedCollectionsById } from "metabase/entities/collections";
-
-const COLLECTION_ICON_COLOR = "#DCE1E4";
-
-const isRoot = collection => collection.id === "root" || collection.id == null;
-
-const getCollectionValue = collection =>
-  collection.id === "root" ? null : collection.id;
-
-export default class CollectionPicker extends React.Component {
-  constructor(props) {
-    super(props);
-    this.state = {
-      parentId: "root",
-    };
-  }
-
-  static propTypes = {
-    // undefined = no selection
-    // null = root collection
-    // number = non-root collection id
-    value: PropTypes.number,
-  };
-
-  // returns a list of "crumbs" starting with the "root" collection
-  _getCrumbs(collection, collectionsById) {
-    if (collection && collection.path) {
-      return [
-        ...collection.path.map(id => [
-          collectionsById[id].name,
-          () => this.setState({ parentId: id }),
-        ]),
-        [collection.name],
-      ];
-    } else {
-      return [
-        [
-          collectionsById["root"].name,
-          () => this.setState({ parentId: collectionsById["root"].id }),
-        ],
-        ["Unknown"],
-      ];
-    }
-  }
-
-  render() {
-    const { value, onChange, collections, style, className } = this.props;
-    const { parentId } = this.state;
-
-    const collectionsById = getExpandedCollectionsById(collections);
-    const collection = collectionsById[parentId];
-    const crumbs = this._getCrumbs(collection, collectionsById);
-
-    let items = (collection && collection.children) || [];
-
-    // show root in itself
-    if (collection && isRoot(collection)) {
-      items = [collection, ...items];
-    }
-
-    return (
-      <Box style={style} className={className}>
-        <Box pb={1} mb={2} className="border-bottom">
-          <Breadcrumbs crumbs={crumbs} />
-        </Box>
-        {items.map(collection => (
-          <Box
-            mt={1}
-            p={1}
-            onClick={() => onChange(getCollectionValue(collection))}
-            className={cx(
-              "bg-brand-hover text-white-hover cursor-pointer rounded",
-              {
-                "bg-brand text-white": value === getCollectionValue(collection),
-              },
-            )}
-          >
-            <Flex align="center">
-              <Icon name="all" color={COLLECTION_ICON_COLOR} size={32} />
-              <h4 className="mx1">{collection.name}</h4>
-              {collection.children.length > 0 &&
-                !isRoot(collection) && (
-                  <Icon
-                    name="chevronright"
-                    className="p1 ml-auto circular text-grey-2 border-grey-2 bordered bg-white-hover cursor-pointer"
-                    onClick={e => {
-                      e.stopPropagation();
-                      this.setState({ parentId: collection.id });
-                    }}
-                  />
-                )}
-            </Flex>
-          </Box>
-        ))}
-      </Box>
-    );
-  }
-}
+import ItemPicker from "./ItemPicker";
+
+const CollectionPicker = ({ value, onChange, ...props }) => (
+  <ItemPicker
+    {...props}
+    value={value === undefined ? undefined : { model: "collection", id: value }}
+    onChange={collection => onChange(collection ? collection.id : undefined)}
+    models={["collection"]}
+  />
+);
+
+CollectionPicker.propTypes = {
+  // a collection ID or null (for "root" collection), or undefined if none selected
+  value: PropTypes.number,
+  // callback that takes a collection ID or null (for "root" collection)
+  onChange: PropTypes.func.isRequired,
+};
+
+export default CollectionPicker;
diff --git a/frontend/src/metabase/containers/CollectionSelect.jsx b/frontend/src/metabase/containers/CollectionSelect.jsx
index eec3e1ac19daa40855cb04b7fd694154b50b37d6..65ae7e0766af40c66bbbafc5fb0c2d1cb2307d2b 100644
--- a/frontend/src/metabase/containers/CollectionSelect.jsx
+++ b/frontend/src/metabase/containers/CollectionSelect.jsx
@@ -1,56 +1,12 @@
-import React from "react";
-import PropTypes from "prop-types";
-import _ from "underscore";
-
-import PopoverWithTrigger from "metabase/components/PopoverWithTrigger";
-import CollectionListLoader from "metabase/containers/CollectionListLoader";
-import { SelectButton } from "metabase/components/Select";
+import ItemSelect from "./ItemSelect";
 
 import CollectionPicker from "./CollectionPicker";
-import { ROOT_COLLECTION } from "metabase/entities/collections";
+import CollectionName from "./CollectionName";
 
-export default class CollectionSelect extends React.Component {
-  static propTypes = {
-    field: PropTypes.object.isRequired,
-    // optional collectionId to filter out so you can't move a collection into itself
-    collectionId: PropTypes.number,
-  };
+const QuestionSelect = ItemSelect(
+  CollectionPicker,
+  CollectionName,
+  "collection",
+);
 
-  render() {
-    const { value, onChange, collectionId } = this.props;
-    return (
-      <CollectionListLoader>
-        {({ collections }) => (
-          <PopoverWithTrigger
-            triggerElement={
-              <SelectButton hasValue={value !== undefined}>
-                {(_.findWhere(collections, { id: value }) || {}).name ||
-                  ROOT_COLLECTION.name}
-              </SelectButton>
-            }
-            sizeToFit
-            pinInitialAttachment
-          >
-            {({ onClose }) => (
-              <CollectionPicker
-                style={{ minWidth: 300 }}
-                className="p2 overflow-auto"
-                value={value}
-                onChange={value => {
-                  onChange(value);
-                  onClose();
-                }}
-                collections={
-                  // don't want to allow moving a collection into itself, so filter it out
-                  collectionId != null
-                    ? collections.filter(c => c.id != collectionId)
-                    : collections
-                }
-              />
-            )}
-          </PopoverWithTrigger>
-        )}
-      </CollectionListLoader>
-    );
-  }
-}
+export default QuestionSelect;
diff --git a/frontend/src/metabase/containers/DashboardForm.jsx b/frontend/src/metabase/containers/DashboardForm.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..be722f4350473137cb77b7082027e1fef4f2db33
--- /dev/null
+++ b/frontend/src/metabase/containers/DashboardForm.jsx
@@ -0,0 +1,22 @@
+import React from "react";
+import { t } from "c-3po";
+import EntityForm from "metabase/entities/containers/EntityForm";
+import ModalContent from "metabase/components/ModalContent";
+
+const DashboardForm = ({ dashboard, onClose, ...props }) => (
+  <ModalContent
+    title={
+      dashboard && dashboard.id != null ? dashboard.name : t`Create dashboard`
+    }
+    onClose={onClose}
+  >
+    <EntityForm
+      entityType="dashboards"
+      entityObject={dashboard}
+      onClose={onClose}
+      {...props}
+    />
+  </ModalContent>
+);
+
+export default DashboardForm;
diff --git a/frontend/src/metabase/containers/DashboardPicker.jsx b/frontend/src/metabase/containers/DashboardPicker.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..0b7a65eb2fd6452c0e105ab9811fcef740189c85
--- /dev/null
+++ b/frontend/src/metabase/containers/DashboardPicker.jsx
@@ -0,0 +1,22 @@
+import React from "react";
+import PropTypes from "prop-types";
+
+import ItemPicker from "./ItemPicker";
+
+const DashboardPicker = ({ value, onChange, ...props }) => (
+  <ItemPicker
+    {...props}
+    value={value === undefined ? undefined : { model: "dashboard", id: value }}
+    onChange={dashboard => onChange(dashboard ? dashboard.id : undefined)}
+    models={["dashboard"]}
+  />
+);
+
+DashboardPicker.propTypes = {
+  // a dashboard ID or null
+  value: PropTypes.number,
+  // callback that takes a dashboard ID
+  onChange: PropTypes.func.isRequired,
+};
+
+export default DashboardPicker;
diff --git a/frontend/src/metabase/containers/EntitySearch.jsx b/frontend/src/metabase/containers/EntitySearch.jsx
index 968ba9d9357e94615df582dc74e0748942b3b987..45f7d09c3251ec045a6a9c6609571f6238edc909 100644
--- a/frontend/src/metabase/containers/EntitySearch.jsx
+++ b/frontend/src/metabase/containers/EntitySearch.jsx
@@ -16,6 +16,8 @@ import { KEYCODE_DOWN, KEYCODE_ENTER, KEYCODE_UP } from "metabase/lib/keyboard";
 import { LocationDescriptor } from "metabase/meta/types/index";
 import { parseHashOptions, updateQueryString } from "metabase/lib/browser";
 
+import colors from "metabase/lib/colors";
+
 const PAGE_SIZE = 10;
 
 const SEARCH_GROUPINGS = [
@@ -183,7 +185,7 @@ export default class EntitySearch extends Component {
       filteredEntities.length > 0;
 
     return (
-      <div className="bg-slate-extra-light full Entity-search">
+      <div className="bg-light full Entity-search">
         <div className="wrapper wrapper--small pt4 pb4">
           <div className="flex mb4 align-center" style={{ height: "50px" }}>
             <div
@@ -233,8 +235,8 @@ export default class EntitySearch extends Component {
                 <EmptyState
                   message={
                     <div className="mt4">
-                      <h3 className="text-grey-5">{t`No results found`}</h3>
-                      <p className="text-grey-4">{t`Try adjusting your filter to find what you’re looking for.`}</p>
+                      <h3 className="text-medium">{t`No results found`}</h3>
+                      <p className="text-medium">{t`Try adjusting your filter to find what you’re looking for.`}</p>
                     </div>
                   }
                   image="app/img/empty_question"
@@ -285,7 +287,7 @@ export class SearchGroupingOption extends Component {
       <li
         className={cx(
           "my2 cursor-pointer text-uppercase text-small text-green-saturated-hover",
-          { "text-grey-4": !active },
+          { "text-medium": !active },
           { "text-green-saturated": active },
         )}
         onClick={this.onSetGrouping}
@@ -447,8 +449,12 @@ export const SearchResultsGroup = ({
 }) => (
   <div>
     {groupName !== null && (
-      <div className="flex align-center bg-slate-almost-extra-light bordered mt3 px3 py2">
-        <Icon className="mr1" style={{ color: "#BCC5CA" }} name={groupIcon} />
+      <div className="flex align-center bg-medium bordered mt3 px3 py2">
+        <Icon
+          className="mr1"
+          style={{ color: colors["text-light"] }}
+          name={groupIcon}
+        />
         <h4>{groupName}</h4>
       </div>
     )}
@@ -491,8 +497,8 @@ class SearchResultsList extends Component {
         <span
           className={cx(
             "mx1 flex align-center justify-center rounded",
-            { "cursor-pointer bg-grey-2 text-white": !isInBeginning },
-            { "bg-grey-0 text-grey-1": isInBeginning },
+            { "cursor-pointer bg-medium text-white": !isInBeginning },
+            { "bg-light text-light": isInBeginning },
           )}
           style={{ width: "22px", height: "22px" }}
           onClick={() =>
@@ -504,8 +510,8 @@ class SearchResultsList extends Component {
         <span
           className={cx(
             "flex align-center justify-center rounded",
-            { "cursor-pointer bg-grey-2 text-white": !isInEnd },
-            { "bg-grey-0 text-grey-2": isInEnd },
+            { "cursor-pointer bg-medium text-white": !isInEnd },
+            { "bg-light text-light": isInEnd },
           )}
           style={{ width: "22px", height: "22px" }}
           onClick={() => !isInEnd && setCurrentPage(entities, currentPage + 1)}
@@ -581,8 +587,8 @@ export class SearchResultListItem extends Component {
       <li>
         <Link
           className={cx(
-            "no-decoration flex py2 px3 cursor-pointer bg-slate-extra-light-hover border-bottom",
-            { "bg-grey-0": highlight },
+            "no-decoration flex py2 px3 cursor-pointer bg-light-hover border-bottom",
+            { "bg-light": highlight },
           )}
           to={getUrlForEntity(entity)}
         >
diff --git a/frontend/src/metabase/containers/ErrorPages.jsx b/frontend/src/metabase/containers/ErrorPages.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..c5dde4942db6b897f05117c95de22d8d1b66c552
--- /dev/null
+++ b/frontend/src/metabase/containers/ErrorPages.jsx
@@ -0,0 +1,68 @@
+import React from "react";
+import { Flex } from "grid-styled";
+import { t } from "c-3po";
+
+import * as Urls from "metabase/lib/urls";
+import fitViewport from "metabase/hoc/FitViewPort";
+
+import Icon from "metabase/components/Icon";
+import EmptyState from "metabase/components/EmptyState";
+import ErrorDetails from "metabase/components/ErrorDetails";
+
+const ErrorPageWrapper = fitViewport(({ fitClassNames, children }) => (
+  <Flex
+    align="center"
+    flexDirection="column"
+    justify="center"
+    className={fitClassNames}
+  >
+    {children}
+  </Flex>
+));
+
+export const GenericError = ({
+  title = t`Something's gone wrong`,
+  message = t`We've run into an error. You can try refreshing the page, or just go back.`,
+  details = null,
+}) => (
+  <ErrorPageWrapper>
+    <EmptyState
+      title={title}
+      message={message}
+      illustrationElement={
+        <div className="QueryError-image QueryError-image--serverError" />
+      }
+    />
+    <ErrorDetails className="pt2" details={details} centered />
+  </ErrorPageWrapper>
+);
+
+export const NotFound = () => (
+  <ErrorPageWrapper>
+    <EmptyState
+      illustrationElement={<img src="../app/assets/img/no_results.svg" />}
+      title={t`We're a little lost...`}
+      message={t`The page you asked for couldn't be found.`}
+      link={Urls.question()}
+    />
+  </ErrorPageWrapper>
+);
+
+export const Unauthorized = () => (
+  <ErrorPageWrapper>
+    <EmptyState
+      title={t`Sorry, you don’t have permission to see that.`}
+      illustrationElement={<Icon name="key" size={100} />}
+    />
+  </ErrorPageWrapper>
+);
+
+export const Archived = ({ entityName, linkTo }) => (
+  <ErrorPageWrapper>
+    <EmptyState
+      title={t`This ${entityName} has been archived`}
+      illustrationElement={<Icon name="viewArchive" size={100} />}
+      link={linkTo}
+    />
+  </ErrorPageWrapper>
+);
diff --git a/frontend/src/metabase/containers/Form.jsx b/frontend/src/metabase/containers/Form.jsx
index fd53a4ccfc73339bfc294559d38ad9a71dc6fc40..6859c7de7703db1c41ee7dd21ab7de1ee6ef3956 100644
--- a/frontend/src/metabase/containers/Form.jsx
+++ b/frontend/src/metabase/containers/Form.jsx
@@ -45,6 +45,7 @@ type Props = {
   initialValues?: ?FormValues,
   formName?: string,
   onSubmit: (values: FormValues) => Promise<any>,
+  formComponent?: React$Component<any, any, any>,
 };
 
 let FORM_ID = 0;
@@ -69,14 +70,17 @@ export default class Form_ extends React.Component {
   };
 
   static defaultProps = {
-    children: StandardForm,
+    formComponent: StandardForm,
   };
 
   // dynamically generates a component decorated with reduxForm
   _updateFormComponent(props: Props) {
     if (this.props.form) {
       const form = makeForm(this.props.form);
-      const initialValues = this.props.initialValues || form.initial();
+      const initialValues = {
+        ...form.initial(),
+        ...(this.props.initialValues || {}),
+      };
       // redux-form config:
       const formConfig = {
         form: this._formName,
@@ -88,13 +92,17 @@ export default class Form_ extends React.Component {
       const mapStateToProps = (state, ownProps) => {
         const values = getValues(state.form[this._formName]);
         if (values) {
-          return { ...formConfig, fields: form.fieldNames(values) };
+          return {
+            ...formConfig,
+            fields: form.fieldNames(values),
+            formDef: form,
+          };
         } else {
-          return formConfig;
+          return { ...formConfig, formDef: form };
         }
       };
       this._FormComponent = reduxForm(formConfig, mapStateToProps)(
-        ({ children, ...props }) => children({ ...props, form }),
+        props.formComponent,
       );
     }
   }
diff --git a/frontend/src/metabase/containers/ItemPicker.jsx b/frontend/src/metabase/containers/ItemPicker.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..d2fa5719a6fcba39f414e8fcf70aa2f019c17fcc
--- /dev/null
+++ b/frontend/src/metabase/containers/ItemPicker.jsx
@@ -0,0 +1,261 @@
+import React from "react";
+import PropTypes from "prop-types";
+import cx from "classnames";
+
+import { Flex, Box } from "grid-styled";
+import Icon from "metabase/components/Icon";
+import Breadcrumbs from "metabase/components/Breadcrumbs";
+
+import colors from "metabase/lib/colors";
+
+import { connect } from "react-redux";
+import EntityListLoader, {
+  entityListLoader,
+} from "metabase/entities/containers/EntityListLoader";
+
+import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
+import Collections from "metabase/entities/collections";
+
+const COLLECTION_ICON_COLOR = colors["text-light"];
+
+const isRoot = collection => collection.id === "root" || collection.id == null;
+
+@entityListLoader({
+  entityType: "collections",
+  loadingAndErrorWrapper: false,
+})
+@connect((state, props) => ({
+  collectionsById: Collections.selectors.getExpandedCollectionsById(state),
+}))
+export default class ItemPicker extends React.Component {
+  constructor(props) {
+    super(props);
+    this.state = {
+      parentId: "root",
+      searchMode: false,
+      searchString: false,
+    };
+  }
+
+  static propTypes = {
+    // undefined = no selection
+    // null = root collection
+    // number = non-root collection id
+    value: PropTypes.number,
+    types: PropTypes.array,
+  };
+
+  // returns a list of "crumbs" starting with the "root" collection
+  _getCrumbs(collection, collectionsById) {
+    if (collection && collection.path) {
+      return [
+        ...collection.path.map(id => [
+          collectionsById[id].name,
+          () => this.setState({ parentId: id }),
+        ]),
+        [collection.name],
+      ];
+    } else {
+      return [
+        [
+          collectionsById["root"].name,
+          () => this.setState({ parentId: collectionsById["root"].id }),
+        ],
+        ["Unknown"],
+      ];
+    }
+  }
+
+  render() {
+    const { value, onChange, collectionsById, style, className } = this.props;
+    const { parentId, searchMode, searchString } = this.state;
+
+    const models = new Set(this.props.models);
+    const modelsIncludeNonCollections =
+      this.props.models.filter(model => model !== "collection").length > 0;
+
+    const collection = collectionsById[parentId];
+    const crumbs = this._getCrumbs(collection, collectionsById);
+
+    let allCollections = (collection && collection.children) || [];
+
+    // show root in itself if we can pick it
+    if (collection && isRoot(collection) && models.has("collection")) {
+      allCollections = [collection, ...allCollections];
+    }
+
+    // code below assumes items have a "model" property
+    allCollections = allCollections.map(collection => ({
+      ...collection,
+      model: "collection",
+    }));
+
+    // special case for root collection
+    const getId = item =>
+      item &&
+      (item.model === "collection" && item.id === null ? "root" : item.id);
+
+    const isSelected = item =>
+      item &&
+      value &&
+      getId(item) === getId(value) &&
+      (models.size === 1 || item.model === value.model);
+
+    return (
+      <LoadingAndErrorWrapper loading={!collectionsById} className="scroll-y">
+        <Box style={style} className={cx(className, "scroll-y")}>
+          {searchMode ? (
+            <Box pb={1} mb={2} className="border-bottom flex align-center">
+              <input
+                type="search"
+                className="input rounded flex-full"
+                placeholder="Search"
+                autoFocus
+                onKeyPress={e => {
+                  if (e.key === "Enter") {
+                    this.setState({ searchString: e.target.value });
+                  }
+                }}
+              />
+              <Icon
+                name="close"
+                className="ml-auto pl2 text-light text-medium-hover cursor-pointer"
+                onClick={() =>
+                  this.setState({ searchMode: null, searchString: null })
+                }
+              />
+            </Box>
+          ) : (
+            <Box pb={1} mb={2} className="border-bottom flex align-center">
+              <Breadcrumbs crumbs={crumbs} />
+              <Icon
+                name="search"
+                className="ml-auto pl2 text-light text-medium-hover cursor-pointer"
+                onClick={() => this.setState({ searchMode: true })}
+              />
+            </Box>
+          )}
+          <Box className="scroll-y">
+            {!searchString
+              ? allCollections.map(collection => {
+                  const hasChildren =
+                    (collection.children &&
+                      collection.children.length > 0 &&
+                      // exclude root since we show root's subcollections alongside it
+                      !isRoot(collection)) ||
+                    // non-collection models are loaded on-demand so we don't know ahead of time
+                    // if they have children, so we have to assume they do
+                    modelsIncludeNonCollections;
+                  // NOTE: this assumes the only reason you'd be selecting a collection is to modify it in some way
+                  const canSelect =
+                    models.has("collection") && collection.can_write;
+                  // only show if collection can be selected or has children
+                  return canSelect || hasChildren ? (
+                    <Item
+                      item={collection}
+                      name={collection.name}
+                      color={COLLECTION_ICON_COLOR}
+                      icon="all"
+                      selected={canSelect && isSelected(collection)}
+                      canSelect={canSelect}
+                      hasChildren={hasChildren}
+                      onChange={collection =>
+                        isRoot(collection)
+                          ? // "root" collection should have `null` id
+                            onChange({ id: null, model: "collection" })
+                          : onChange(collection)
+                      }
+                      onChangeParentId={parentId => this.setState({ parentId })}
+                    />
+                  ) : null;
+                })
+              : null}
+            {(modelsIncludeNonCollections || searchString) && (
+              <EntityListLoader
+                entityType="search"
+                entityQuery={{
+                  ...(searchString
+                    ? { q: searchString }
+                    : { collection: parentId }),
+                  ...(models.size === 1
+                    ? { model: Array.from(models)[0] }
+                    : {}),
+                }}
+                wrapped
+              >
+                {({ list }) =>
+                  list
+                    .filter(
+                      item =>
+                        // remove collections unless we're searching
+                        (item.model !== "collection" || !!searchString) &&
+                        // only include desired models (TODO: ideally the endpoint would handle this)
+                        models.has(item.model),
+                    )
+                    .map(item => (
+                      <Item
+                        item={item}
+                        name={item.getName()}
+                        color={item.getColor()}
+                        icon={item.getIcon()}
+                        selected={isSelected(item)}
+                        canSelect
+                        onChange={onChange}
+                      />
+                    ))
+                }
+              </EntityListLoader>
+            )}
+          </Box>
+        </Box>
+      </LoadingAndErrorWrapper>
+    );
+  }
+}
+
+const Item = ({
+  item,
+  name,
+  icon,
+  color,
+  selected,
+  canSelect,
+  hasChildren,
+  onChange,
+  onChangeParentId,
+}) => (
+  <Box
+    mt={1}
+    p={1}
+    onClick={
+      canSelect
+        ? () => onChange(item)
+        : hasChildren ? () => onChangeParentId(item.id) : null
+    }
+    className={cx("rounded", {
+      "bg-brand text-white": selected,
+      "bg-brand-hover text-white-hover cursor-pointer":
+        canSelect || hasChildren,
+    })}
+  >
+    <Flex align="center">
+      <Icon name={icon} color={selected ? "white" : color} size={22} />
+      <h4 className="mx1">{name}</h4>
+      {hasChildren && (
+        <Icon
+          name="chevronright"
+          className={cx(
+            "p1 ml-auto circular text-light border-grey-2 bordered bg-white-hover cursor-pointer",
+            {
+              "bg-brand-hover": !canSelect,
+            },
+          )}
+          onClick={e => {
+            e.stopPropagation();
+            onChangeParentId(item.id);
+          }}
+        />
+      )}
+    </Flex>
+  </Box>
+);
diff --git a/frontend/src/metabase/containers/ItemSelect.jsx b/frontend/src/metabase/containers/ItemSelect.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..7f64b90537893d29efd33f7c669a2704defb5f3c
--- /dev/null
+++ b/frontend/src/metabase/containers/ItemSelect.jsx
@@ -0,0 +1,91 @@
+import React from "react";
+import ReactDOM from "react-dom";
+import PropTypes from "prop-types";
+
+import { t } from "c-3po";
+
+import PopoverWithTrigger from "metabase/components/PopoverWithTrigger";
+import SelectButton from "metabase/components/SelectButton";
+
+const MIN_POPOVER_WIDTH = 300;
+
+export default (PickerComponent, NameComponent, type) =>
+  class ItemSelect extends React.Component {
+    state = {
+      width: MIN_POPOVER_WIDTH,
+    };
+
+    static propTypes = {
+      // collection ID, null (for root collection), or undefined
+      value: PropTypes.number,
+      onChange: PropTypes.func.isRequired,
+      field: PropTypes.object.isRequired,
+      // optional collectionId to filter out so you can't move a collection into itself
+      collectionId: PropTypes.number,
+      // make the popover content inherit the select widget's width
+      inheritWidth: PropTypes.bool,
+    };
+
+    static defaultProps = {
+      placeholder: t`Select a ${type}`,
+      inheritWidth: true,
+    };
+
+    componentDidMount() {
+      this.componentDidUpdate();
+    }
+
+    componentDidUpdate() {
+      // save the width so we can make the poopver content match
+      const { width } = ReactDOM.findDOMNode(this).getBoundingClientRect();
+      if (this.state.width !== width) {
+        this.setState({ width });
+      }
+    }
+
+    render() {
+      const {
+        value,
+        onChange,
+        className,
+        style,
+        placeholder,
+        inheritWidth,
+        ...props
+      } = this.props;
+      return (
+        <PopoverWithTrigger
+          pinInitialAttachment // keep the popover from jumping if content height changes
+          triggerClasses={className}
+          triggerElement={
+            <SelectButton style={style}>
+              {value !== undefined && value !== "" ? (
+                <NameComponent collectionId={value} />
+              ) : (
+                placeholder
+              )}
+            </SelectButton>
+          }
+          sizeToFit
+          autoWidth
+        >
+          {({ onClose }) => (
+            <PickerComponent
+              {...props}
+              style={
+                inheritWidth
+                  ? { width: Math.max(this.state.width, MIN_POPOVER_WIDTH) }
+                  : { minWidth: MIN_POPOVER_WIDTH }
+              }
+              className="p2 overflow-auto"
+              value={value}
+              onChange={itemId => {
+                onChange(itemId);
+                onClose();
+              }}
+            />
+          )}
+        </PopoverWithTrigger>
+      );
+    }
+  };
diff --git a/frontend/src/metabase/containers/Overworld.jsx b/frontend/src/metabase/containers/Overworld.jsx
index 4ee7e733cf2806bb1f9d69d4b6c31b4522473f82..de22ad83f6bb4642f7fca3969662c0e00ecc1bef 100644
--- a/frontend/src/metabase/containers/Overworld.jsx
+++ b/frontend/src/metabase/containers/Overworld.jsx
@@ -1,42 +1,85 @@
 import React from "react";
+import _ from "underscore";
 import { Box, Flex } from "grid-styled";
 import { connect } from "react-redux";
-import { t } from "c-3po";
+import { t, jt } from "c-3po";
 
 import CollectionItemsLoader from "metabase/containers/CollectionItemsLoader";
 import CandidateListLoader from "metabase/containers/CandidateListLoader";
 import { DatabaseListLoader } from "metabase/components/BrowseApp";
 import ExplorePane from "metabase/components/ExplorePane";
+import Tooltip from "metabase/components/Tooltip.jsx";
 
 import * as Urls from "metabase/lib/urls";
-import { normal } from "metabase/lib/colors";
+import colors, { normal } from "metabase/lib/colors";
 
 import Card from "metabase/components/Card";
 import { Grid, GridItem } from "metabase/components/Grid";
 import Icon from "metabase/components/Icon";
 import Link from "metabase/components/Link";
 import Subhead from "metabase/components/Subhead";
+import RetinaImage from "react-retina-image";
 
 import { getUser } from "metabase/home/selectors";
 
+import CollectionList from "metabase/components/CollectionList";
+
+import { ROOT_COLLECTION } from "metabase/entities/collections";
+
 import MetabotLogo from "metabase/components/MetabotLogo";
 import Greeting from "metabase/lib/greeting";
 
-const mapStateToProps = state => ({
-  user: getUser(state),
-});
+import { entityListLoader } from "metabase/entities/containers/EntityListLoader";
+
+const PAGE_PADDING = [1, 2, 4];
+
+import { createSelector } from "reselect";
+
+// use reselect select to avoid re-render if list doesn't change
+const getParitionedCollections = createSelector(
+  [(state, props) => props.list],
+  list => {
+    const [collections, items] = _.partition(
+      list,
+      item => item.model === "collection",
+    );
+    const [pinned, unpinned] = _.partition(
+      items,
+      item => item.collection_position != null,
+    );
+
+    // sort the pinned items by collection_position
+    pinned.sort((a, b) => a.collection_position - b.collection_position);
+    return {
+      collections,
+      pinned,
+      unpinned,
+    };
+  },
+);
 
 //class Overworld extends Zelda
-@connect(mapStateToProps)
+@entityListLoader({
+  entityType: "search",
+  entityQuery: { collection: "root" },
+  wrapped: true,
+})
+@connect((state, props) => ({
+  // split out collections, pinned, and unpinned since bulk actions only apply to unpinned
+  ...getParitionedCollections(state, props),
+  user: getUser(state, props),
+}))
 class Overworld extends React.Component {
   render() {
+    const { user } = this.props;
     return (
-      <Box px={4}>
-        <Flex mt={3} mb={1} align="center">
-          <MetabotLogo />
+      <Box>
+        <Flex px={PAGE_PADDING} pt={3} pb={1} align="center">
+          <Tooltip tooltip={t`Don't tell anyone, but you're my favorite.`}>
+            <MetabotLogo />
+          </Tooltip>
           <Box ml={2}>
-            <Subhead>{Greeting.sayHello(this.props.user.first_name)}</Subhead>
-            <p className="text-paragraph m0 text-grey-3">{t`Don't tell anyone but you're my favorite`}</p>
+            <Subhead>{Greeting.sayHello(user.first_name)}</Subhead>
           </Box>
         </Flex>
         <CollectionItemsLoader collectionId="root">
@@ -49,35 +92,54 @@ class Overworld extends React.Component {
               return (
                 <CandidateListLoader>
                   {({ candidates, sampleCandidates, isSample }) => {
+                    // if there are no items to show then just hide the section
+                    if (!candidates && !sampleCandidates) {
+                      return null;
+                    }
                     return (
-                      <ExplorePane
-                        candidates={candidates}
-                        withMetabot={false}
-                        title=""
-                        gridColumns={1 / 3}
-                        asCards={true}
-                        description={
-                          isSample
-                            ? t`Once you connect your own data, I can show you some automatic explorations called x-rays. Here are some examples with sample data.`
-                            : t`I took a look at the data you just connected, and I have some explorations of interesting things I found. Hope you like them!`
-                        }
-                      />
+                      <Box mx={PAGE_PADDING} mt={[1, 3]}>
+                        {user.is_superuser && <AdminPinMessage />}
+                        <Box mt={[1, 3]}>
+                          <Flex align="center">
+                            <SectionHeading>
+                              {t`Try these x-rays based on your data.`}
+                            </SectionHeading>
+                          </Flex>
+                          <Box>
+                            <ExplorePane
+                              candidates={candidates}
+                              withMetabot={false}
+                              title=""
+                              gridColumns={[1, 1 / 3]}
+                              asCards={true}
+                            />
+                          </Box>
+                        </Box>
+                      </Box>
                     );
                   }}
                 </CandidateListLoader>
               );
             }
 
+            if (items.length === 0) {
+              return null;
+            }
+
             return (
-              <Box>
-                <Box mt={3} mb={1}>
-                  <h4>{t`Pinned dashboards`}</h4>
-                </Box>
-                <Grid w={1 / 3}>
+              <Box px={PAGE_PADDING} mt={2}>
+                <SectionHeading>{t`Start here`}</SectionHeading>
+                <Grid>
                   {pinnedDashboards.map(pin => {
                     return (
-                      <GridItem>
+                      <GridItem
+                        w={[1, 1 / 2, 1 / 3]}
+                        key={`${pin.model}-${pin.id}`}
+                      >
                         <Link
+                          data-metabase-event={`Homepage;Pinned Item Click;Pin Type ${
+                            pin.model
+                          }`}
                           to={Urls.dashboard(pin.id)}
                           hover={{ color: normal.blue }}
                         >
@@ -96,58 +158,182 @@ class Overworld extends React.Component {
                       </GridItem>
                     );
                   })}
-                  <GridItem>
-                    <Link
-                      to="/collection/root"
-                      color={normal.grey2}
-                      className="text-brand-hover"
-                    >
-                      <Flex p={4} align="center">
-                        <h3>See more items</h3>
-                        <Icon name="chevronright" size={14} ml={1} />
-                      </Flex>
-                    </Link>
-                  </GridItem>
                 </Grid>
               </Box>
             );
           }}
         </CollectionItemsLoader>
 
-        <Box mt={4}>
-          <h4>{t`Our data`}</h4>
-          <Box mt={2}>
-            <DatabaseListLoader>
-              {({ databases }) => {
-                return (
-                  <Grid w={1 / 3}>
+        <Box px={PAGE_PADDING} my={3}>
+          <SectionHeading>{ROOT_COLLECTION.name}</SectionHeading>
+          <Box p={[1, 2]} mt={2} bg={colors["bg-medium"]}>
+            {this.props.collections.filter(
+              c => c.id !== user.personal_collection_id,
+            ).length > 0 ? (
+              <CollectionList
+                collections={this.props.collections}
+                analyticsContext="Homepage"
+                asCards={true}
+              />
+            ) : (
+              <Box className="text-centered">
+                <Box style={{ opacity: 0.5 }}>
+                  <RetinaImage
+                    src="app/img/empty.png"
+                    className="block ml-auto mr-auto"
+                  />
+                </Box>
+                <h3 className="text-medium">
+                  {user.is_superuser
+                    ? t`Save dashboards, questions, and collections in "${
+                        ROOT_COLLECTION.name
+                      }"`
+                    : t`Access dashboards, questions, and collections in "${
+                        ROOT_COLLECTION.name
+                      }"`}
+                </h3>
+              </Box>
+            )}
+            <Link
+              to="/collection/root"
+              color={normal.grey2}
+              className="text-brand-hover"
+              data-metabase-event={`Homepage;Browse Items Clicked;`}
+            >
+              <Flex color={colors["brand"]} p={2} my={1} align="center">
+                <Box ml="auto" mr="auto">
+                  <Flex align="center">
+                    <h4>{t`Browse all items`}</h4>
+                    <Icon name="chevronright" size={14} ml={1} />
+                  </Flex>
+                </Box>
+              </Flex>
+            </Link>
+          </Box>
+        </Box>
+
+        <DatabaseListLoader>
+          {({ databases }) => {
+            if (databases.length === 0) {
+              return null;
+            }
+            return (
+              <Box pt={2} px={PAGE_PADDING}>
+                <SectionHeading>{t`Our data`}</SectionHeading>
+                <Box mb={4}>
+                  <Grid>
                     {databases.map(database => (
-                      <GridItem>
+                      <GridItem w={[1, 1 / 3]} key={database.id}>
                         <Link
                           to={`browse/${database.id}`}
                           hover={{ color: normal.blue }}
+                          data-metabase-event={`Homepage;Browse DB Clicked; DB Type ${
+                            database.engine
+                          }`}
                         >
-                          <Box p={3} bg="#F2F5F7">
+                          <Box
+                            p={3}
+                            bg={colors["bg-medium"]}
+                            className="hover-parent hover--visibility"
+                          >
                             <Icon
                               name="database"
-                              color={normal.green}
+                              color={normal.purple}
                               mb={3}
                               size={28}
                             />
-                            <h3>{database.name}</h3>
+                            <Flex align="center">
+                              <h3>{database.name}</h3>
+                              <Box ml="auto" mr={1} className="hover-child">
+                                <Flex align="center">
+                                  <Tooltip tooltip={t`Learn about this table`}>
+                                    <Link
+                                      to={`reference/databases/${database.id}`}
+                                    >
+                                      <Icon
+                                        name="reference"
+                                        color={normal.grey1}
+                                      />
+                                    </Link>
+                                  </Tooltip>
+                                </Flex>
+                              </Box>
+                            </Flex>
                           </Box>
                         </Link>
                       </GridItem>
                     ))}
                   </Grid>
-                );
-              }}
-            </DatabaseListLoader>
+                </Box>
+              </Box>
+            );
+          }}
+        </DatabaseListLoader>
+      </Box>
+    );
+  }
+}
+
+export const PIN_MESSAGE_STORAGE_KEY =
+  "mb-admin-homepage-pin-propaganda-hidden";
+
+export class AdminPinMessage extends React.Component {
+  state = {
+    showMessage: !window.localStorage.getItem(PIN_MESSAGE_STORAGE_KEY),
+  };
+
+  dismissPinMessage = () => {
+    window.localStorage.setItem(PIN_MESSAGE_STORAGE_KEY, "true");
+    this.setState({ showMessage: false });
+  };
+  render() {
+    const { showMessage } = this.state;
+
+    if (!showMessage) {
+      return null;
+    }
+
+    const link = (
+      <Link className="link" to={Urls.collection()}>{t`Our analytics`}</Link>
+    );
+
+    return (
+      <Box>
+        <SectionHeading>{t`Start here`}</SectionHeading>
+
+        <Flex
+          bg={colors["bg-medium"]}
+          p={2}
+          align="center"
+          style={{ borderRadius: 6 }}
+          className="hover-parent hover--visibility"
+        >
+          <Icon name="dashboard" color={colors["brand"]} size={32} mr={1} />
+          <Box ml={1}>
+            <h3>{t`Your team's most important dashboards go here`}</h3>
+            <p className="m0 text-medium text-bold">{jt`Pin dashboards in ${link} to have them appear in this space for everyone`}</p>
           </Box>
-        </Box>
+          <Icon
+            className="hover-child text-brand-hover cursor-pointer bg-medium"
+            name="close"
+            ml="auto"
+            onClick={() => this.dismissPinMessage()}
+          />
+        </Flex>
       </Box>
     );
   }
 }
 
+const SectionHeading = ({ children }) => (
+  <Box mb={1}>
+    <h5
+      className="text-uppercase"
+      style={{ color: colors["text-medium"], fontWeight: 900 }}
+    >
+      {children}
+    </h5>
+  </Box>
+);
+
 export default Overworld;
diff --git a/frontend/src/metabase/containers/QuestionListLoader.jsx b/frontend/src/metabase/containers/QuestionListLoader.jsx
deleted file mode 100644
index 2b589c252aaa7a70a2c75d1cdd14047b96aca5f8..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/containers/QuestionListLoader.jsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import React from "react";
-
-import EntityListLoader from "metabase/entities/containers/EntityListLoader";
-
-const QuestionListLoader = props => (
-  <EntityListLoader entityType="questions" {...props} />
-);
-
-export default QuestionListLoader;
diff --git a/frontend/src/metabase/containers/QuestionLoader.jsx b/frontend/src/metabase/containers/QuestionLoader.jsx
index 50ec004f47fabaae3ebf84f8dcd987dd23df5aae..75cd4e557be7fc68b192c657f8b768efb004c7bf 100644
--- a/frontend/src/metabase/containers/QuestionLoader.jsx
+++ b/frontend/src/metabase/containers/QuestionLoader.jsx
@@ -41,7 +41,7 @@ type Props = {
  *          to={
  *            question.query()
  *                    .addFilter([
- *                      "SEGMENT",
+ *                      "segment",
  *                      question.query().filterSegmentOptions()[0]
  *                    ])
  *                    .question()
diff --git a/frontend/src/metabase/containers/QuestionName.jsx b/frontend/src/metabase/containers/QuestionName.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..1b752b4e3cb36e791f4d98c5adbfa1f391f33af9
--- /dev/null
+++ b/frontend/src/metabase/containers/QuestionName.jsx
@@ -0,0 +1,19 @@
+import React from "react";
+
+import { entityObjectLoader } from "metabase/entities/containers/EntityObjectLoader";
+
+const QuestionNameLoader = entityObjectLoader({
+  entityType: "question",
+  properties: ["name"],
+  loadingAndErrorWrapper: false,
+})(({ object }) => <span>{object && object.name}</span>);
+
+const QuestionName = ({ questionId }) => {
+  if (questionId == undefined || isNaN(questionId)) {
+    return null;
+  } else {
+    return <QuestionNameLoader entityId={questionId} />;
+  }
+};
+
+export default QuestionName;
diff --git a/frontend/src/metabase/containers/QuestionPicker.jsx b/frontend/src/metabase/containers/QuestionPicker.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..24421d962bcfcd9c797eea69458b91db8676905c
--- /dev/null
+++ b/frontend/src/metabase/containers/QuestionPicker.jsx
@@ -0,0 +1,22 @@
+import React from "react";
+import PropTypes from "prop-types";
+
+import ItemPicker from "./ItemPicker";
+
+const QuestionPicker = ({ value, onChange, ...props }) => (
+  <ItemPicker
+    {...props}
+    value={value === undefined ? undefined : { model: "card", id: value }}
+    onChange={question => onChange(question ? question.id : undefined)}
+    models={["card"]}
+  />
+);
+
+QuestionPicker.propTypes = {
+  // a question ID or null
+  value: PropTypes.number,
+  // callback that takes a question ID
+  onChange: PropTypes.func.isRequired,
+};
+
+export default QuestionPicker;
diff --git a/frontend/src/metabase/containers/QuestionSelect.jsx b/frontend/src/metabase/containers/QuestionSelect.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..c40052e69abc60ca3462373ec10a22fa3d03c67b
--- /dev/null
+++ b/frontend/src/metabase/containers/QuestionSelect.jsx
@@ -0,0 +1,8 @@
+import ItemSelect from "./ItemSelect";
+
+import QuestionPicker from "./QuestionPicker";
+import QuestionName from "./QuestionName";
+
+const QuestionSelect = ItemSelect(QuestionPicker, QuestionName, "question");
+
+export default QuestionSelect;
diff --git a/frontend/src/metabase/containers/SaveQuestionModal.jsx b/frontend/src/metabase/containers/SaveQuestionModal.jsx
index 196c5cc26b75be4938646c435de53c9d86e98729..aade70fcee6d146a130f14eaec8f8401b789bc17 100644
--- a/frontend/src/metabase/containers/SaveQuestionModal.jsx
+++ b/frontend/src/metabase/containers/SaveQuestionModal.jsx
@@ -32,7 +32,10 @@ export default class SaveQuestionModal extends Component {
               )
             : "",
         description: props.card.description || "",
-        collection_id: props.card.collection_id || null,
+        collection_id:
+          props.card.collection_id === undefined
+            ? props.initialCollectionId
+            : props.card.collection_id,
         saveType: props.originalCard ? "overwrite" : "create",
       },
     };
@@ -174,7 +177,7 @@ export default class SaveQuestionModal extends Component {
               },
               { name: t`Save as new question`, value: "create" },
             ]}
-            isVertical
+            vertical
           />
         </FormField>
       );
@@ -198,7 +201,7 @@ export default class SaveQuestionModal extends Component {
         ]}
         onClose={this.props.onClose}
       >
-        <form className="Form-inputs" onSubmit={this.formSubmitted}>
+        <form onSubmit={this.formSubmitted}>
           {saveOrUpdate}
           <CSSTransitionGroup
             transitionName="saveQuestionModalFields"
diff --git a/frontend/src/metabase/containers/UndoListing.jsx b/frontend/src/metabase/containers/UndoListing.jsx
index 75a01cbeb1e713f252f0fce93c3abef994812c9a..542d9470a73d32d29924b27127e68281dd2297fd 100644
--- a/frontend/src/metabase/containers/UndoListing.jsx
+++ b/frontend/src/metabase/containers/UndoListing.jsx
@@ -34,9 +34,9 @@ const DefaultMessage = ({
   undo: { verb = t`modified`, count = 1, subject = t`item` },
 }) => (
   <div>
-    {count > 1
-      ? t`${capitalize(verb)} ${count} ${inflect(subject, count)}`
-      : t`${capitalize(verb)} ${subject}`}
+    {count > 1 // TODO: figure out how to i18n this?
+      ? `${capitalize(verb)} ${count} ${inflect(subject, count)}`
+      : `${capitalize(verb)} ${subject}`}
   </div>
 );
 DefaultMessage.propTypes = {
@@ -59,6 +59,11 @@ export default class UndoListing extends Component {
         {undos.map(undo => (
           <Card key={undo._domId} dark p={2} mt={1}>
             <Flex align="center">
+              <Icon
+                name={(undo.icon && undo.icon) || "check"}
+                color="white"
+                mr={1}
+              />
               {typeof undo.message === "function" ? (
                 undo.message(undo)
               ) : undo.message ? (
@@ -72,6 +77,7 @@ export default class UndoListing extends Component {
                   <Link
                     ml={1}
                     onClick={() => performUndo(undo.id)}
+                    className="link text-bold"
                   >{t`Undo`}</Link>
                 )}
               <Icon
diff --git a/frontend/src/metabase/containers/UserCollectionList.jsx b/frontend/src/metabase/containers/UserCollectionList.jsx
index ba678d63c08d3239e45f2b6e22b67993c3fa270c..7f5926c51b67886a92327cfc65a95082150355e6 100644
--- a/frontend/src/metabase/containers/UserCollectionList.jsx
+++ b/frontend/src/metabase/containers/UserCollectionList.jsx
@@ -1,8 +1,8 @@
 import React from "react";
 import { Box, Flex } from "grid-styled";
-import { t } from "c-3po";
 
 import * as Urls from "metabase/lib/urls";
+import colors from "metabase/lib/colors";
 
 import Card from "metabase/components/Card";
 import Icon from "metabase/components/Icon";
@@ -12,18 +12,25 @@ import BrowserCrumbs from "metabase/components/BrowserCrumbs";
 
 import EntityListLoader from "metabase/entities/containers/EntityListLoader";
 
+import {
+  ROOT_COLLECTION,
+  PERSONAL_COLLECTIONS,
+} from "metabase/entities/collections";
+
 const UserListLoader = ({ children, ...props }) => (
   <EntityListLoader entityType="users" children={children} {...props} />
 );
 
 const UserCollectionList = () => (
   <Box px={4}>
-    <BrowserCrumbs
-      crumbs={[
-        { title: t`Saved items`, to: Urls.collection() },
-        { title: t`Everyone else’s personal collections` },
-      ]}
-    />
+    <Box py={2}>
+      <BrowserCrumbs
+        crumbs={[
+          { title: ROOT_COLLECTION.name, to: Urls.collection() },
+          { title: PERSONAL_COLLECTIONS.name },
+        ]}
+      />
+    </Box>
     <UserListLoader>
       {({ list }) => {
         return (
@@ -43,10 +50,10 @@ const UserCollectionList = () => (
                             <Icon
                               name="person"
                               mr={1}
-                              color="#93B3C9"
-                              size={22}
+                              color={colors["text-medium"]}
+                              size={18}
                             />
-                            <h2>{user.common_name}</h2>
+                            <h3>{user.common_name}</h3>
                           </Flex>
                         </Card>
                       </Link>
diff --git a/frontend/src/metabase/containers/dnd/CollectionDropTarget.jsx b/frontend/src/metabase/containers/dnd/CollectionDropTarget.jsx
index 15449bf5711a291acf8451ba5b6eecda06d35c6f..cc54d216255b362246f5b246a4fd7467bd22e972 100644
--- a/frontend/src/metabase/containers/dnd/CollectionDropTarget.jsx
+++ b/frontend/src/metabase/containers/dnd/CollectionDropTarget.jsx
@@ -11,6 +11,10 @@ const CollectionDropTarget = DropTarget(
     },
     canDrop(props, monitor) {
       const { item } = monitor.getItem();
+      // can't drop if can't write the  collection
+      if (props.collection.can_write === false) {
+        return false;
+      }
       return item.model !== "collection" || item.id !== props.collection.id;
     },
   },
diff --git a/frontend/src/metabase/containers/dnd/DropArea.jsx b/frontend/src/metabase/containers/dnd/DropArea.jsx
index 3288f9874c3c92d5ce75b1aa82120abf2dd5b3af..cac0f1a23a05a337ffd723b76ac07674d1775f55 100644
--- a/frontend/src/metabase/containers/dnd/DropArea.jsx
+++ b/frontend/src/metabase/containers/dnd/DropArea.jsx
@@ -1,6 +1,5 @@
 import React from "react";
 import cx from "classnames";
-import { normal } from "metabase/lib/colors";
 
 const DropTargetBackgroundAndBorder = ({
   highlighted,
@@ -15,7 +14,7 @@ const DropTargetBackgroundAndBorder = ({
   <div
     className={cx("absolute rounded", {
       "pointer-events-none": !highlighted,
-      "bg-slate-almost-extra-light": highlighted,
+      "bg-medium": highlighted,
     })}
     style={{
       top: -marginTop,
@@ -24,8 +23,6 @@ const DropTargetBackgroundAndBorder = ({
       right: -marginRight,
       zIndex: -1,
       boxSizing: "border-box",
-      border: "2px solid transparent",
-      borderColor: hovered & !noDrop ? normal.blue : "transparent",
     }}
   />
 );
diff --git a/frontend/src/metabase/containers/dnd/ItemDragSource.jsx b/frontend/src/metabase/containers/dnd/ItemDragSource.jsx
index 030b19abc0638081379af91e3fe9c89dc543cf09..67ccb1586d0cd6150f0a02edb22e568c5a9a2ed2 100644
--- a/frontend/src/metabase/containers/dnd/ItemDragSource.jsx
+++ b/frontend/src/metabase/containers/dnd/ItemDragSource.jsx
@@ -9,6 +9,10 @@ import { dragTypeForItem } from ".";
   props => dragTypeForItem(props.item),
   {
     canDrag(props, monitor) {
+      // can't drag if can't write the parent collection
+      if (props.collection && props.collection.can_write === false) {
+        return false;
+      }
       // if items are selected only allow dragging selected items
       if (
         props.selection &&
diff --git a/frontend/src/metabase/containers/dnd/PinDropTarget.jsx b/frontend/src/metabase/containers/dnd/PinDropTarget.jsx
index e9cff8d2f93a940bcb0623e3a4a3f99cc9dbb2f6..29f3c654229b1b31532949ecdc98817c4301ede6 100644
--- a/frontend/src/metabase/containers/dnd/PinDropTarget.jsx
+++ b/frontend/src/metabase/containers/dnd/PinDropTarget.jsx
@@ -13,6 +13,8 @@ const PinDropTarget = DropTarget(
     },
     canDrop(props, monitor) {
       const { item } = monitor.getItem();
+      // NOTE: not necessary to check collection permission here since we
+      // enforce it when beginning to drag and item within the same collection
       return props.pinIndex != item.collection_position;
     },
   },
diff --git a/frontend/src/metabase/containers/dnd/index.js b/frontend/src/metabase/containers/dnd/index.js
index 30a8c33272b193023484ca48635e08898f335271..8164cb1ef6a57df5bd56b0322ae5dba3e56e1f7c 100644
--- a/frontend/src/metabase/containers/dnd/index.js
+++ b/frontend/src/metabase/containers/dnd/index.js
@@ -10,7 +10,11 @@ export const DragTypes = {
   PULSE: "pulse",
 };
 
-export const PinnableDragTypes = [DragTypes.QUESTION, DragTypes.DASHBOARD];
+export const PinnableDragTypes = [
+  DragTypes.QUESTION,
+  DragTypes.DASHBOARD,
+  DragTypes.PULSE,
+];
 
 export const MoveableDragTypes = [
   DragTypes.QUESTION,
diff --git a/frontend/src/metabase/css/admin.css b/frontend/src/metabase/css/admin.css
index 620263bf0089ecf1b428a7e6e0214957c85a49f9..a886c098e58f28712cb0d2f5594173f4b6983f4c 100644
--- a/frontend/src/metabase/css/admin.css
+++ b/frontend/src/metabase/css/admin.css
@@ -1,24 +1,24 @@
 :root {
-  --admin-nav-bg-color: #8091ab;
-  --admin-nav-bg-color-tint: #9aa7bc;
-  --admin-nav-item-text-color: rgba(255, 255, 255, 0.63);
-  --admin-nav-item-text-active-color: #fff;
+  --admin-nav-bg-color: var(--color-bg-dark);
+  --admin-nav-bg-color-tint: var(--color-bg-dark);
+  --admin-nav-item-text-color: color(var(--color-text-white) alpha(-37%));
+  --admin-nav-item-text-active-color: var(--color-text-white);
   --page-header-padding: 2.375rem;
 }
 
 .AdminNav {
-  background: var(--admin-nav-bg-color);
-  color: #fff;
+  background: var(--color-bg-dark);
+  color: var(--color-text-white);
   font-size: 0.85rem;
 }
 
 .AdminNav .NavItem {
-  color: var(--admin-nav-item-text-color);
+  color: color(var(--color-text-white) alpha(-37%));
 }
 
 .AdminNav .NavItem:hover,
 .AdminNav .NavItem.is--selected {
-  color: var(--admin-nav-item-text-active-color);
+  color: var(--color-text-white);
 }
 
 /* TODO: this feels itchy. should refactor .NavItem.is--selected to be less cascadey */
@@ -29,27 +29,27 @@
 
 .AdminNav .NavDropdown.open .NavDropdown-button,
 .AdminNav .NavDropdown .NavDropdown-content-layer {
-  background-color: var(--admin-nav-bg-color-tint);
+  background-color: var(--color-bg-dark);
 }
 
 .AdminNav .Dropdown-item:hover {
-  background-color: var(--admin-nav-bg-color);
+  background-color: var(--color-bg-dark);
 }
 
 /* utility to get a simple common hover state for admin items */
 .HoverItem:hover,
 .AdminHoverItem:hover {
-  background-color: #f3f8fd;
+  background-color: var(--color-bg-medium);
   transition: background 0.2s linear;
 }
 
 .AdminNav .Dropdown-chevron {
-  color: #fff;
+  color: var(--color-text-white);
 }
 
 .Actions {
-  background-color: rgba(243, 243, 243, 0.46);
-  border: 1px solid #e0e0e0;
+  background-color: color(var(--color-bg-light) alpha(-54%));
+  border: 1px solid var(--color-border);
   padding: 2em;
 }
 
@@ -74,13 +74,13 @@
 }
 
 .ContentTable thead {
-  border-bottom: 1px solid #d8d8d8;
+  border-bottom: 1px solid var(--color-border);
 }
 
 .AdminBadge {
-  background-color: #a989c5;
+  background-color: var(--color-accent2);
   border-radius: 4px;
-  color: #fff;
+  color: var(--color-text-white);
   padding: 0.25em;
 }
 .PageHeader {
@@ -107,7 +107,7 @@
 
 /* TODO: remove this and apply AdminHoverItem to content rows */
 .ContentTable tbody tr:hover {
-  background-color: rgba(74, 144, 226, 0.04);
+  background-color: color(var(--color-brand) alpha(-96%));
 }
 
 .ContentTable tr:hover .Table-actions {
@@ -116,11 +116,11 @@
 }
 
 .AdminList {
-  background-color: #f9fbfc;
-  border: var(--border-size) var(--border-style) var(--border-color);
+  background-color: var(--color-bg-light);
+  border: var(--border-size) var(--border-style) var(--color-border);
   border-radius: var(--default-border-radius);
   width: 266px;
-  box-shadow: inset -1px -1px 3px rgba(0, 0, 0, 0.05);
+  box-shadow: inset -1px -1px 3px var(--color-shadow);
   padding-bottom: 0.75em;
 }
 
@@ -136,7 +136,7 @@
   bottom: 0;
   margin: auto;
   margin-left: 1em;
-  color: #c0c0c0;
+  color: var(--color-text-light);
 }
 
 .AdminList-search .AdminInput {
@@ -146,7 +146,7 @@
   width: 100%;
   border-top-left-radius: var(--default-border-radius);
   border-top-right-radius: var(--default-border-radius);
-  border-bottom-color: var(--border-color);
+  border-bottom-color: var(--color-border);
 }
 
 .AdminList-item {
@@ -157,37 +157,37 @@
 }
 
 .AdminList-item.selected {
-  color: var(--brand-color);
+  color: var(--color-brand);
 }
 
 .AdminList-item.selected,
 .AdminList-item:hover {
   background-color: white;
-  border-color: var(--border-color);
+  border-color: var(--color-border);
   margin-left: -0.5em;
   margin-right: -0.5em;
   padding-left: 1.5em;
   padding-right: 1.5em;
-  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
+  box-shadow: 0 1px 2px var(--color-shadow);
 }
 
 .AdminList-section {
   margin-top: 1em;
   padding: 0.5em 1em 0.5em 1em;
   text-transform: uppercase;
-  color: color(var(--base-grey) shade(20%));
+  color: var(--color-text-light);
   font-weight: 700;
   font-size: smaller;
 }
 
 .AdminInput {
-  color: var(--default-font-color);
+  color: var(--color-text-dark);
   padding: var(--padding-1);
-  background-color: #fcfcfc;
+  background-color: var(--color-bg-light);
   border: 1px solid transparent;
 }
 .AdminInput:focus {
-  border-color: var(--brand-color);
+  border-color: var(--color-brand);
   box-shadow: none;
   outline: 0;
 }
@@ -195,7 +195,7 @@
 .AdminSelect {
   display: inline-block;
   padding: 0.6em;
-  border: 1px solid var(--border-color);
+  border: 1px solid var(--color-border);
   border-radius: var(--default-border-radius);
   font-size: 14px;
   font-weight: 700;
@@ -220,7 +220,7 @@
 }
 
 .MetadataTable-title {
-  background-color: #fcfcfc;
+  background-color: var(--color-bg-white);
 }
 
 .TableEditor-table-name {
@@ -237,32 +237,32 @@
 }
 
 .TableEditor-field-visibility {
-  /*color: var(--orange-color);*/
+  /*color: var(--color-warning);*/
 }
 
 .TableEditor-field-visibility .ColumnarSelector-row:hover {
-  background-color: var(--brand-color) !important;
+  background-color: var(--color-brand) !important;
   color: white !important;
 }
 
 .TableEditor-field-type {
-  /*color: var(--purple-color);*/
+  /*color: var(--color-accent2);*/
 }
 
 .TableEditor-field-type .ColumnarSelector-row:hover {
-  background-color: var(--brand-color) !important;
+  background-color: var(--color-brand) !important;
   color: white !important;
 }
 
 .TableEditor-field-special-type,
 .TableEditor-field-target {
   margin-top: 3px;
-  /*color: var(--green-color);*/
+  /*color: var(--color-accent1);*/
 }
 
 .TableEditor-field-special-type .ColumnarSelector-row:hover,
 .TableEditor-field-target .ColumnarSelector-row:hover {
-  background-color: var(--brand-color) !important;
+  background-color: var(--color-brand) !important;
   color: white !important;
 }
 
@@ -297,15 +297,23 @@
 
 .AdminTable th {
   text-transform: uppercase;
-  color: color(var(--base-grey) shade(40%));
+  color: var(--color-text-medium);
   padding: var(--padding-1);
   font-weight: normal;
 }
 
 .AdminTable thead {
-  border-bottom: var(--border-size) var(--border-style) var(--border-color);
+  border-bottom: var(--border-size) var(--border-style) var(--color-border);
 }
 
 .AdminTable tbody tr:first-child td {
   padding-top: var(--margin-1);
 }
+
+.AdminLink {
+  opacity: 0.435;
+}
+
+.AdminLink:hover {
+  opacity: 1;
+}
diff --git a/frontend/src/metabase/css/card.css b/frontend/src/metabase/css/card.css
index efd7c5faa0340ab500a4d64105eb03b2d278631d..305d7219aa175be1746b735566efb4628cd080f9 100644
--- a/frontend/src/metabase/css/card.css
+++ b/frontend/src/metabase/css/card.css
@@ -1,6 +1,6 @@
 :root {
-  --card-border-color: #d9d9d9;
-  --card-text-color: #777;
+  --card-border-color: var(--color-border);
+  --card-text-color: var(--color-text-medium);
   --card-border-radius: 4px;
 
   --card-scalar-text-lg: 2.4em;
@@ -13,16 +13,16 @@
 
 .Card-footing {
   font-size: 0.8rem;
-  color: #999;
+  color: var(--color-text-medium);
 }
 
 .Card-title {
-  color: #3f3a3a;
+  color: var(--color-text-dark);
   font-size: 0.8em;
 }
 
 .Card-dataSource {
-  color: #999;
+  color: var(--color-text-medium);
   padding-top: 0.5em;
 }
 
@@ -57,28 +57,28 @@
 }
 
 .CardSettings-group {
-  border-bottom: 1px solid #ddd;
+  border-bottom: 1px solid var(--color-border);
 }
 
 .CardSettings-groupTitle {
   padding: 0.5em;
-  border-bottom: 1px solid #ddd;
+  border-bottom: 1px solid var(--color-border);
 }
 
 .CardSettings-content {
   padding: 2em;
-  background-color: #fafafa;
+  background-color: var(--color-bg-white);
 }
 
 .CardSettings {
-  border-top: 1px solid #ddd;
-  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.12);
+  border-top: 1px solid var(--color-border);
+  box-shadow: inset 0 1px 1px var(--color-shadow);
 }
 
 .CardSettings-label {
   font-size: 1.15em;
   margin-left: 0.5em;
-  color: #666;
+  color: var(--color-text-dark);
 }
 
 .CardSettings-colorBlock {
diff --git a/frontend/src/metabase/css/components/buttons.css b/frontend/src/metabase/css/components/buttons.css
index 08c9aeb65804be0bf331fdc6e8ce9bbf6a40d1ac..68b23a44e333e253fd30be4ba990237485d7447d 100644
--- a/frontend/src/metabase/css/components/buttons.css
+++ b/frontend/src/metabase/css/components/buttons.css
@@ -1,16 +1,5 @@
 :root {
   --default-button-border-radius: 4px;
-  --default-button-border-color: #b9b9b9;
-  --default-button-background-color: #fff;
-
-  --primary-button-border-color: #509ee3;
-  --primary-button-bg-color: #509ee3;
-  --warning-button-border-color: #ef8c8c;
-  --warning-button-bg-color: #ef8c8c;
-  --danger-button-bg-color: #ef8c8c;
-  --selected-button-bg-color: #f4f6f8;
-
-  --success-button-color: var(--success-color);
 }
 
 .Button {
@@ -18,9 +7,9 @@
   box-sizing: border-box;
   text-decoration: none;
   padding: 0.5rem 0.75rem;
-  background: #fbfcfd;
-  border: 1px solid #ddd;
-  color: var(--default-font-color);
+  background: var(--color-bg-light);
+  border: 1px solid var(--color-border);
+  color: var(--color-text-dark);
   cursor: pointer;
   text-decoration: none;
   font-weight: bold;
@@ -28,7 +17,7 @@
   border-radius: var(--default-button-border-radius);
 }
 .Button:hover {
-  color: var(--brand-color);
+  color: var(--color-brand);
 }
 
 @media screen and (--breakpoint-min-lg) {
@@ -67,27 +56,27 @@
 }
 
 .Button--primary {
-  color: #fff;
-  background: var(--primary-button-bg-color);
-  border: 1px solid var(--primary-button-border-color);
+  color: var(--color-text-white);
+  background: var(--color-brand);
+  border: 1px solid var(--color-brand);
 }
 
 .Button--primary:hover {
-  color: #fff;
-  border-color: color(var(--primary-button-border-color) shade(10%));
-  background-color: color(var(--primary-button-bg-color) shade(10%));
+  color: var(--color-text-white);
+  border-color: var(--color-brand);
+  background-color: var(--color-brand);
 }
 
 .Button--warning {
-  color: #fff;
-  background: var(--warning-button-bg-color);
-  border: 1px solid var(--warning-button-border-color);
+  color: var(--color-text-white);
+  background: var(--color-error);
+  border: 1px solid var(--color-error);
 }
 
 .Button--warning:hover {
-  color: #fff;
-  border-color: color(var(--warning-button-border-color) shade(10%));
-  background-color: color(var(--warning-button-bg-color) shade(10%));
+  color: var(--color-text-white);
+  border-color: var(--color-error);
+  background-color: var(--color-error);
 }
 
 .Button--cancel {
@@ -96,42 +85,42 @@
 
 .Button--white {
   background-color: white;
-  color: color(var(--base-grey) shade(30%));
-  border-color: color(var(--base-grey) shade(30%));
+  color: var(--color-text-medium);
+  border-color: var(--color-border);
 }
 
 .Button--purple {
   color: white;
-  background-color: #a989c5;
-  border: 1px solid #a989c5;
+  background-color: var(--color-accent2);
+  border: 1px solid var(--color-accent2);
 }
 
 .Button--purple:hover {
   color: white;
-  background-color: #885ab1;
-  border-color: #885ab1;
+  background-color: var(--color-accent2);
+  border-color: var(--color-accent2);
 }
 
 .Button--borderless {
   border: none;
   background: transparent;
-  color: color(var(--base-grey) shade(40%));
+  color: var(--color-text-medium);
 }
 .Button--borderless:hover {
-  color: color(var(--base-grey) shade(50%));
+  color: var(--color-text-medium);
 }
 
 .Button--onlyIcon {
   border: none;
   background: transparent;
-  color: var(--default-font-color);
+  color: var(--color-text-dark);
   padding: 0;
 }
 
 .Button-group {
   display: inline-block;
   border-radius: var(--default-button-border-radius);
-  border: 1px solid var(--default-button-border-color);
+  border: 1px solid var(--color-border);
   overflow: hidden;
   clear: both;
 }
@@ -147,8 +136,8 @@
 }
 
 .Button-group .Button--active {
-  background-color: var(--success-color);
-  color: #fff;
+  background-color: var(--color-success);
+  color: var(--color-text-white);
 }
 
 .Button-group .Button:first-child {
@@ -156,16 +145,16 @@
 }
 
 .Button-group--blue {
-  border-color: rgb(194, 216, 242);
+  border-color: var(--color-border);
 }
 
 .Button-group--blue .Button {
-  color: rgb(147, 155, 178);
+  color: var(--color-text-medium);
 }
 
 .Button-group--blue .Button--active {
-  background-color: rgb(227, 238, 250);
-  color: rgb(74, 144, 226);
+  background-color: var(--color-bg-medium);
+  color: var(--color-brand);
 }
 
 .Button-group--brand {
@@ -174,12 +163,12 @@
 
 .Button-group--brand .Button {
   border-color: white;
-  color: var(--brand-color);
-  background-color: #e5e5e5;
+  color: var(--color-brand);
+  background-color: var(--color-bg-medium);
 }
 
 .Button-group--brand .Button--active {
-  background-color: var(--brand-color);
+  background-color: var(--color-brand);
   color: white;
 }
 
@@ -190,67 +179,31 @@
 
 .Button--selected,
 .Button--selected:hover {
-  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.12);
-  background-color: var(--selected-button-bg-color);
+  box-shadow: inset 0 1px 1px var(--color-shadow);
+  background-color: var(--color-bg-light);
 }
 
 .Button--danger {
-  background-color: var(--danger-button-bg-color);
-  border-color: var(--danger-button-bg-color);
-  color: #fff;
+  background-color: var(--color-error);
+  border-color: var(--color-error);
+  color: var(--color-text-white);
 }
 
 .Button--danger:hover {
   color: white;
-  background-color: color(var(--danger-button-bg-color) shade(10%));
-  border-color: color(var(--danger-button-bg-color) shade(10%));
+  background-color: var(--color-error);
+  border-color: var(--color-error);
 }
 
 .Button--success {
-  background-color: var(--success-button-color);
-  border-color: var(--success-button-color);
-  color: #fff;
+  background-color: var(--color-success);
+  border-color: var(--color-success);
+  color: var(--color-text-white);
 }
 .Button--success:hover {
-  background-color: var(--green-saturated-color);
-  border-color: var(--green-saturated-color);
-  color: #fff;
-}
-
-/* toggle button */
-.Button-toggle {
-  color: var(--grey-text-color);
-  display: flex;
-  line-height: 1;
-  border: 1px solid #ddd;
-  border-radius: 40px;
-  width: 3rem;
-  transition: background 0.2s linear 0.2s, border 0.2s linear 0.2s;
-}
-
-.Button-toggleIndicator {
-  margin-left: 0;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  padding: 0.25rem;
-  border: 1px solid #ddd;
-  border-radius: 99px;
-  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.02);
-  transition: margin 0.3s linear;
-  background-color: #fff;
-}
-
-.Button-toggle.Button--toggled .Button-toggleIndicator {
-  margin-left: 50%;
-  transition: margin 0.3s linear;
-}
-
-.Button-toggle.Button--toggled {
-  color: var(--brand-color);
-  background-color: var(--brand-color);
-  border-color: var(--brand-color);
-  transition: background 0.2s linear 0.2s, border 0.2s linear 0.2s;
+  background-color: var(--color-success);
+  border-color: var(--color-success);
+  color: var(--color-text-white);
 }
 
 .Button--withIcon {
diff --git a/frontend/src/metabase/css/components/dropdown.css b/frontend/src/metabase/css/components/dropdown.css
deleted file mode 100644
index 826aa5dd1498115c0d4c38d2e409e0ca9645515d..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/css/components/dropdown.css
+++ /dev/null
@@ -1,65 +0,0 @@
-:root {
-  --dropdown-border-color: rgba(0, 0, 0, 0.064);
-}
-
-.Dropdown {
-  position: relative;
-}
-
-.Dropdown-content {
-  opacity: 0; /* start invisible */
-  pointer-events: none; /* and without any clicks */
-  z-index: 20;
-  position: absolute;
-  top: 40px;
-  min-width: 200px;
-  margin-top: 18px;
-  border: 1px solid var(--dropdown-border-color);
-  background-color: #fff;
-  border-radius: 4px;
-  box-shadow: 0 0 2px rgba(0, 0, 0, 0.12);
-  background-clip: padding-box;
-  padding-top: 1em;
-  padding-bottom: 1em;
-}
-
-.Dropdown-content:before {
-  position: absolute;
-  top: -20px;
-  right: 0;
-  border-left: 5px solid transparent;
-  border-right: 5px solid transparent;
-  border-right: 5px solid red;
-  content: "";
-  display: block;
-}
-
-/* switching from home rolled to BS logic for dropdowns so we still have both classes */
-.Dropdown.open .Dropdown-content,
-.Dropdown--showing.Dropdown-content {
-  opacity: 1;
-  pointer-events: all;
-  transition: opacity 0.3s linear, margin 0.2s linear;
-  margin-top: 0;
-}
-
-.Dropdown-item {
-  padding-top: 1rem;
-  padding-bottom: 1rem;
-  padding-left: 2rem;
-  padding-right: 2rem;
-  line-height: 1;
-}
-
-.Dropdown .Dropdown-item .link:hover {
-  text-decoration: none;
-}
-
-.Dropdown-item:hover {
-  color: #fff;
-  background-color: var(--brand-color);
-}
-
-.Dropdown .Dropdown-item:hover {
-  text-decoration: none;
-}
diff --git a/frontend/src/metabase/css/components/form.css b/frontend/src/metabase/css/components/form.css
index 9881938d2bb7862cedf6d8941cb209184d8b81c1..2f4421c70872f61d08c7785ee745f99a5c3ad505 100644
--- a/frontend/src/metabase/css/components/form.css
+++ b/frontend/src/metabase/css/components/form.css
@@ -1,12 +1,12 @@
 :root {
   --form-padding: 1em;
-  --form-input-placeholder-color: #c0c0c0;
+  --form-input-placeholder-color: var(--color-text-light);
 
   --form-input-size: 1rem;
   --form-input-size-medium: 1.25rem;
   --form-input-size-large: 1.571rem;
 
-  --form-label-color: #949494;
+  --form-label-color: var(--color-text-medium);
   --form-offset: 2.4rem;
 }
 
@@ -17,13 +17,13 @@
 /* TODO: combine this and the scoped version */
 .Form-label {
   display: block;
-  color: var(--form-label-color);
+  color: var(--color-text-medium);
   font-size: 1.2rem;
 }
 
 .Form-field {
   position: relative;
-  color: #6c6c6c;
+  color: var(--color-text-medium);
   margin-bottom: 1.5rem;
 }
 
@@ -37,7 +37,7 @@
 }
 
 .Form-field.Form--fieldError {
-  color: var(--error-color);
+  color: var(--color-error);
 }
 
 .Form-input {
@@ -92,7 +92,7 @@
   left: 0;
   width: 0.15em;
   height: 3em;
-  background-color: #ddd;
+  background-color: var(--color-bg-medium);
   box-sizing: border-box;
   opacity: 0;
   transition: background-color 0.3s linear;
@@ -100,24 +100,24 @@
 }
 
 .Form-field.Form--fieldError .Form-charm {
-  background-color: var(--error-color);
+  background-color: var(--color-error);
   opacity: 1;
 }
 
 .Form-input:focus + .Form-charm {
-  background-color: var(--brand-color);
+  background-color: var(--color-brand);
   opacity: 1;
 }
 
 .Form-field:hover .Form-input {
-  color: #ddd;
-  background: rgba(0, 0, 0, 0.02);
+  color: var(--color-text-light);
+  background: color(var(--color-bg-black) alpha(-98%));
 }
 
 /* ewww */
 .Form-field:hover .Form-input.ng-dirty {
-  color: #222;
-  background-color: #fff;
+  color: var(--color-text-dark);
+  background-color: var(--color-bg-white);
 }
 
 .Form-field:hover .Form-charm {
@@ -126,7 +126,7 @@
 
 .Form-field:hover .Form-input:focus {
   transition: color 0.3s linear;
-  color: #444;
+  color: var(--color-text-dark);
   background-color: transparent;
   transition: background 0.3s linear;
 }
@@ -144,13 +144,13 @@
   width: 8px;
   height: 8px;
   border: 2px solid white;
-  box-shadow: 0 0 0 2px var(--grey-3);
+  box-shadow: 0 0 0 2px var(--color-shadow);
   border-radius: 8px;
 }
 
 .Form-radio:checked + label {
-  box-shadow: 0 0 0 2px var(--brand-color);
-  background-color: var(--brand-color);
+  box-shadow: 0 0 0 2px var(--color-shadow);
+  background-color: var(--color-brand);
 }
 
 /* TODO: replace instances of Form-group with Form-field */
@@ -174,44 +174,45 @@
 
 .FormTitleSeparator {
   position: relative;
-  border-bottom: 1px solid #e8e8e8;
+  border-bottom: 1px solid var(--color-border);
 }
 
 ::-webkit-input-placeholder {
   /* WebKit browsers */
-  color: var(--form-input-placeholder-color);
+  color: var(--color-text-light);
 }
 :-moz-placeholder {
   /* Mozilla Firefox 4 to 18 */
-  color: var(--form-input-placeholder-color);
+  color: var(--color-text-light);
   opacity: 1;
 }
 ::-moz-placeholder {
   /* Mozilla Firefox 19+ */
-  color: var(--form-input-placeholder-color);
+  color: var(--color-text-light);
   opacity: 1;
 }
 :-ms-input-placeholder {
   /* Internet Explorer 10+ */
-  color: var(--form-input-placeholder-color);
+  color: var(--color-text-light);
 }
 
 .NewForm .Form-label {
   text-transform: uppercase;
-  color: color(var(--base-grey) shade(30%));
+  letter-spacing: 0.05em;
+  color: var(--color-text-medium);
   margin-bottom: 0.5em;
 }
 
 .NewForm .Form-input {
   font-size: 16px;
-  color: var(--default-font-color);
+  color: var(--color-text-dark);
   padding: 0.5em;
-  background-color: #fcfcfc;
-  border: 1px solid #eaeaea;
+  background-color: var(--color-bg-white);
+  border: 1px solid var(--color-border);
   border-radius: 4px;
 }
 .NewForm .Form-input:focus {
-  border-color: var(--brand-color);
+  border-color: var(--color-brand);
   box-shadow: none;
   outline: 0;
 }
diff --git a/frontend/src/metabase/css/components/header.css b/frontend/src/metabase/css/components/header.css
index ed096260547d6e12a19d90133152a4cf2ad0612e..d974b98b75c0dfe0ff935b50fec288d1dbd6503e 100644
--- a/frontend/src/metabase/css/components/header.css
+++ b/frontend/src/metabase/css/components/header.css
@@ -4,19 +4,19 @@
 
 .Header-title-name {
   font-size: 1.24em;
-  color: var(--grey-text-color);
+  color: var(--color-text-dark);
 }
 
 .Header-attribution {
   display: none; /* disabled */
-  color: #adadad;
+  color: var(--color-text-medium);
   margin-bottom: 0.5em;
 }
 
 .Header-buttonSection {
   padding-right: 1em;
   margin-right: 1em;
-  border-right: 1px solid rgba(0, 0, 0, 0.2);
+  border-right: 1px solid color(var(--color-accent2) alpha(-80%));
 }
 
 .Header-buttonSection:last-child {
@@ -34,11 +34,24 @@
 }
 
 .EditHeader {
-  background-color: #6cafed;
+  background-color: color(var(--color-bg-white) alpha(-85%));
+  position: relative;
 }
 
-.EditHeader.EditHeader--admin {
-  background-color: var(--admin-nav-bg-color-tint);
+/* a bit of a hack to fade out the edit header */
+.EditHeader:after {
+  content: "";
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: -1;
+  background-color: var(--color-brand);
+}
+
+.EditHeader.EditHeader--admin:after {
+  background-color: var(--color-bg-dark);
 }
 
 .EditHeader-title {
@@ -47,7 +60,7 @@
 }
 
 .EditHeader-subtitle {
-  color: rgba(255, 255, 255, 0.5);
+  color: color(var(--color-text-white) alpha(-50%));
 }
 
 .EditHeader .Button {
@@ -55,25 +68,25 @@
   border: none;
   font-size: 1em;
   text-transform: capitalize;
-  background-color: rgba(255, 255, 255, 0.1);
+  background-color: color(var(--color-bg-white) alpha(-90%));
   margin-left: 0.75em;
 }
 
 .EditHeader .Button--primary {
   background-color: white;
-  color: var(--brand-color);
+  color: var(--color-brand);
 }
 
 .EditHeader.EditHeader--admin .Button--primary {
   background-color: white;
-  color: var(--70-percent-black);
+  color: var(--color-text-dark);
 }
 
 .EditHeader .Button:hover {
   color: white;
-  background-color: var(--brand-color);
+  background-color: var(--color-brand);
 }
 
 .EditHeader.EditHeader--admin .Button:hover {
-  background-color: var(--admin-nav-bg-color);
+  background-color: var(--color-bg-dark);
 }
diff --git a/frontend/src/metabase/css/components/icons.css b/frontend/src/metabase/css/components/icons.css
index 77b7a4419f24e226dd160364264cab2263f29ab8..cf8a15f86dee8ef41cd48588e6f13664da861536 100644
--- a/frontend/src/metabase/css/components/icons.css
+++ b/frontend/src/metabase/css/components/icons.css
@@ -1,5 +1,5 @@
 :root {
-  --icon-color: #bfc4d1;
+  --icon-color: var(--color-text-light);
 }
 
 .IconWrapper {
@@ -13,19 +13,19 @@
 
 @keyframes icon-pulse {
   0% {
-    box-shadow: 0 0 5px rgba(80, 158, 227, 1);
+    box-shadow: 0 0 5px var(--color-shadow);
   }
   50% {
-    box-shadow: 0 0 5px rgba(80, 158, 227, 0.25);
+    box-shadow: 0 0 5px var(--color-shadow);
   }
   100% {
-    box-shadow: 0 0 5px rgba(80, 158, 227, 1);
+    box-shadow: 0 0 5px var(--color-shadow);
   }
 }
 
 .Icon--pulse {
   border-radius: 99px;
-  box-shadow: 0 0 5px #509ee3;
+  box-shadow: 0 0 5px var(--color-shadow);
   padding: 0.75em;
   animation-name: icon-pulse;
   animation-duration: 2s;
diff --git a/frontend/src/metabase/css/components/list.css b/frontend/src/metabase/css/components/list.css
index 32fb3866fd98bffd67a8e3b991726762c4fe51a6..fc63ab720ffed0c3d1a6dafa330eed96a9f2aeb9 100644
--- a/frontend/src/metabase/css/components/list.css
+++ b/frontend/src/metabase/css/components/list.css
@@ -4,15 +4,15 @@
 
 .List-section-header .Icon,
 .List-item .List-item-arrow .Icon {
-  color: var(--default-font-color);
+  color: var(--color-text-dark);
 }
 
 .List-item .Icon {
-  color: var(--slate-light-color);
+  color: var(--color-text-light);
 }
 
 .List-section-header {
-  color: var(--default-font-color);
+  color: var(--color-text-dark);
   border: 2px solid transparent; /* so that spacing matches .List-item */
 }
 
@@ -26,7 +26,7 @@
 }
 
 .List-section--expanded .List-section-header .List-section-title {
-  color: var(--default-font-color);
+  color: var(--color-text-dark);
 }
 
 .List-section-title {
@@ -42,18 +42,18 @@
 }
 
 .List-item--disabled .List-item-title {
-  color: var(--grey-3);
+  color: var(--color-text-medium);
 }
 
 .List-item:not(.List-item--disabled):hover,
 .List-item--selected {
   background-color: currentColor;
-  border-color: rgba(0, 0, 0, 0.2);
+  border-color: color(var(--color-accent2) alpha(-80%));
   /*color: white;*/
 }
 
 .List-item-title {
-  color: var(--default-font-color);
+  color: var(--color-text-dark);
 }
 
 .List-item:not(.List-item--disabled):hover .List-item-title,
diff --git a/frontend/src/metabase/css/components/modal.css b/frontend/src/metabase/css/components/modal.css
index a2b4898c199828b0938a210b2a218ad02b8a8aab..c99fe5334c950dc0c357c3a62ef65548ae0e857e 100644
--- a/frontend/src/metabase/css/components/modal.css
+++ b/frontend/src/metabase/css/components/modal.css
@@ -1,6 +1,6 @@
 :root {
-  --modal-background-color: rgba(46, 53, 59, 0.6);
-  --modal-background-color-transition: rgba(46, 53, 59, 0.01);
+  --modal-background-color: color(var(--color-bg-black) alpha(-40%));
+  --modal-background-color-transition: color(var(--color-bg-black) alpha(-99%));
 }
 
 .ModalContainer {
@@ -10,7 +10,7 @@
 .Modal {
   margin: auto;
   width: 640px;
-  box-shadow: 0 0 6px rgba(0, 0, 0, 0.12);
+  box-shadow: 0 0 6px var(--color-shadow);
   max-height: 90%;
   overflow-y: auto;
 }
@@ -48,7 +48,7 @@
 }
 
 .Modal-backdrop {
-  background-color: var(--modal-background-color);
+  background-color: color(var(--color-bg-black) alpha(-40%));
 }
 
 /* TRANSITIONS */
@@ -58,21 +58,21 @@
 .Modal-backdrop.Modal-appear,
 .Modal-backdrop.Modal-enter {
   transition: background-color 200ms ease-in-out;
-  background-color: var(--modal-background-color-transition);
+  background-color: color(var(--color-bg-black) alpha(-99%));
 }
 
 .Modal-backdrop.Modal-appear-active,
 .Modal-backdrop.Modal-enter-active {
-  background-color: var(--modal-background-color);
+  background-color: color(var(--color-bg-black) alpha(-40%));
 }
 
 .Modal-backdrop.Modal-leave {
   transition: background-color 200ms ease-in-out 100ms;
-  background-color: var(--modal-background-color);
+  background-color: color(var(--color-bg-black) alpha(-40%));
 }
 
 .Modal-backdrop.Modal-leave-active {
-  background-color: var(--modal-background-color-transition);
+  background-color: color(var(--color-bg-black) alpha(-99%));
 }
 
 /* modal */
diff --git a/frontend/src/metabase/css/components/select.css b/frontend/src/metabase/css/components/select.css
index bc2fbc53446b5442db2beb47b215f2442dcfbbac..d24d32c293941fab75783419cf6e61c5f7cbc05d 100644
--- a/frontend/src/metabase/css/components/select.css
+++ b/frontend/src/metabase/css/components/select.css
@@ -1,15 +1,15 @@
 :root {
-  --select-arrow-bg-color: #cacaca;
-  --select-border-color: #d9d9d9;
-  --select-bg-color: #fff;
-  --select-text-color: #777;
+  --select-arrow-bg-color: var(--color-bg-medium);
+  --select-border-color: var(--color-border);
+  --select-bg-color: var(--color-bg-white);
+  --select-text-color: var(--color-text-medium);
 
   --select-border-radius: 4px;
 }
 .Select {
   position: relative;
   display: inline-block;
-  color: var(--select-text-color);
+  color: var(--color-text-medium);
 }
 
 /* custom arrows */
@@ -29,7 +29,7 @@
   margin-top: -0.25rem;
   border-left: 0.3rem solid transparent;
   border-right: 0.3rem solid transparent;
-  border-bottom: 0.3rem solid var(--select-arrow-bg-color);
+  border-bottom: 0.3rem solid var(--color-border);
 }
 
 /* arrow pointing down */
@@ -37,7 +37,7 @@
   margin-top: 0.2rem;
   border-left: 0.3rem solid transparent;
   border-right: 0.3rem solid transparent;
-  border-top: 0.3rem solid var(--select-arrow-bg-color);
+  border-top: 0.3rem solid var(--color-border);
 }
 
 .Select select {
@@ -46,40 +46,40 @@
   padding: 1rem 3rem 1rem 1rem;
   font-size: 0.8em;
   line-height: 1;
-  color: var(--select-text-color);
+  color: var(--color-text-medium);
 
-  border: 1px solid var(--select-border-color);
-  background: var(--select-bg-color);
+  border: 1px solid var(--color-border);
+  background: var(--color-bg-white);
 
   border-radius: var(--select-border-radius);
   -webkit-appearance: none;
   -moz-appearance: none;
 
-  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12);
+  box-shadow: 0 1px 2px var(--color-shadow);
 }
 
 .Select--blue select {
-  color: rgb(78, 146, 223);
-  border-color: rgb(195, 216, 241);
-  background-color: rgb(227, 238, 249);
+  color: var(--color-brand);
+  border-color: var(--color-border);
+  background-color: var(--color-bg-medium);
 }
 .Select--blue:after {
-  border-top: 0.3rem solid rgb(78, 146, 223);
+  border-top: 0.3rem solid var(--color-brand);
 }
 .Select--blue:before {
-  border-bottom: 0.3rem solid rgb(78, 146, 223);
+  border-bottom: 0.3rem solid var(--color-brand);
 }
 
 .Select--purple select {
-  color: rgb(168, 138, 195);
-  border-color: rgb(203, 186, 219);
-  background-color: rgb(231, 223, 239);
+  color: var(--color-accent2);
+  border-color: var(--color-accent2);
+  background-color: var(--color-bg-medium);
 }
 .Select--purple:after {
-  border-top: 0.3rem solid rgb(168, 138, 195);
+  border-top: 0.3rem solid var(--color-accent2);
 }
 .Select--purple:before {
-  border-bottom: 0.3rem solid rgb(168, 138, 195);
+  border-bottom: 0.3rem solid var(--color-accent2);
 }
 
 .Select--small select {
diff --git a/frontend/src/metabase/css/components/table.css b/frontend/src/metabase/css/components/table.css
index d995ae0b45f6d77b6f564838ca78c9368166ff59..98ba247fcac025f306ee5342291cc9f489c28a76 100644
--- a/frontend/src/metabase/css/components/table.css
+++ b/frontend/src/metabase/css/components/table.css
@@ -1,6 +1,6 @@
 :root {
-  --table-border-color: rgba(213, 213, 213, 0.3);
-  --table-alt-bg-color: rgba(0, 0, 0, 0.02);
+  --table-border-color: color(var(--color-border) alpha(-70%));
+  --table-alt-bg-color: color(var(--color-bg-black) alpha(-98%));
 
   --entity-image-small-size: 24px;
   --entity-image-large-size: 64px;
@@ -29,21 +29,21 @@ th {
 }
 
 .Table--bordered {
-  border: 1px solid var(--table-border-color);
+  border: 1px solid color(var(--color-border) alpha(-70%));
 }
 
 .Table tr {
-  border-bottom: 1px solid var(--table-border-color);
+  border-bottom: 1px solid color(var(--color-border) alpha(-70%));
 }
 
 .Table tr:nth-child(even) {
-  background-color: var(--table-alt-bg-color);
+  background-color: color(var(--color-bg-black) alpha(-98%));
 }
 
 .Table th,
 .Table td {
   padding: 1em;
-  border: 1px solid var(--table-border-color);
+  border: 1px solid color(var(--color-border) alpha(-70%));
 }
 
 .ComparisonTable {
@@ -53,5 +53,5 @@ th {
 
 .ComparisonTable th,
 .ComparisonTable td {
-  border-bottom: 1px solid var(--border-color);
+  border-bottom: 1px solid var(--color-border);
 }
diff --git a/frontend/src/metabase/css/core/arrow.css b/frontend/src/metabase/css/core/arrow.css
index f39e075313474487e7ceb1f68ce9d7ea78104857..a4f94e5dfeddee79e05a92d199e747a093926e0a 100644
--- a/frontend/src/metabase/css/core/arrow.css
+++ b/frontend/src/metabase/css/core/arrow.css
@@ -20,13 +20,13 @@
 /* create a slightly larger arrow on the right for border purposes */
 .arrow-right:before {
   right: -20px;
-  border-left-color: #ddd;
+  border-left-color: var(--color-border);
 }
 
 /* create a smaller inset arrow on the right */
 .arrow-right:after {
   right: -19px;
-  border-left-color: #fff;
+  border-left-color: var(--color-white);
 }
 
 /* move our arrows to the center */
diff --git a/frontend/src/metabase/css/core/base.css b/frontend/src/metabase/css/core/base.css
index 0b08e103c2bd19e26cefc42a42879c7613bf2d87..e6dc72edd37d5d55f386631376d5f905faee68b6 100644
--- a/frontend/src/metabase/css/core/base.css
+++ b/frontend/src/metabase/css/core/base.css
@@ -1,8 +1,8 @@
 :root {
   --default-font-family: "Lato";
   --default-font-size: 0.875em;
-  --default-font-color: #2e353b;
-  --default-bg-color: #f9fbfc;
+  --default-font-color: var(--color-text-dark);
+  --default-bg-color: var(--color-bg-light);
 }
 
 html {
@@ -15,12 +15,12 @@ body {
   font-size: var(--default-font-size);
   font-weight: 400;
   font-style: normal;
-  color: var(--default-font-color);
+  color: var(--color-text-dark);
   margin: 0;
   height: 100%; /* ensure the entire page will fill the window */
   display: flex;
   flex-direction: column;
-  background-color: var(--default-bg-color);
+  background-color: var(--color-bg-light);
 
   -webkit-font-smoothing: antialiased;
   -moz-osx-font-smoothing: grayscale;
@@ -76,7 +76,7 @@ textarea {
 }
 
 .MB-lightBG {
-  background-color: #f9fbfc;
+  background-color: var(--color-bg-light);
 }
 
 .circle {
diff --git a/frontend/src/metabase/css/core/bordered.css b/frontend/src/metabase/css/core/bordered.css
index 4cc9ca9b4fa6876be8d851e9162d9b85db6e6c91..674e7a034985306dcbd48d065e14b04dac3f7fbe 100644
--- a/frontend/src/metabase/css/core/bordered.css
+++ b/frontend/src/metabase/css/core/bordered.css
@@ -2,17 +2,17 @@
   --border-size: 1px;
   --border-size-med: 2px;
   --border-style: solid;
-  --border-color: #f0f0f0;
+  --border-color: var(--color-border);
 }
 
 .bordered,
 :local(.bordered) {
-  border: var(--border-size) var(--border-style) var(--border-color);
+  border: var(--border-size) var(--border-style) var(--color-border);
 }
 
 .border-bottom,
 :local(.border-bottom) {
-  border-bottom: var(--border-size) var(--border-style) var(--border-color);
+  border-bottom: var(--border-size) var(--border-style) var(--color-border);
 }
 
 /* ensure that a border-top item inside of a bordred element won't double up */
@@ -22,7 +22,7 @@
 
 .border-top,
 :local(.border-top) {
-  border-top: var(--border-size) var(--border-style) var(--border-color);
+  border-top: var(--border-size) var(--border-style) var(--color-border);
 }
 
 /* ensure that a border-top item inside of a bordred element won't double up */
@@ -31,7 +31,7 @@
 }
 
 .border-column-divider {
-  border-right: var(--border-size) var(--border-style) var(--border-color);
+  border-right: var(--border-size) var(--border-style) var(--color-border);
 }
 
 .border-column-divider:last-child {
@@ -39,7 +39,7 @@
 }
 
 .border-row-divider {
-  border-bottom: var(--border-size) var(--border-style) var(--border-color);
+  border-bottom: var(--border-size) var(--border-style) var(--color-border);
 }
 
 .border-row-divider:last-child {
@@ -47,53 +47,42 @@
 }
 
 .border-right {
-  border-right: var(--border-size) var(--border-style) var(--border-color);
+  border-right: var(--border-size) var(--border-style) var(--color-border);
 }
 
 .border-left {
-  border-left: var(--border-size) var(--border-style) var(--border-color);
+  border-left: var(--border-size) var(--border-style) var(--color-border);
 }
 
 .border-light {
-  border-color: rgba(255, 255, 255, 0.2) !important;
+  border-color: color(var(--color-border) alpha(-80%)) !important;
 }
 
 .border-dark,
 .border-dark-hover:hover {
-  border-color: rgba(0, 0, 0, 0.2) !important;
-}
-
-.border-grey-1 {
-  border-color: var(--grey-1) !important;
-}
-.border-grey-2 {
-  border-color: var(--grey-2) !important;
-}
-
-.border-green {
-  border-color: var(--green-color) !important;
+  border-color: color(var(--color-accent2) alpha(-80%)) !important;
 }
 
 .border-purple {
-  border-color: var(--purple-color) !important;
+  border-color: var(--color-accent2) !important;
 }
 
 .border-error,
 :local(.border-error) {
-  border-color: var(--error-color) !important;
+  border-color: var(--color-error) !important;
 }
 
 .border-gold {
-  border-color: var(--gold-color) !important;
+  border-color: var(--color-warning) !important;
 }
 
 .border-success {
-  border-color: var(--success-color) !important;
+  border-color: var(--color-success) !important;
 }
 
 .border-brand,
 :local(.border-brand) {
-  border-color: var(--brand-color) !important;
+  border-color: var(--color-brand) !important;
 }
 
 .border-transparent {
@@ -101,11 +90,11 @@
 }
 
 .border-brand-hover:hover {
-  border-color: var(--brand-color);
+  border-color: var(--color-brand);
 }
 
 .border-hover:hover {
-  border-color: color(var(--border-color) shade(20%));
+  border-color: var(--color-border);
 }
 
 /* BORDERLESS IS THE DEFAULT */
diff --git a/frontend/src/metabase/css/core/colors.css b/frontend/src/metabase/css/core/colors.css
index 0aefc04656b8f2ef91b58b6f605f21a9cb47be96..48a9c2da42386aabfa0431ff71f832f82b08827b 100644
--- a/frontend/src/metabase/css/core/colors.css
+++ b/frontend/src/metabase/css/core/colors.css
@@ -1,52 +1,83 @@
+/* NOTE: DO NOT ADD COLORS WITHOUT EXTREMELY GOOD REASON AND DESIGN REVIEW
+ * NOTE: KEEP SYNCRONIZED WITH COLORS.JS
+ */
 :root {
-  --brand-color: #509ee3;
-  --brand-light-color: #cde3f8;
-  --brand-saturated-color: #2d86d4;
-
-  --base-grey: #f8f9fa;
-  --grey-5percent: color(var(--base-grey) shade(5%));
-  --grey-1: color(var(--base-grey) shade(10%));
-  --grey-2: color(var(--base-grey) shade(20%));
-  --grey-3: color(var(--base-grey) shade(30%));
-  --grey-4: color(var(--base-grey) shade(40%));
-  --grey-5: color(var(--base-grey) shade(50%));
-
-  --grey-text-color: #797979;
-  --alt-color: #f5f7f9;
-  --alt-bg-color: #f4f6f8;
-
-  --success-color: #9cc177;
-  --headsup-color: #f5a623;
-
-  --gold-color: #f9d45c;
-  --orange-color: #f9a354;
-  --purple-color: #a989c5;
-  --green-color: #9cc177;
-  --green-saturated-color: #90bd64;
-  --dark-color: #4c545b;
-  --slate-color: #9ba5b1;
-  --slate-light-color: #dfe8ea;
-  --slate-almost-extra-light-color: #edf2f5;
-  --slate-extra-light-color: #f9fbfc;
-
-  --error-color: #e35050;
-
-  /* type colors */
-  --metric-color: #9cc177;
-  --segment-color: #7172ad;
-  --pulse-color: #f9d45c;
-  --dashboard-color: #509ee3;
-  --data-color: "#9cc177";
-  --question-color: #93b3c9;
-}
+  --color-brand: #509ee3;
+  --color-accent1: #9cc177;
+  --color-accent2: #a989c5;
+  --color-accent3: #ef8c8c;
+  --color-accent4: #f9d45c;
+  --color-accent5: #f1b556;
+  --color-accent6: #a6e7f3;
+  --color-accent7: #7172ad;
+  --color-white: #ffffff;
+  --color-black: #2e353b;
+  --color-success: #84bb4c;
+  --color-error: #ed6e6e;
+  --color-warning: #f9cf48;
+  --color-text-dark: #2e353b;
+  --color-text-medium: #74838f;
+  --color-text-light: #c7cfd4;
+  --color-text-white: #ffffff;
+  --color-bg-black: #2e353b;
+  --color-bg-dark: #93a1ab;
+  --color-bg-medium: #edf2f5;
+  --color-bg-light: #f9fbfc;
+  --color-bg-white: #ffffff;
+  --color-shadow: rgba(0, 0, 0, 0.08);
+  --color-border: #d7dbde;
+}
+
+/* NOTE: DEPRECATED, replaced with colors above
+:root {
+  --brand-color: var(--color-brand);
+  --brand-light-color: var(--color-text-light);
+  --brand-saturated-color: var(--color-brand);
+
+  --base-grey: var(--color-bg-light);
+  --grey-5percent: var(--color-bg-medium);
+  --grey-1: var(--color-text-light);
+  --grey-2: var(--color-text-light);
+  --grey-3: var(--color-text-medium);
+  --grey-4: var(--color-text-medium);
+  --grey-5: var(--color-text-medium);
+
+  --grey-text-color: var(--color-text-medium);
+  --alt-color: var(--color-bg-light);
+  --alt-bg-color: var(--color-bg-light);
+
+  --success-color: var(--color-accent1);
+  --headsup-color: var(--color-warning);
+
+  --gold-color: var(--color-accent4);
+  --orange-color: var(--color-warning);
+  --purple-color: var(--color-accent2);
+  --green-color: var(--color-accent1);
+  --green-saturated-color: var(--color-accent1);
+  --dark-color: var(--color-text-dark);
+  --slate-color: var(--color-text-medium);
+  --slate-light-color: var(--color-text-light);
+  --slate-almost-extra-light-color: var(--color-bg-medium);
+  --slate-extra-light-color: var(--color-bg-light);
+
+  --error-color: var(--color-error);
+
+  --metric-color: var(--color-accent1);
+  --segment-color: var(--color-accent2);
+  --pulse-color: var(--color-accent4);
+  --dashboard-color: var(--color-brand);
+  --data-color: var(--color-accent1);
+  --question-color: var(--color-text-medium);
+}
+*/
 
 .text-default,
 :local(.text-default) {
-  color: var(--default-font-color);
+  color: var(--color-text-dark);
 }
 
 .text-default-hover:hover {
-  color: var(--default-font-color);
+  color: var(--color-text-dark);
 }
 
 /* brand */
@@ -54,40 +85,40 @@
 :local(.text-brand),
 .text-brand-hover:hover,
 :local(.text-brand-hover):hover {
-  color: var(--brand-color);
+  color: var(--color-brand);
 }
 
 .text-brand-darken,
 .text-brand-darken-hover:hover {
-  color: color(var(--brand-color) shade(20%));
+  color: var(--color-brand);
 }
 
 .text-brand-light,
 :local(.text-brand-light),
 .text-brand-light-hover:hover,
 :local(.text-brand-light-hover):hover {
-  color: var(--brand-light-color);
+  color: var(--color-text-light);
 }
 
 .bg-brand,
 .bg-brand-hover:hover,
 .bg-brand-active:active {
-  background-color: var(--brand-color);
+  background-color: var(--color-brand);
 }
 
 @media screen and (--breakpoint-min-md) {
   .md-bg-brand {
-    background-color: var(--brand-color) !important;
+    background-color: var(--color-brand) !important;
   }
 }
 
 /* success */
 
 .text-success {
-  color: var(--success-color);
+  color: var(--color-accent1);
 }
 .bg-success {
-  background-color: var(--success-color);
+  background-color: var(--color-accent1);
 }
 
 /* error */
@@ -95,191 +126,151 @@
 .text-error,
 :local(.text-error),
 .text-error-hover:hover {
-  color: var(--error-color);
+  color: var(--color-error);
 }
 
 .bg-error,
 .bg-error-hover:hover {
-  background-color: var(--error-color);
+  background-color: var(--color-error);
 }
 .bg-error-input {
-  background-color: #fce8e8;
+  background-color: var(--color-bg-white);
 }
 
 /* favorite */
 .text-gold,
 .text-gold-hover:hover {
-  color: var(--gold-color);
+  color: var(--color-accent4);
 }
 
 .text-purple,
 .text-purple-hover:hover {
-  color: var(--purple-color);
+  color: var(--color-accent2);
 }
 
 .text-green,
 .text-green-hover:hover {
-  color: var(--green-color);
+  color: var(--color-accent1);
 }
 
 .text-green-saturated,
 .text-green-saturated-hover:hover {
-  color: var(--green-saturated-color);
+  color: var(--color-accent1);
 }
 
 .text-orange,
 .text-orange-hover:hover {
-  color: var(--orange-color);
+  color: var(--color-warning);
 }
 
 .text-slate {
-  color: var(--slate-color);
+  color: var(--color-text-medium);
 }
 .text-slate-light {
-  color: var(--slate-light-color);
+  color: var(--color-text-light);
 }
 .text-slate-extra-light {
-  background-color: var(--slate-extra-light-color);
+  background-color: var(--color-bg-light);
 }
 
 .bg-gold {
-  background-color: var(--gold-color);
+  background-color: var(--color-accent4);
 }
 
 .bg-purple,
 .bg-purple-hover:hover {
-  background-color: var(--purple-color);
+  background-color: var(--color-accent2);
 }
 
 .bg-green {
-  background-color: var(--green-color);
+  background-color: var(--color-accent1);
 }
 .bg-green-saturated,
 .bg-green-saturated-hover:hover {
-  background-color: var(--green-saturated-color);
+  background-color: var(--color-accent1);
 }
 
 /* alt */
 .bg-alt,
 .bg-alt-hover:hover {
-  background-color: var(--alt-color);
-}
-
-/* grey */
-.text-grey-1,
-:local(.text-grey-1),
-.text-grey-1-hover:hover {
-  color: var(--grey-1);
-}
-
-.text-grey-2,
-:local(.text-grey-2),
-.text-grey-2-hover:hover {
-  color: var(--grey-2);
+  background-color: var(--color-bg-light);
 }
 
-.text-grey-3,
-:local(.text-grey-3),
-.text-grey-3-hover:hover {
-  color: var(--grey-3);
+.text-light,
+:local(.text-light),
+.text-light-hover:hover {
+  color: var(--color-text-light);
 }
 
-.text-grey-4,
-.text-grey-4-hover:hover {
-  color: var(--grey-4);
+.text-medium,
+:local(.text-medium),
+.text-medium-hover:hover {
+  color: var(--color-text-medium);
 }
 
-.text-grey-5,
-.text-grey-5-hover:hover {
-  color: var(--grey-5);
+.text-dark,
+:local(.text-dark),
+.text-dark-hover {
+  color: var(--color-text-dark);
 }
 
-.bg-grey-0,
-.bg-grey-0-hover:hover {
-  background-color: var(--base-grey);
-}
-.bg-grey-05 {
-  background-color: var(--grey-5percent);
-}
-.bg-grey-1 {
-  background-color: var(--grey-1);
-}
-.bg-grey-2 {
-  background-color: var(--grey-2);
-}
-.bg-grey-3 {
-  background-color: var(--grey-3);
-}
-.bg-grey-4 {
-  background-color: var(--grey-4);
-}
-.bg-grey-5 {
-  background-color: var(--grey-5);
+.bg-light,
+.bg-light-hover:hover {
+  background-color: var(--color-bg-light);
 }
 
-.bg-slate {
-  background-color: var(--slate-color);
-}
-.bg-slate-light {
-  background-color: var(--slate-light-color);
-}
-.bg-slate-almost-extra-light {
-  background-color: var(--slate-almost-extra-light-color);
-}
-.bg-slate-extra-light {
-  background-color: var(--slate-extra-light-color);
-}
-.bg-slate-extra-light-hover:hover {
-  background-color: var(--slate-extra-light-color);
+.bg-medium,
+.bg-medium-hover:hover {
+  background-color: var(--color-bg-medium);
 }
 
-.text-dark,
-:local(.text-dark) {
-  color: var(--dark-color);
+.bg-dark,
+.bg-dark-hover:hover {
+  background-color: var(--color-bg-dark);
 }
 
 /* white  - move to bottom for specificity since its often used on hovers, etc */
 .text-white,
 :local(.text-white),
 .text-white-hover:hover {
-  color: #fff;
+  color: var(--color-text-white);
 }
 
 @media screen and (--breakpoint-min-md) {
   .md-text-white {
-    color: #fff;
+    color: var(--color-text-white);
   }
 }
 
 /* common pattern, background brand, text white when hovering or selected */
 .brand-hover:hover {
-  color: #fff;
-  background-color: var(--brand-color);
+  color: var(--color-text-white);
+  background-color: var(--color-brand);
 }
 .brand-hover:hover * {
-  color: #fff;
+  color: var(--color-text-white);
 }
 
 .bg-white,
 :local(.bg-white),
 .bg-white-hover:hover {
-  background-color: #fff;
+  background-color: var(--color-bg-white);
 }
 
 .bg-light-blue {
-  background-color: #f5fafc;
+  background-color: var(--color-bg-light);
 }
 
 .bg-light-blue-hover:hover {
-  background-color: #e4f0fa;
+  background-color: var(--color-bg-medium);
 }
 
 .text-light-blue,
 .text-light-blue-hover:hover {
-  color: #cfe4f5;
+  color: var(--color-text-light);
 }
 .text-slate {
-  color: #606e7b;
+  color: var(--color-text-medium);
 }
 
 .bg-transparent {
@@ -289,43 +280,53 @@
 /* entity colors */
 
 .bg-metric {
-  background-color: var(--metric-color);
+  background-color: var(--color-accent1);
 }
 .text-metric {
-  color: var(--metric-color);
+  color: var(--color-accent1);
 }
 
 .bg-data {
-  background-color: var(--data-color);
+  background-color: var(--color-accent1);
 }
 .text-data {
-  color: var(--data-color);
+  color: var(--color-accent1);
 }
 
 .bg-segment {
-  background-color: var(--segment-color);
+  background-color: var(--color-accent2);
 }
 .text-segment {
-  color: var(--segment-color);
+  color: var(--color-accent2);
 }
 
 .bg-dashboard {
-  background-color: var(--dashboard-color);
+  background-color: var(--color-brand);
 }
 .text-dashboard {
-  color: var(--dashboard-color);
+  color: var(--color-brand);
 }
 
 .bg-pulse {
-  background-color: var(--pulse-color);
+  background-color: var(--color-accent4);
 }
 .text-pulse {
-  color: var(--pulse-color);
+  color: var(--color-accent4);
 }
 
 .bg-question {
-  background-color: var(--question-color);
+  background-color: var(--color-bg-dark);
 }
 .text-question {
-  color: var(--question-color);
+  color: var(--color-text-medium);
+}
+
+.text-light {
+  color: var(--color-text-light);
+}
+.text-medium {
+  color: var(--color-text-medium);
+}
+.text-dark {
+  color: var(--color-text-dark);
 }
diff --git a/frontend/src/metabase/css/core/cursor.css b/frontend/src/metabase/css/core/cursor.css
index 3e50861fcc9a21c05b25cdf0302d29056a034939..f035d78d6d5addf695295032b79eb9f7f574495c 100644
--- a/frontend/src/metabase/css/core/cursor.css
+++ b/frontend/src/metabase/css/core/cursor.css
@@ -3,6 +3,11 @@
   cursor: pointer;
 }
 
+.cursor-grab,
+:local(.cursor-grab) {
+  cursor: grab;
+}
+
 .cursor-default,
 :local(.cursor-default) {
   cursor: default;
diff --git a/frontend/src/metabase/css/core/hover.css b/frontend/src/metabase/css/core/hover.css
index 2d48da6c4f9d38739e44a05559988423a6a5849a..1103a80be90da515d500b4eddc85497c31585e87 100644
--- a/frontend/src/metabase/css/core/hover.css
+++ b/frontend/src/metabase/css/core/hover.css
@@ -23,3 +23,7 @@
 .hover-parent:hover.hover--visibility .hover-child {
   visibility: visible;
 }
+
+.hover-parent:hover.hover--inherit > * {
+  color: inherit !important;
+}
diff --git a/frontend/src/metabase/css/core/inputs.css b/frontend/src/metabase/css/core/inputs.css
index db6c5b3b1939a16c96c1b5d12236b5455b043c17..48e1c665ec3fb02f4887100b580a95356842c928 100644
--- a/frontend/src/metabase/css/core/inputs.css
+++ b/frontend/src/metabase/css/core/inputs.css
@@ -1,15 +1,15 @@
 :root {
-  --input-border-color: #d9d9d9;
-  --input-border-active-color: #4e82c0;
+  --input-border-color: var(--color-border);
+  --input-border-active-color: var(--color-brand);
   --input-border-radius: 4px;
 }
 
 .input,
 :local(.input) {
-  color: var(--dark-color);
+  color: var(--color-text-dark);
   font-size: 1.12em;
   padding: 0.75rem 0.75rem;
-  border: 1px solid var(--input-border-color);
+  border: 1px solid var(--color-border);
   border-radius: var(--input-border-radius);
   transition: border 0.3s linear;
 }
@@ -29,9 +29,9 @@
 .input:focus,
 :local(.input):focus {
   outline: none;
-  border: 1px solid var(--input-border-active-color);
+  border: 1px solid var(--color-brand);
   transition: border 0.3s linear;
-  color: #222;
+  color: var(--color-text-dark);
 }
 
 .input--borderless,
diff --git a/frontend/src/metabase/css/core/layout.css b/frontend/src/metabase/css/core/layout.css
index e23e95c44994ea4c691830169af7ed73966b90e0..eb05e213a1cc999af311cceee3ceea466fa2d944 100644
--- a/frontend/src/metabase/css/core/layout.css
+++ b/frontend/src/metabase/css/core/layout.css
@@ -33,11 +33,6 @@
   height: 100%;
 }
 
-/* set height to that of the viewport */
-.viewport-height {
-  height: 100vh;
-}
-
 /* display utilities */
 .block,
 :local(.block) {
@@ -93,6 +88,12 @@
   position: absolute;
 }
 
+@media screen and (--breakpoint-min-sm) {
+  .sm-absolute {
+    position: absolute;
+  }
+}
+
 .top,
 :local(.top) {
   top: 0;
diff --git a/frontend/src/metabase/css/core/link.css b/frontend/src/metabase/css/core/link.css
index e3e272244fc98bebe0873e8c93ee4d8d7c8ba34e..db9d956cedc2fe2d975ec439dd5a84888173be3d 100644
--- a/frontend/src/metabase/css/core/link.css
+++ b/frontend/src/metabase/css/core/link.css
@@ -1,5 +1,5 @@
 :root {
-  --default-link-color: #4a90e2;
+  --default-link-color: var(--color-brand);
 }
 
 .no-decoration,
@@ -10,7 +10,7 @@
 .link {
   cursor: pointer;
   text-decoration: none;
-  color: var(--default-link-color);
+  color: var(--color-brand);
 }
 .link:hover {
   text-decoration: underline;
diff --git a/frontend/src/metabase/css/core/scroll.css b/frontend/src/metabase/css/core/scroll.css
index dee6d79171864284c498c76baf37f16b89c7f0b1..ef4af0fd5c1f50df78c4d1d0694974a6c67ddf36 100644
--- a/frontend/src/metabase/css/core/scroll.css
+++ b/frontend/src/metabase/css/core/scroll.css
@@ -22,7 +22,7 @@
   border: 4px solid transparent;
   border-radius: 7px;
   background-clip: padding-box;
-  background-color: #c2c2c2;
+  background-color: var(--color-bg-medium);
 }
 
 .scroll-show::-webkit-scrollbar-button {
@@ -35,28 +35,28 @@
 }
 
 .scroll-show:hover::-webkit-scrollbar-thumb {
-  background-color: #7d7d7d;
+  background-color: var(--color-bg-dark);
 }
 .scroll-show::-webkit-scrollbar-thumb:horizontal:hover,
 .scroll-show::-webkit-scrollbar-thumb:vertical:hover {
-  background-color: #7d7d7d;
+  background-color: var(--color-bg-dark);
 }
 .scroll-show::-webkit-scrollbar-thumb:horizontal:active,
 .scroll-show::-webkit-scrollbar-thumb:vertical:active {
-  background-color: #7d7d7d;
+  background-color: var(--color-bg-dark);
 }
 
 /* scroll light */
 .scroll-show.scroll--light::-webkit-scrollbar-thumb {
   border-radius: 0;
-  background-color: #cfe4f5;
+  background-color: var(--color-bg-medium);
 }
 
 .scroll-show.scroll--light::-webkit-scrollbar-thumb:horizontal:hover,
 .scroll-show.scroll--light::-webkit-scrollbar-thumb:vertical:hover,
 .scroll-show.scroll--light::-webkit-scrollbar-thumb:horizontal:active,
 .scroll-show.scroll--light::-webkit-scrollbar-thumb:vertical:active {
-  background-color: #c7d9e4;
+  background-color: var(--color-bg-medium);
 }
 
 .scroll-hide {
diff --git a/frontend/src/metabase/css/core/shadow.css b/frontend/src/metabase/css/core/shadow.css
index 23f04090f49652791d347d65d6e3d3e489d69af4..77772844a23b68be73c896c50eb03b4be0fa8427 100644
--- a/frontend/src/metabase/css/core/shadow.css
+++ b/frontend/src/metabase/css/core/shadow.css
@@ -1,13 +1,13 @@
 :root {
-  --shadow-color: rgba(0, 0, 0, 0.08);
-  --shadow-hover-color: rgba(0, 0, 0, 0.12);
+  --shadow-color: var(--color-shadow);
+  --shadow-hover-color: var(--color-shadow);
 }
 .shadowed,
 :local(.shadowed) {
-  box-shadow: 0 2px 2px var(--shadow-color);
+  box-shadow: 0 2px 2px var(--color-shadow);
 }
 
 .shadow-hover:hover {
-  box-shadow: 0 2px 2px var(--shadow-hover-color);
+  box-shadow: 0 2px 2px color(var(--color-shadow) alpha(20%));
   transition: box-shadow 300ms linear;
 }
diff --git a/frontend/src/metabase/css/core/text.css b/frontend/src/metabase/css/core/text.css
index 2cdd2b7eb1c1a088c7e05535068758840b008428..d4fc21679781f7dd3c8eaf80d5f0acc9ec9e6934 100644
--- a/frontend/src/metabase/css/core/text.css
+++ b/frontend/src/metabase/css/core/text.css
@@ -1,6 +1,6 @@
 :root {
-  --body-text-color: #8e9ba9;
-  --70-percent-black: #444444;
+  --body-text-color: var(--color-text-medium);
+  --70-percent-black: var(--color-text-dark);
 }
 
 /* center */
@@ -131,7 +131,7 @@
 :local(.text-body) {
   font-size: 1.286em;
   line-height: 1.457em;
-  color: var(--body-text-color); /* TODO - is this bad? */
+  color: var(--color-text-medium); /* TODO - is this bad? */
 }
 
 .text-paragraph,
@@ -170,8 +170,8 @@
 
 .text-code {
   font-family: monospace;
-  color: #8691ac;
-  background-color: #e9f2f5;
+  color: var(--color-text-medium);
+  background-color: var(--color-bg-medium);
   border-radius: 2px;
   padding: 0.2em 0.4em;
   line-height: 1.4em;
diff --git a/frontend/src/metabase/css/dashboard.css b/frontend/src/metabase/css/dashboard.css
index 94657d12f12c1ac2dffdae60a53d5206242cd164..7a5b9ae35e363fc649a5e6f737c7dd9f9a73d76f 100644
--- a/frontend/src/metabase/css/dashboard.css
+++ b/frontend/src/metabase/css/dashboard.css
@@ -1,14 +1,14 @@
 :root {
-  --night-mode-color: rgba(255, 255, 255, 0.86);
+  --night-mode-color: color(var(--color-text-white) alpha(-14%));
 }
 .Dashboard {
-  background-color: #f9fbfc;
+  background-color: var(--color-bg-light);
   min-height: 100vh;
 }
 
 .DashboardHeader {
   background-color: white;
-  border-bottom: var(--border-size) var(--border-style) var(--border-color);
+  border-bottom: var(--border-size) var(--border-style) var(--color-border);
 }
 
 .Dash-wrapper {
@@ -35,7 +35,7 @@
 
 /* Fullscreen mode */
 .Dashboard.Dashboard--fullscreen .Header-button {
-  color: #d2dbe4;
+  color: var(--color-text-light);
 }
 
 .Dashboard.Dashboard--fullscreen .DashboardHeader {
@@ -48,35 +48,35 @@
 
 /* Night mode */
 .Dashboard.Dashboard--night {
-  background-color: rgb(34, 37, 39);
+  background-color: var(--color-bg-black);
 }
 
 .Dashboard.Dashboard--night .Card {
-  color: #fff;
+  color: var(--color-text-white);
 }
 
 .Dashboard.Dashboard--night .Header-button,
 .Dashboard.Dashboard--night .Header-button svg {
-  color: rgba(151, 151, 151, 0.3);
+  color: color(var(--color-text-medium) alpha(-70%));
 }
 
 .Dashboard.Dashboard--fullscreen .fullscreen-normal-text {
-  color: #3f3a3a;
+  color: var(--color-text-dark);
   transition: color 1s linear;
 }
 
 .Dashboard.Dashboard--night.Dashboard--fullscreen .fullscreen-night-text {
-  color: var(--night-mode-color);
+  color: color(var(--color-text-white) alpha(-14%));
   transition: color 1s linear;
 }
 
 .Dashboard.Dashboard--night .DashCard .Card svg text {
-  fill: rgba(255, 255, 255, 0.86) !important;
+  fill: color(var(--color-text-white) alpha(-14%)) !important;
 }
 
 .Dashboard.Dashboard--night .DashCard .Card {
-  background-color: rgb(54, 58, 61);
-  border: 1px solid rgb(46, 49, 52);
+  background-color: var(--color-bg-black);
+  border: 1px solid var(--color-accent2);
 }
 
 .Dashboard.Dashboard--night .enable-dots-onhover .dc-tooltip circle.dot:hover,
@@ -107,11 +107,11 @@
   right: 0;
   overflow: hidden;
   background-color: white;
-  border: 1px solid rgb(219, 219, 219);
+  border: 1px solid var(--color-border);
 }
 
 .DashCard .Card.Card--slow {
-  border-color: var(--gold-color);
+  border-color: var(--color-accent4);
 }
 
 .Dash--editing .DashCard .Card {
@@ -124,10 +124,10 @@
 
 @keyframes fade-out-yellow {
   from {
-    background-color: rgba(255, 250, 243, 1);
+    background-color: var(--color-bg-white);
   }
   to {
-    background-color: rgba(255, 255, 255, 1);
+    background-color: var(--color-bg-white);
   }
 }
 
@@ -162,7 +162,7 @@
 
 .PinMapUpdateButton--disabled {
   pointer-events: none;
-  color: color(var(--base-grey) shade(10%));
+  color: var(--color-text-light);
 }
 
 .Dash--editing .DashCard .Card {
@@ -170,11 +170,11 @@
 }
 
 .DashCard .Card {
-  box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.08);
+  box-shadow: 0px 1px 3px var(--color-shadow);
 }
 
 .Dash--editing .DashCard.dragging .Card {
-  box-shadow: 3px 3px 8px rgba(0, 0, 0, 0.1);
+  box-shadow: 3px 3px 8px var(--color-shadow);
 }
 
 .Dash--editing .DashCard.dragging,
@@ -184,8 +184,8 @@
 
 .Dash--editing .DashCard.dragging .Card,
 .Dash--editing .DashCard.resizing .Card {
-  background-color: #e5f1fb !important;
-  border: 1px solid var(--brand-color);
+  background-color: var(--color-bg-medium) !important;
+  border: 1px solid var(--color-brand);
 }
 
 .DashCard .DashCard-actions {
@@ -250,15 +250,15 @@
   height: 8px;
   bottom: 10px;
   right: 10px;
-  border-bottom: 2px solid color(var(--base-grey) shade(20%));
-  border-right: 2px solid color(var(--base-grey) shade(20%));
+  border-bottom: 2px solid var(--color-border);
+  border-right: 2px solid var(--color-border);
   border-bottom-right-radius: 2px;
   transition: opacity 0.2s;
   opacity: 0.01;
 }
 
 .Dash--editing .DashCard .react-resizable-handle:hover:after {
-  border-color: color(var(--base-grey) shade(40%));
+  border-color: var(--color-border);
 }
 
 .Dash--editing .DashCard:hover .react-resizable-handle:after {
@@ -272,7 +272,7 @@
 
 .Dash--editing .react-grid-placeholder {
   z-index: 0;
-  background-color: #f2f2f2;
+  background-color: var(--color-bg-light);
   transition: all 0.15s linear;
 }
 
@@ -330,13 +330,13 @@
   }
   .DashCard .Card {
     box-shadow: none;
-    border-color: #a1a1a1;
+    border-color: var(--color-border);
   }
   /* improve label contrast */
   .dc-chart .axis .tick text,
   .dc-chart .x-axis-label,
   .dc-chart .y-axis-label {
-    fill: #222222;
+    fill: var(--color-text-dark);
   }
 }
 
@@ -362,5 +362,5 @@
 
 /* when in night mode code snippets should have a more readable background-color */
 .Dashboard--night pre code {
-  background-color: rgba(255, 255, 255, 0.14);
+  background-color: color(var(--color-bg-white) alpha(-86%));
 }
diff --git a/frontend/src/metabase/css/home.css b/frontend/src/metabase/css/home.css
index fde098b191d80dd31268fa164bc171ff87976e29..d7e7d553a258f82fb71c57eb1e04686599f2d0e3 100644
--- a/frontend/src/metabase/css/home.css
+++ b/frontend/src/metabase/css/home.css
@@ -1,160 +1,6 @@
-:root {
-  --search-bar-color: #60a6e4;
-  --search-bar-active-color: #7bb7ec;
-  --search-bar-active-border-color: #4894d8;
-}
-
 .Nav {
   z-index: 4;
-}
-
-/* temporary css for the navbar and search */
-.search-bar {
-  background-color: var(--search-bar-color);
-  border-color: transparent;
-  color: white;
-}
-
-.nav-light {
-  background-color: var(--search-bar-color);
-}
-
-.search-bar--active {
-  background-color: var(--search-bar-active-color);
-  border-color: var(--search-bar-active-border-color);
-}
-
-.NavItem.NavItem--selected {
-  background-color: rgba(0, 0, 0, 0.2);
-}
-
-.NavItem {
-  justify-content: center;
-}
-
-.NavItem > .Icon {
-  padding-left: 1em;
-  padding-right: 1em;
-  padding-top: 0.5em;
-  padding-bottom: 0.5em;
-}
-
-@media screen and (--breakpoint-min-sm) {
-  .NavItem {
-    border-radius: 8px;
-  }
-  .NavItem:hover,
-  .NavItem.NavItem--selected {
-    background-color: rgba(255, 255, 255, 0.08);
-  }
-}
-
-.NavNewQuestion {
-  box-shadow: 0px 2px 2px 0px rgba(77, 136, 189, 0.69);
-}
-.NavNewQuestion:hover {
-  box-shadow: 0px 3px 2px 2px rgba(77, 136, 189, 0.75);
-  color: #3875ac;
-}
-
-.Greeting {
-  padding-top: 2rem;
-  padding-bottom: 3rem;
-}
-
-@media screen and (--breakpoint-min-xl) {
-  .Greeting {
-    padding-top: 6em;
-    padding-bottom: 6em;
-  }
-}
-
-.bullet {
-  position: relative;
-  margin-left: 1.2em;
-}
-.bullet:before {
-  content: "\2022";
-  color: #6fb0eb;
-  position: absolute;
-  top: 0;
-  margin-top: 16px;
-  left: -0.85em;
-}
-
-.NavDropdown {
-  position: relative;
-}
-.NavDropdown.open {
-  z-index: 100;
-}
-.NavDropdown .NavDropdown-content {
-  display: none;
-}
-.NavDropdown.open .NavDropdown-content {
-  display: inherit;
-}
-.NavDropdown .NavDropdown-button {
-  position: relative;
-  border-radius: 8px;
-}
-.NavDropdown .NavDropdown-content {
-  position: absolute;
-  border-radius: 4px;
-  top: 38px;
-  min-width: 200px;
-}
-
-.NavDropdown .NavDropdown-content.NavDropdown-content--dashboards {
-  top: 33px;
-}
-
-.NavDropdown .NavDropdown-button:before,
-.NavDropdown .NavDropdown-content:before {
-  content: "";
-  position: absolute;
-  top: 0;
-  left: 0;
-  width: 100%;
-  height: 100%;
-  box-shadow: 0 0 4px rgba(0, 0, 0, 0.12);
-  background-clip: padding-box;
-}
-
-.NavDropdown .NavDropdown-content:before {
-  z-index: -2;
-  border-radius: 4px;
-}
-.NavDropdown .NavDropdown-button:before {
-  z-index: -1;
-  opacity: 0;
-  border-radius: 8px;
-}
-.NavDropdown.open .NavDropdown-button:before {
-  opacity: 1;
-}
-.NavDropdown .NavDropdown-content-layer {
-  position: relative;
-  z-index: 1;
-  overflow: hidden;
-}
-.NavDropdown .NavDropdown-button-layer {
-  position: relative;
-  z-index: 2;
-}
-
-.NavDropdown.open .NavDropdown-button,
-.NavDropdown .NavDropdown-content-layer {
-  background-color: #6fb0eb;
-}
-
-.NavDropdown .NavDropdown-content-layer {
-  padding-top: 10px;
-  border-radius: 4px;
-}
-
-.NavDropdown .DashboardList {
-  min-width: 332px;
+  flex-shrink: 0;
 }
 
 .QuestionCircle {
@@ -167,61 +13,23 @@
   text-align: center;
 }
 
-.IconCircle {
-  line-height: 0;
-  padding: var(--padding-1);
-  border-radius: 99px;
-  border: 1px solid currentcolor;
-}
-
-@keyframes pop {
-  0% {
-    transform: scale(0.75);
-  }
-  75% {
-    transform: scale(1.0625);
-  }
-  100% {
-    transform: scale(1);
-  }
-}
-
-.animate-pop {
-  animation-name: popin;
-  animation-duration: 0.15s;
-  animation-timing-function: ease-out;
-}
-
-.AdminLink {
-  opacity: 0.435;
-}
-
-.AdminLink:hover {
-  opacity: 1;
-}
-
 .break-word {
   word-wrap: break-word;
 }
 
 .tooltip {
   position: absolute;
-  background-color: #fff;
+  background-color: var(--color-bg-white);
   border-radius: 2px;
-  box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.12);
-  color: #ddd;
-}
-
-.TableDescription {
-  max-width: 42rem;
-  line-height: 1.4;
+  box-shadow: 1px 1px 1px var(--color-shadow);
+  color: var(--color-text-light);
 }
 
 .Layout-sidebar {
   min-height: 100vh;
   width: 346px;
-  background-color: #f9fbfc;
-  border-left: 2px solid var(--border-color);
+  background-color: var(--color-bg-light);
+  border-left: 2px solid var(--color-border);
 }
 .Layout-mainColumn {
   max-width: 700px;
@@ -235,38 +43,3 @@
   line-height: 1;
   text-transform: uppercase;
 }
-
-@media screen and (--breakpoint-min-md) {
-  .HomepageGreeting {
-    margin-right: 346px;
-  }
-}
-
-/* there are 5 mav items on mobile, so distribute the items evenly */
-.Nav ul li {
-  flex: 0 20%;
-}
-
-/* on larger screens, things should just flow naturally */
-@media screen and (--breakpoint-min-md) {
-  .Nav ul li {
-    flex: unset;
-  }
-}
-
-/* the logo nav item needs a little bit of additional padding so that it
- * matches up with the other nav items which have 16px icons and 0.75em padding.
- * Since the logo is 32px we cut the padding in half to get 0.375
- * */
-.LogoNavItem {
-  padding-top: 0.375em;
-  padding-bottom: 0.375em;
-}
-
-/* we want to unset the above when we no longer need the nav item to fill the space */
-@media screen and (--breakpoint-min-md) {
-  .LogoNavItem {
-    padding-top: 0;
-    padding-bottom: 0;
-  }
-}
diff --git a/frontend/src/metabase/css/index.css b/frontend/src/metabase/css/index.css
index f3cfe5e7ca6a542c19437509ed7b3e4153985c39..81c606efa54a99fb1338ef6f90488f4b2c8b28b2 100644
--- a/frontend/src/metabase/css/index.css
+++ b/frontend/src/metabase/css/index.css
@@ -3,7 +3,6 @@
 @import "./core/index.css";
 
 @import "./components/buttons.css";
-@import "./components/dropdown.css";
 @import "./components/form.css";
 @import "./components/header.css";
 @import "./components/icons.css";
diff --git a/frontend/src/metabase/css/login.css b/frontend/src/metabase/css/login.css
index e30bee15dda3293b65e6ce227bf9435a8dc3e28a..540edf16482f88c9d2819fffa68a0c4a42f5f417 100644
--- a/frontend/src/metabase/css/login.css
+++ b/frontend/src/metabase/css/login.css
@@ -10,7 +10,7 @@
 }
 
 .Login-header {
-  color: #6a6a6a;
+  color: var(--color-text-dark);
 }
 
 .brand-scene {
@@ -121,16 +121,16 @@
   display: flex;
   flex-direction: column;
   align-items: center;
-  color: var(--success-color);
+  color: var(--color-accent1);
   padding: 4em;
 }
 
 .SuccessMark {
   display: flex;
   padding: 1em;
-  border: 3px solid var(--success-color);
+  border: 3px solid var(--color-accent1);
   border-radius: 99px;
-  color: var(--success-color);
+  color: var(--color-accent1);
   line-height: 1;
 }
 
diff --git a/frontend/src/metabase/css/pulse.css b/frontend/src/metabase/css/pulse.css
index 29bd27b47ac9575f3cf260fb2b1964bffd72927f..d258aebab5094a39462a691030f74f1e5f0ee697 100644
--- a/frontend/src/metabase/css/pulse.css
+++ b/frontend/src/metabase/css/pulse.css
@@ -12,10 +12,10 @@
 }
 
 .PulseButton {
-  color: rgb(121, 130, 127);
+  color: var(--color-text-medium);
   font-weight: 700;
   border-width: 2px;
-  border-color: rgb(222, 228, 226);
+  border-color: var(--color-border);
 }
 
 .PulseEdit .input,
@@ -24,7 +24,7 @@
 .PulseEdit .border-row-divider,
 .PulseEdit .AdminSelect {
   border-width: 2px;
-  border-color: rgb(222, 228, 226);
+  border-color: var(--color-border);
 }
 
 .PulseEdit .AdminSelect {
@@ -34,17 +34,13 @@
 .PulseEdit .input:focus,
 .PulseEdit .input--focus {
   border-width: 2px;
-  border-color: rgb(97, 167, 229) !important;
+  border-color: var(--color-brand) !important;
 }
 
 .PulseListItem button {
   font-family: "Lato", Helvetica, sans-serif;
 }
 
-.bg-grey-0 {
-  background-color: rgb(252, 252, 253);
-}
-
 .PulseEditButton {
   opacity: 0;
   transition: opacity 0.3s linear;
@@ -59,27 +55,27 @@
 }
 
 .PulseListItem.PulseListItem--focused {
-  border-color: #509ee3;
-  box-shadow: 0 0 3px #509ee3;
+  border-color: var(--color-brand);
+  box-shadow: 0 0 3px var(--color-shadow);
 }
 
 .DangerZone:hover {
-  border-color: var(--error-color);
+  border-color: var(--color-accent3);
   transition: border 0.3s ease-in;
 }
 
 .DangerZone .Button--danger {
   opacity: 0.4;
-  background: #fbfcfd;
-  border: 1px solid #ddd;
-  color: #444;
+  background: var(--color-bg-light);
+  border: 1px solid var(--color-border);
+  color: var(--color-text-dark);
 }
 
 .DangerZone:hover .Button--danger {
   opacity: 1;
-  background-color: var(--danger-button-bg-color);
-  border-color: var(--danger-button-bg-color);
-  color: #fff;
+  background-color: var(--color-accent3);
+  border-color: var(--color-accent3);
+  color: var(--color-text-white);
 }
 
 .Modal.WhatsAPulseModal {
diff --git a/frontend/src/metabase/css/query_builder.css b/frontend/src/metabase/css/query_builder.css
index 59e51e595da5604ced3f9a22d392c0208cc2c03c..0d3a97a1fa9dcfc11fa9bc2c81ea574d549ddcb4 100644
--- a/frontend/src/metabase/css/query_builder.css
+++ b/frontend/src/metabase/css/query_builder.css
@@ -1,5 +1,5 @@
 :root {
-  --selection-color: #ccdff6;
+  --selection-color: var(--color-text-light);
 }
 
 #react_qb_viz {
@@ -23,7 +23,7 @@
 .QueryHeader-section {
   padding-right: 1em;
   margin-right: 1em;
-  border-right: 1px solid rgba(0, 0, 0, 0.2);
+  border-right: 1px solid color(var(--color-accent2) alpha(-80%));
 }
 
 .QueryHeader-section:last-child {
@@ -33,13 +33,13 @@
 /*
 .Icon-download,
 .Icon-addToDash {
-    fill: #919191;
+    fill: var(--color-text-medium);
     transition: fill .3s linear;
 }
 
 .Icon-download:hover,
 .Icon-addToDash:hover {
-    fill: var(--brand-color);
+    fill: var(--color-brand);
     transition: fill .3s linear;
 }
 */
@@ -65,7 +65,7 @@
   text-transform: uppercase;
   font-size: 10px;
   font-weight: 700;
-  color: color(var(--base-grey) shade(30%));
+  color: var(--color-text-medium);
 }
 
 .Query-filters {
@@ -88,7 +88,7 @@
 }
 
 .Query-filter.selected {
-  border-color: var(--purple-color);
+  border-color: var(--color-accent2);
 }
 
 .Filter-section {
@@ -123,7 +123,7 @@
     @selectionmodule
 */
 .SelectionModule {
-  color: var(--brand-color);
+  color: var(--color-brand);
 }
 
 .SelectionList {
@@ -151,7 +151,7 @@
   align-items: center;
   cursor: pointer;
   padding: 0.75rem 1.5rem 0.75rem 0.75rem;
-  background-color: #fff;
+  background-color: var(--color-bg-white);
 }
 
 .SelectionItem:hover {
@@ -173,15 +173,15 @@
 }
 
 .SelectionItem:hover .Icon {
-  color: #fff !important;
+  color: var(--color-text-white) !important;
 }
 
 .SelectionItem:hover .SelectionModule-display {
-  color: #fff;
+  color: var(--color-text-white);
 }
 
 .SelectionItem:hover .SelectionModule-description {
-  color: #fff;
+  color: var(--color-text-white);
 }
 
 .SelectionItem.SelectionItem--selected .Icon-check {
@@ -194,7 +194,7 @@
 }
 
 .SelectionModule-description {
-  color: color(var(--base-grey) shade(40%));
+  color: var(--color-text-medium);
   font-size: 0.8rem;
 }
 
@@ -217,7 +217,7 @@
 }
 
 .Loading {
-  background-color: rgba(255, 255, 255, 0.82);
+  background-color: color(var(--color-bg-white) alpha(-18%));
 }
 
 /* query errors */
@@ -232,7 +232,7 @@
 .QueryError-iconWrapper {
   padding: 2em;
   margin-bottom: 2em;
-  border: 4px solid var(--error-color);
+  border: 4px solid var(--color-accent3);
   border-radius: 99px;
 }
 
@@ -279,7 +279,7 @@
   position: relative;
   display: inline-block;
   border-radius: var(--default-border-radius);
-  border: 1px solid rgb(197, 197, 197);
+  border: 1px solid var(--color-border);
   margin-top: var(--margin-2);
   padding: var(--padding-1) var(--padding-4) var(--padding-1) var(--padding-4);
 }
@@ -310,7 +310,7 @@
 }
 
 .QueryError2-detailBody {
-  background-color: #f8f8f8;
+  background-color: var(--color-bg-light);
   max-height: 15rem;
   overflow: auto;
 }
@@ -323,9 +323,9 @@
   flex-direction: column;
   font-size: 0.9em;
   z-index: 2;
-  background-color: #fff;
+  background-color: var(--color-bg-white);
 
-  border: 1px solid #e0e0e0;
+  border: 1px solid var(--color-border);
 }
 
 /* for medium breakpoint only expand if data reference is not shown */
@@ -337,22 +337,22 @@
 
 /* un-expanded (default) */
 .GuiBuilder-row {
-  border-bottom: 1px solid #e0e0e0;
+  border-bottom: 1px solid var(--color-border);
 }
 .GuiBuilder-row:last-child {
   border-bottom-color: transparent;
 }
 .GuiBuilder-data {
-  border-right: 1px solid #e0e0e0;
+  border-right: 1px solid var(--color-border);
 }
 .GuiBuilder-filtered-by {
   border-right: 1px solid transparent;
 }
 .GuiBuilder-view {
-  border-right: 1px solid #e0e0e0;
+  border-right: 1px solid var(--color-border);
 }
 .GuiBuilder-sort-limit {
-  border-left: 1px solid #e0e0e0;
+  border-left: 1px solid var(--color-border);
 }
 
 /* expanded */
@@ -361,10 +361,10 @@
 }
 .GuiBuilder.GuiBuilder--expand .GuiBuilder-row:last-child {
   border-right-color: transparent;
-  border-bottom-color: #e0e0e0;
+  border-bottom-color: var(--color-border);
 }
 .GuiBuilder.GuiBuilder--expand .GuiBuilder-filtered-by {
-  border-right-color: #e0e0e0;
+  border-right-color: var(--color-border);
 }
 
 .GuiBuilder-section {
@@ -407,19 +407,19 @@
 
 .Filter-section-field,
 .Filter-section-operator {
-  color: var(--purple-color);
+  color: var(--color-accent2);
 }
 
 .Filter-section-field .QueryOption {
-  color: var(--purple-color);
+  color: var(--color-accent2);
 }
 .Filter-section-operator .QueryOption {
-  color: var(--purple-color);
+  color: var(--color-accent2);
 }
 .Filter-section-value .QueryOption {
   color: white;
-  background-color: var(--purple-color);
-  border: 1px solid color(var(--purple-color) shade(30%));
+  background-color: var(--color-accent2);
+  border: 1px solid var(--color-accent2);
   border-radius: 6px;
   padding: 0.5em;
   padding-top: 0.3em;
@@ -439,10 +439,10 @@
 
 .FilterPopover .ColumnarSelector-row--selected,
 .FilterPopover .PopoverHeader-item.selected {
-  color: var(--purple-color) !important;
+  color: var(--color-accent2) !important;
 }
 .FilterPopover .ColumnarSelector-row:hover {
-  background-color: var(--purple-color) !important;
+  background-color: var(--color-accent2) !important;
 }
 
 /* VIEW SECTION */
@@ -450,13 +450,13 @@
 .View-section-aggregation,
 .View-section-aggregation-target,
 .View-section-breakout {
-  color: var(--green-color);
+  color: var(--color-accent1);
 }
 
 .View-section-aggregation.selected .QueryOption,
 .View-section-aggregation-target.selected .QueryOption,
 .View-section-breakout.selected .QueryOption {
-  color: var(--green-color);
+  color: var(--color-accent1);
 }
 
 /* SORT/LIMIT SECTION */
@@ -488,7 +488,7 @@
   height: 38px;
   border-radius: 38px;
   background-color: white;
-  border: 1px solid #ccdff6;
+  border: 1px solid var(--color-border);
 }
 
 .ChartType-popover {
@@ -497,7 +497,7 @@
 
 .ChartType--selected {
   color: white;
-  background-color: rgb(74, 144, 226);
+  background-color: var(--color-brand);
 }
 
 .ChartType--notSensible {
@@ -514,12 +514,17 @@
 .RunButton {
   z-index: 1;
   opacity: 1;
-  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.22);
+  box-shadow: 0 1px 2px var(--color-shadow);
   transition: transform 0.5s, opacity 0.5s;
-  min-width: 8em;
   position: relative;
 }
 
+@media screen and (--breakpoint-min-sm) {
+  .RunButton {
+    min-width: 8em;
+  }
+}
+
 .RunButton.RunButton--hidden {
   transform: translateY(-65px);
   opacity: 0;
@@ -534,7 +539,7 @@
   right: 0;
   width: 300px;
   height: 100%;
-  background-color: var(--slate-extra-light-color);
+  background-color: var(--color-bg-light);
   overflow: hidden;
 }
 
@@ -559,7 +564,7 @@
   width: 100%;
   margin: 0 auto;
   margin-bottom: 2rem;
-  border: 1px solid #dedede;
+  border: 1px solid var(--color-border);
 }
 
 @media screen and (--breakpoint-min-xl) {
@@ -570,11 +575,11 @@
 }
 
 .ObjectDetail-headingGroup {
-  border-bottom: 1px solid #dedede;
+  border-bottom: 1px solid var(--color-border);
 }
 
 .ObjectDetail-infoMain {
-  border-right: 1px solid #dedede;
+  border-right: 1px solid var(--color-border);
   margin-left: 2.4rem;
   font-size: 1rem;
 }
@@ -583,8 +588,8 @@
   max-height: 200px;
   overflow: scroll;
   padding: 1em;
-  background-color: #f8f8f8;
-  border: 1px solid #dedede;
+  background-color: var(--color-bg-light);
+  border: 1px solid var(--color-border);
   border-radius: 2px;
 }
 
@@ -599,23 +604,23 @@
 
 .List-item--segment .Icon,
 .List-item--segment .List-item-title {
-  color: var(--purple-color);
+  color: var(--color-accent2);
 }
 
 .List-item--customfield .Icon,
 .List-item--customfield .List-item-title {
-  color: var(--brand-color);
+  color: var(--color-brand);
 }
 
 .List-item:not(.List-item--disabled):hover .FieldList-grouping-trigger,
 .List-item--selected .FieldList-grouping-trigger {
   visibility: visible;
-  border-left: 2px solid rgba(0, 0, 0, 0.1);
-  color: rgba(255, 255, 255, 0.5);
+  border-left: 2px solid color(var(--color-accent2) alpha(-90%));
+  color: color(var(--color-text-white) alpha(-50%));
 }
 
 .QuestionTooltipTarget {
-  color: rgb(225, 225, 225);
+  color: var(--color-text-light);
   display: inline-block;
   border: 2px solid currentColor;
   border-radius: 99px;
@@ -644,8 +649,8 @@
   display: flex;
   align-items: center;
   justify-content: center;
-  background-color: var(--purple-color);
-  border: 1px solid var(--purple-color);
+  background-color: var(--color-accent2);
+  border: 1px solid var(--color-accent2);
   transition: opacity 0.3s ease-out;
 }
 
@@ -666,27 +671,27 @@
   white-space: pre-wrap;
   white-space: -moz-pre-wrap;
   white-space: -o-pre-wrap;
-  background-color: #f9fbfc;
-  border: 1px solid #d5dbe3;
+  background-color: var(--color-bg-light);
+  border: 1px solid var(--color-border);
   border-radius: 4px;
 }
 
 .ParameterValuePickerNoPopover input {
   font-size: 16px;
-  color: var(--default-font-color);
+  color: var(--color-text-dark);
   border: none;
 }
 
 .ParameterValuePickerNoPopover--selected input {
   font-weight: bold;
-  color: var(--brand-color);
+  color: var(--color-brand);
 }
 
 .ParameterValuePickerNoPopover input:focus {
   outline: none;
-  color: var(--default-font-color);
+  color: var(--color-text-dark);
 }
 
 .ParameterValuePickerNoPopover input::-webkit-input-placeholder {
-  color: var(--grey-4);
+  color: var(--color-text-medium);
 }
diff --git a/frontend/src/metabase/css/setup.css b/frontend/src/metabase/css/setup.css
index c18df9720cda0589695040a0771091070f1c197e..2971e08d801d205ee3f7b3e23cdb146c652be232 100644
--- a/frontend/src/metabase/css/setup.css
+++ b/frontend/src/metabase/css/setup.css
@@ -1,7 +1,7 @@
 :root {
   --indicator-size: 3em; /* ~ 42 px */
   --indicator-border-radius: 99px;
-  --setup-border-color: #d7d7d7;
+  --setup-border-color: var(--color-border);
 }
 
 .SetupSteps {
@@ -9,7 +9,7 @@
 }
 
 .SetupNav {
-  border-bottom: 1px solid #f5f5f5;
+  border-bottom: 1px solid var(--color-border);
 }
 
 .Setup-brandWordMark {
@@ -18,21 +18,21 @@
 
 .SetupStep {
   margin-bottom: 1.714rem;
-  border: 1px solid var(--setup-border-color);
+  border: 1px solid var(--color-border);
   flex: 1;
 }
 
 .SetupStep.SetupStep--active {
-  color: var(--brand-color);
+  color: var(--color-brand);
 }
 
 .SetupStep.SetupStep--completed {
-  color: var(--success-color);
+  color: var(--color-accent1);
 }
 
 .SetupStep.SetupStep--todo {
-  color: var(--brand-color);
-  background-color: #edf2f8;
+  color: var(--color-brand);
+  background-color: var(--color-bg-medium);
   border-style: dashed;
 }
 
@@ -41,15 +41,15 @@
   width: var(--indicator-size);
   height: var(--indicator-size);
   border-radius: var(--indicator-border-radius);
-  border-color: color(var(--base-grey) shade(20%));
+  border-color: var(--color-border);
   font-weight: bold;
   line-height: 1;
-  background-color: #fff;
+  background-color: var(--color-bg-white);
   margin-top: -3px;
 }
 
 .SetupStep-check {
-  color: #fff;
+  color: var(--color-text-white);
   display: none;
 }
 
@@ -58,12 +58,12 @@
 }
 
 .SetupStep.SetupStep--active .SetupStep-indicator {
-  color: var(--brand-color);
+  color: var(--color-brand);
 }
 
 .SetupStep.SetupStep--completed .SetupStep-indicator {
-  border-color: #9cc177;
-  background-color: #c8e1b0;
+  border-color: var(--color-accent1);
+  background-color: var(--color-accent1);
 }
 
 .SetupStep.SetupStep--completed .SetupStep-check {
@@ -84,5 +84,5 @@
 }
 
 .SetupHelp {
-  color: var(--body-text-color);
+  color: var(--color-text-medium);
 }
diff --git a/frontend/src/metabase/dashboard/components/AddSeriesModal.jsx b/frontend/src/metabase/dashboard/components/AddSeriesModal.jsx
index e7de3a340cf2f6b8fc05a11a8758c6f6316d87b2..316424ca4b7143a7b38cf69c95a3555918ef9097 100644
--- a/frontend/src/metabase/dashboard/components/AddSeriesModal.jsx
+++ b/frontend/src/metabase/dashboard/components/AddSeriesModal.jsx
@@ -1,14 +1,15 @@
 import React, { Component } from "react";
 import PropTypes from "prop-types";
+import { t } from "c-3po";
 
 import Visualization from "metabase/visualizations/components/Visualization.jsx";
 import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper.jsx";
 import Icon from "metabase/components/Icon.jsx";
 import Tooltip from "metabase/components/Tooltip.jsx";
 import CheckBox from "metabase/components/CheckBox.jsx";
-import { t } from "c-3po";
 import MetabaseAnalytics from "metabase/lib/analytics";
 import Query from "metabase/lib/query";
+import colors from "metabase/lib/colors";
 
 import { getVisualizationRaw } from "metabase/visualizations";
 
@@ -25,7 +26,7 @@ function getQueryColumns(card, databases) {
   let table =
     databases &&
     databases[dbId] &&
-    databases[dbId].tables_lookup[query.source_table];
+    databases[dbId].tables_lookup[query["source-table"]];
   if (!table) {
     return null;
   }
@@ -262,7 +263,7 @@ export default class AddSeriesModal extends Component {
             {this.state.state && (
               <div
                 className="spred flex layout-centered"
-                style={{ backgroundColor: "rgba(255,255,255,0.80)" }}
+                style={{ backgroundColor: colors["bg-white"] }}
               >
                 {this.state.state === "loading" ? (
                   <div className="h3 rounded bordered p3 bg-white shadowed">
@@ -293,13 +294,13 @@ export default class AddSeriesModal extends Component {
           className="border-left flex flex-column"
           style={{
             width: 370,
-            backgroundColor: "#F8FAFA",
-            borderColor: "#DBE1DF",
+            backgroundColor: colors["bg-light"],
+            borderColor: colors["border"],
           }}
         >
           <div
             className="flex-no-shrink border-bottom flex flex-row align-center"
-            style={{ borderColor: "#DBE1DF" }}
+            style={{ borderColor: colors["border"] }}
           >
             <Icon className="ml2" name="search" size={16} />
             <input
@@ -338,7 +339,7 @@ export default class AddSeriesModal extends Component {
                         tooltip={t`We're not sure if this question is compatible`}
                       >
                         <Icon
-                          className="px1 flex-align-right text-grey-2 text-grey-4-hover cursor-pointer flex-no-shrink"
+                          className="px1 flex-align-right text-light text-medium-hover cursor-pointer flex-no-shrink"
                           name="warning"
                           size={20}
                         />
diff --git a/frontend/src/metabase/dashboard/components/AddToDashSelectQuestionModal.jsx b/frontend/src/metabase/dashboard/components/AddToDashSelectQuestionModal.jsx
index 2d2e285d031e261ccf608ba3246f340423c4544c..818a4c8371e4eee9bbd40aa6d0a877b6ffce9151 100644
--- a/frontend/src/metabase/dashboard/components/AddToDashSelectQuestionModal.jsx
+++ b/frontend/src/metabase/dashboard/components/AddToDashSelectQuestionModal.jsx
@@ -33,22 +33,17 @@ export default class AddToDashSelectQuestionModal extends Component {
     }
   }
 
-  onAdd(card) {
+  onAdd = cardId => {
     this.props.addCardToDashboard({
       dashId: this.props.dashboard.id,
-      cardId: card.id,
+      cardId: cardId,
     });
     this.props.onEditingChange(true);
     this.props.onClose();
     MetabaseAnalytics.trackEvent("Dashboard", "Add Card");
-  }
+  };
 
   render() {
-    return (
-      <AddToDashboard
-        onAdd={card => this.onAdd(card)}
-        onClose={this.props.onClose}
-      />
-    );
+    return <AddToDashboard onAdd={this.onAdd} onClose={this.props.onClose} />;
   }
 }
diff --git a/frontend/src/metabase/dashboard/components/ArchiveDashboardModal.jsx b/frontend/src/metabase/dashboard/components/ArchiveDashboardModal.jsx
index 14eba601aa9cc936fb31efb1981fcd6132658655..a1fe47cdd162072a527c562fc3e1e513f5cb69f6 100644
--- a/frontend/src/metabase/dashboard/components/ArchiveDashboardModal.jsx
+++ b/frontend/src/metabase/dashboard/components/ArchiveDashboardModal.jsx
@@ -1,16 +1,14 @@
 import React, { Component } from "react";
 import PropTypes from "prop-types";
 import { t } from "c-3po";
+
+import Button from "metabase/components/Button";
 import ModalContent from "metabase/components/ModalContent.jsx";
 
 export default class ArchiveDashboardModal extends Component {
-  constructor(props, context) {
-    super(props, context);
-
-    this.state = {
-      error: null,
-    };
-  }
+  state = {
+    error: null,
+  };
 
   static propTypes = {
     dashboard: PropTypes.object.isRequired,
@@ -43,23 +41,15 @@ export default class ArchiveDashboardModal extends Component {
 
     return (
       <ModalContent title={t`Archive Dashboard`} onClose={this.props.onClose}>
-        <div className="Form-inputs mb4">
-          <p>{t`Are you sure you want to do this?`}</p>
-        </div>
+        <p>{t`Are you sure you want to do this?`}</p>
 
-        <div className="Form-actions">
-          <button
-            className="Button Button--danger"
-            onClick={() => this.archiveDashboard()}
-          >
+        <div>
+          <Button danger onClick={() => this.archiveDashboard()}>
             Yes
-          </button>
-          <button
-            className="Button Button--primary ml1"
-            onClick={this.props.onClose}
-          >
+          </Button>
+          <Button primary ml={1} onClick={this.props.onClose}>
             No
-          </button>
+          </Button>
           {formError}
         </div>
       </ModalContent>
diff --git a/frontend/src/metabase/dashboard/components/DashCard.jsx b/frontend/src/metabase/dashboard/components/DashCard.jsx
index 51f5e1e7d548c65959c72c46c5d94458ccca1456..68b669448a759300180e2b5fa7574716da61a16e 100644
--- a/frontend/src/metabase/dashboard/components/DashCard.jsx
+++ b/frontend/src/metabase/dashboard/components/DashCard.jsx
@@ -7,6 +7,7 @@ import Visualization, {
   ERROR_MESSAGE_GENERIC,
   ERROR_MESSAGE_PERMISSION,
 } from "metabase/visualizations/components/Visualization.jsx";
+import QueryDownloadWidget from "metabase/query_builder/components/QueryDownloadWidget";
 
 import ModalWithTrigger from "metabase/components/ModalWithTrigger.jsx";
 import ChartSettings from "metabase/visualizations/components/ChartSettings.jsx";
@@ -20,6 +21,8 @@ import { IS_EMBED_PREVIEW } from "metabase/lib/embed";
 import cx from "classnames";
 import _ from "underscore";
 import { getIn } from "icepick";
+import { getParametersBySlug } from "metabase/meta/Parameter";
+import Utils from "metabase/lib/utils";
 
 const DATASET_USUALLY_FAST_THRESHOLD = 15 * 1000;
 
@@ -65,6 +68,8 @@ export default class DashCard extends Component {
       onRemove,
       navigateToNewCardFromDashboard,
       metadata,
+      dashboard,
+      parameterValues,
     } = this.props;
 
     const mainCard = {
@@ -75,6 +80,8 @@ export default class DashCard extends Component {
       },
     };
     const cards = [mainCard].concat(dashcard.series || []);
+    const dashboardId = dashcard.dashboard_id;
+    const isEmbed = Utils.isJWT(dashboardId);
     const series = cards.map(card => ({
       ...getIn(dashcardData, [dashcard.id, card.id]),
       card: card,
@@ -108,6 +115,8 @@ export default class DashCard extends Component {
       errorIcon = "warning";
     }
 
+    const params = getParametersBySlug(dashboard.parameters, parameterValues);
+
     const hideBackground =
       !isEditing &&
       mainCard.visualization_settings["dashcard.background"] === false;
@@ -129,6 +138,7 @@ export default class DashCard extends Component {
       >
         <Visualization
           className="flex-full"
+          classNameWidgets={isEmbed && "text-light text-medium-hover"}
           error={errorMessage}
           errorIcon={errorIcon}
           isSlow={isSlow}
@@ -152,6 +162,16 @@ export default class DashCard extends Component {
                   this.props.onReplaceAllVisualizationSettings
                 }
               />
+            ) : isEmbed ? (
+              <QueryDownloadWidget
+                className="m1 text-brand-hover text-light"
+                classNameClose="hover-child"
+                card={dashcard.card}
+                params={params}
+                dashcardId={dashcard.id}
+                token={dashcard.dashboard_id}
+                icon="download"
+              />
             ) : (
               undefined
             )
@@ -214,7 +234,7 @@ const ChartSettingsButton = ({ series, onReplaceAllVisualizationSettings }) => (
     triggerElement={
       <Icon name="gear" size={HEADER_ICON_SIZE} style={HEADER_ACTION_STYLE} />
     }
-    triggerClasses="text-grey-2 text-grey-4-hover cursor-pointer flex align-center flex-no-shrink mr1"
+    triggerClasses="text-light text-medium-hover cursor-pointer flex align-center flex-no-shrink mr1"
   >
     <ChartSettings
       series={series}
@@ -226,7 +246,7 @@ const ChartSettingsButton = ({ series, onReplaceAllVisualizationSettings }) => (
 
 const RemoveButton = ({ onRemove }) => (
   <a
-    className="text-grey-2 text-grey-4-hover "
+    className="text-light text-medium-hover "
     data-metabase-event="Dashboard;Remove Card Modal"
     onClick={onRemove}
     style={HEADER_ACTION_STYLE}
@@ -238,7 +258,7 @@ const RemoveButton = ({ onRemove }) => (
 const AddSeriesButton = ({ series, onAddSeries }) => (
   <a
     data-metabase-event={"Dashboard;Edit Series Modal;open"}
-    className="text-grey-2 text-grey-4-hover cursor-pointer h3 flex-no-shrink relative mr1"
+    className="text-light text-medium-hover cursor-pointer h3 flex-no-shrink relative mr1"
     onClick={onAddSeries}
     style={HEADER_ACTION_STYLE}
   >
diff --git a/frontend/src/metabase/dashboard/components/DashCardParameterMapper.jsx b/frontend/src/metabase/dashboard/components/DashCardParameterMapper.jsx
index 465df150065b380bdb11f71a7821542b45cc4d0f..d0cfe60c589b0134b133f24c080d11396d6f622a 100644
--- a/frontend/src/metabase/dashboard/components/DashCardParameterMapper.jsx
+++ b/frontend/src/metabase/dashboard/components/DashCardParameterMapper.jsx
@@ -1,7 +1,10 @@
 import React from "react";
 import { t } from "c-3po";
+
 import DashCardCardParameterMapper from "../containers/DashCardCardParameterMapper.jsx";
 
+import colors from "metabase/lib/colors";
+
 const DashCardParameterMapper = ({ dashcard }) => (
   <div className="relative flex-full flex flex-column layout-centered">
     {dashcard.series &&
@@ -9,8 +12,8 @@ const DashCardParameterMapper = ({ dashcard }) => (
         <div
           className="mx4 my1 p1 rounded"
           style={{
-            backgroundColor: "#F5F5F5",
-            color: "#8691AC",
+            backgroundColor: colors["bg-light"],
+            color: colors["text-medium"],
             marginTop: -10,
           }}
         >
diff --git a/frontend/src/metabase/dashboard/components/Dashboard.jsx b/frontend/src/metabase/dashboard/components/Dashboard.jsx
index b23786c59a39726c682e5970a30de427608e117e..d8fa547e74c102aa6c64333458da85181d8d87db 100644
--- a/frontend/src/metabase/dashboard/components/Dashboard.jsx
+++ b/frontend/src/metabase/dashboard/components/Dashboard.jsx
@@ -88,6 +88,7 @@ type Props = {
     parameterId: ParameterId,
     defaultValue: string,
   ) => void,
+  setParameterIndex: (parameterId: ParameterId, index: number) => void,
 
   editingParameter: ?Parameter,
 
@@ -243,6 +244,7 @@ export default class Dashboard extends Component {
           editingParameter={editingParameter}
           setEditingParameter={this.props.setEditingParameter}
           setParameterName={this.props.setParameterName}
+          setParameterIndex={this.props.setParameterIndex}
           setParameterDefaultValue={this.props.setParameterDefaultValue}
           removeParameter={this.props.removeParameter}
           setParameterValue={this.props.setParameterValue}
@@ -283,7 +285,7 @@ export default class Dashboard extends Component {
                   <div className="text-normal mt3 mb1">
                     {t`This dashboard is looking empty.`}
                   </div>
-                  <div className="text-normal text-grey-2">
+                  <div className="text-normal text-light">
                     {t`Add a question to start making it useful!`}
                   </div>
                 </div>
diff --git a/frontend/src/metabase/dashboard/components/DashboardGrid.jsx b/frontend/src/metabase/dashboard/components/DashboardGrid.jsx
index 4690afc6a166f1ea643e6811cf0561ff381bc6e2..972a0ce8729f6befcc8a3a3afe545e2322b8c6ec 100644
--- a/frontend/src/metabase/dashboard/components/DashboardGrid.jsx
+++ b/frontend/src/metabase/dashboard/components/DashboardGrid.jsx
@@ -25,7 +25,7 @@ import cx from "classnames";
 const MOBILE_ASPECT_RATIO = 3 / 2;
 const MOBILE_TEXT_CARD_ROW_HEIGHT = 40;
 
-@ExplicitSize
+@ExplicitSize()
 export default class DashboardGrid extends Component {
   constructor(props, context) {
     super(props, context);
@@ -227,6 +227,7 @@ export default class DashboardGrid extends Component {
           this.props.navigateToNewCardFromDashboard
         }
         metadata={this.props.metadata}
+        dashboard={this.props.dashboard}
       />
     );
   }
diff --git a/frontend/src/metabase/dashboard/components/DashboardHeader.jsx b/frontend/src/metabase/dashboard/components/DashboardHeader.jsx
index 59a2d4712eeefadca5ca5e5b4b55fc7e2a5f5e35..08c9acf0a194157dac9e14a03ca9e7063045d010 100644
--- a/frontend/src/metabase/dashboard/components/DashboardHeader.jsx
+++ b/frontend/src/metabase/dashboard/components/DashboardHeader.jsx
@@ -3,20 +3,21 @@
 import React, { Component } from "react";
 import PropTypes from "prop-types";
 import { t } from "c-3po";
-import ActionButton from "metabase/components/ActionButton.jsx";
-import AddToDashSelectQuestionModal from "./AddToDashSelectQuestionModal.jsx";
-import ArchiveDashboardModal from "./ArchiveDashboardModal.jsx";
-import Header from "metabase/components/Header.jsx";
-import Icon from "metabase/components/Icon.jsx";
-import ModalWithTrigger from "metabase/components/ModalWithTrigger.jsx";
-import Tooltip from "metabase/components/Tooltip.jsx";
+import ActionButton from "metabase/components/ActionButton";
+import AddToDashSelectQuestionModal from "./AddToDashSelectQuestionModal";
+import ArchiveDashboardModal from "./ArchiveDashboardModal";
+import Header from "metabase/components/Header";
+import Icon from "metabase/components/Icon";
+import ModalWithTrigger from "metabase/components/ModalWithTrigger";
+import Tooltip from "metabase/components/Tooltip";
 import DashboardEmbedWidget from "../containers/DashboardEmbedWidget";
 
 import { getDashboardActions } from "./DashboardActions";
 
-import ParametersPopover from "./ParametersPopover.jsx";
-import Popover from "metabase/components/Popover.jsx";
+import ParametersPopover from "./ParametersPopover";
+import Popover from "metabase/components/Popover";
 
+import * as Urls from "metabase/lib/urls";
 import MetabaseSettings from "metabase/lib/settings";
 
 import cx from "classnames";
@@ -144,8 +145,10 @@ export default class DashboardHeader extends Component {
   }
 
   async onArchive() {
-    await this.props.archiveDashboard(this.props.dashboard.id);
-    this.props.onChangeLocation("/dashboards");
+    const { dashboard } = this.props;
+    // TODO - this should use entity action
+    await this.props.archiveDashboard(dashboard.id);
+    this.props.onChangeLocation(Urls.collection(dashboard.collection_id));
   }
 
   getEditingButtons() {
@@ -207,7 +210,6 @@ export default class DashboardHeader extends Component {
     if (!isFullscreen && canEdit) {
       buttons.push(
         <ModalWithTrigger
-          full
           key="add"
           ref="addQuestionModal"
           triggerElement={
@@ -342,6 +344,7 @@ export default class DashboardHeader extends Component {
       <Header
         headerClassName="wrapper"
         objectType="dashboard"
+        analyticsContext="Dashboard"
         item={dashboard}
         isEditing={this.props.isEditing}
         isEditingInfo={this.props.isEditing}
diff --git a/frontend/src/metabase/dashboard/components/RefreshWidget.css b/frontend/src/metabase/dashboard/components/RefreshWidget.css
index a312808825005daebf74ce5f867ecf3b976b9980..6ba30b9ff3a89dddb0807e983e366c40a3eb60f7 100644
--- a/frontend/src/metabase/dashboard/components/RefreshWidget.css
+++ b/frontend/src/metabase/dashboard/components/RefreshWidget.css
@@ -4,7 +4,7 @@
 }
 
 :local .title {
-  color: color(var(--base-grey) shade(40%));
+  color: var(--color-text-medium);
   font-weight: bold;
   font-size: 0.75em;
   text-transform: uppercase;
@@ -15,18 +15,18 @@
 :local .option {
   composes: cursor-pointer from "style";
 
-  color: var(--default-font-color);
+  color: var(--color-text-dark);
   font-weight: bold;
   padding-top: 0.5em;
   padding-bottom: 0.5em;
 }
 :local .option:hover,
 :local .option:hover .valueLabel {
-  color: var(--brand-color) !important;
+  color: var(--color-brand) !important;
 }
 :local .option.on.selected,
 :local .option.on.selected .valueLabel {
-  color: var(--green-color);
+  color: var(--color-accent1);
 }
 
 :local .option :global(.Icon) {
@@ -41,5 +41,5 @@
 }
 
 :local .option .valueLabel {
-  color: color(var(--base-grey) shade(40%));
+  color: var(--color-text-medium);
 }
diff --git a/frontend/src/metabase/dashboard/components/grid/GridLayout.jsx b/frontend/src/metabase/dashboard/components/grid/GridLayout.jsx
index f8689a5170e23d35beb99a2f986953c3832a17f6..92e6426cbad4a8976333f7b81698caf83db92909 100644
--- a/frontend/src/metabase/dashboard/components/grid/GridLayout.jsx
+++ b/frontend/src/metabase/dashboard/components/grid/GridLayout.jsx
@@ -4,6 +4,7 @@ import ReactDOM from "react-dom";
 import GridItem from "./GridItem.jsx";
 
 import _ from "underscore";
+import colors from "metabase/lib/colors";
 
 export default class GridLayout extends Component {
   constructor(props, context) {
@@ -236,7 +237,9 @@ export default class GridLayout extends Component {
       _(cols)
         .times(
           i =>
-            `<rect stroke='rgba(0, 0, 0, 0.117647)' stroke-width='1' fill='none' x='${Math.round(
+            `<rect stroke='${
+              colors["border"]
+            }' stroke-width='1' fill='none' x='${Math.round(
               margin / 2 + i * cellSize.width,
             ) + 1.5}' y='${margin / 2 + 1.5}' width='${Math.round(
               cellSize.width - margin - 3,
diff --git a/frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx b/frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx
index 5c5ffa89f93561f8993faf7a1dc1b5e166df241e..2dfb16d5644c5222a6eceeef5c4e1a5e405bb86d 100644
--- a/frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx
+++ b/frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx
@@ -1,34 +1,33 @@
 /* @flow weak */
 
 import React from "react";
-
+import { Box, Flex } from "grid-styled";
+import { t } from "c-3po";
 import { connect } from "react-redux";
-import { Link } from "react-router";
 
 import title from "metabase/hoc/Title";
 import withToast from "metabase/hoc/Toast";
+import DashboardData from "metabase/dashboard/hoc/DashboardData";
+
 import ActionButton from "metabase/components/ActionButton";
 import Button from "metabase/components/Button";
+import Card from "metabase/components/Card";
 import Icon from "metabase/components/Icon";
 import Filter from "metabase/query_builder/components/Filter";
-
-import cxs from "cxs";
-import { t } from "c-3po";
-import _ from "underscore";
+import Link from "metabase/components/Link";
+import Tooltip from "metabase/components/Tooltip";
 
 import { Dashboard } from "metabase/dashboard/containers/Dashboard";
-import DashboardData from "metabase/dashboard/hoc/DashboardData";
 import Parameters from "metabase/parameters/components/Parameters";
 
 import { getMetadata } from "metabase/selectors/metadata";
-import { getUserIsAdmin } from "metabase/selectors/user";
 
-import { DashboardApi } from "metabase/services";
+import Dashboards from "metabase/entities/dashboards";
 import * as Urls from "metabase/lib/urls";
 import MetabaseAnalytics from "metabase/lib/analytics";
-
 import * as Q from "metabase/lib/query/query";
 import Dimension from "metabase-lib/lib/Dimension";
+import colors from "metabase/lib/colors";
 
 import { dissoc } from "icepick";
 
@@ -36,12 +35,15 @@ const getDashboardId = (state, { params: { splat }, location: { hash } }) =>
   `/auto/dashboard/${splat}${hash.replace(/^#?/, "?")}`;
 
 const mapStateToProps = (state, props) => ({
-  isAdmin: getUserIsAdmin(state),
   metadata: getMetadata(state),
   dashboardId: getDashboardId(state, props),
 });
 
-@connect(mapStateToProps)
+const mapDispatchToProps = {
+  saveDashboard: Dashboards.actions.save,
+};
+
+@connect(mapStateToProps, mapDispatchToProps)
 @DashboardData
 @withToast
 @title(({ dashboard }) => dashboard && dashboard.name)
@@ -58,12 +60,13 @@ class AutomaticDashboardApp extends React.Component {
   }
 
   save = async () => {
-    const { dashboard, triggerToast } = this.props;
+    const { dashboard, triggerToast, saveDashboard } = this.props;
     // remove the transient id before trying to save
-    const newDashboard = await DashboardApi.save(dissoc(dashboard, "id"));
+    const { payload: newDashboard } = await saveDashboard(
+      dissoc(dashboard, "id"),
+    );
     triggerToast(
       <div className="flex align-center">
-        <Icon name="dashboard" size={22} className="mr2" color="#93A1AB" />
         {t`Your dashboard was saved`}
         <Link
           className="link text-bold ml1"
@@ -72,12 +75,20 @@ class AutomaticDashboardApp extends React.Component {
           {t`See it`}
         </Link>
       </div>,
+      { icon: "dashboard" },
     );
 
     this.setState({ savedDashboardId: newDashboard.id });
     MetabaseAnalytics.trackEvent("AutoDashboard", "Save");
   };
 
+  componentWillReceiveProps(nextProps) {
+    // clear savedDashboardId if changing to a different dashboard
+    if (this.props.location.pathname !== nextProps.location.pathname) {
+      this.setState({ savedDashboardId: null });
+    }
+  }
+
   render() {
     const {
       dashboard,
@@ -85,13 +96,13 @@ class AutomaticDashboardApp extends React.Component {
       parameterValues,
       setParameterValue,
       location,
-      isAdmin,
     } = this.props;
     const { savedDashboardId } = this.state;
     // pull out "more" related items for displaying as a button at the bottom of the dashboard
     const more = dashboard && dashboard.more;
     const related = dashboard && dashboard.related;
-    const hasSidebar = _.any(related || {}, list => list.length > 0);
+
+    const hasSidebar = related && Object.keys(related).length > 0;
 
     return (
       <div className="relative">
@@ -111,7 +122,7 @@ class AutomaticDashboardApp extends React.Component {
               </div>
               {savedDashboardId != null ? (
                 <Button className="ml-auto" disabled>{t`Saved`}</Button>
-              ) : isAdmin ? (
+              ) : (
                 <ActionButton
                   className="ml-auto"
                   success
@@ -120,7 +131,7 @@ class AutomaticDashboardApp extends React.Component {
                 >
                   {t`Save this`}
                 </ActionButton>
-              ) : null}
+              )}
             </div>
           </div>
 
@@ -174,7 +185,7 @@ const TransientTitle = ({ dashboard }) =>
   ) : null;
 
 const TransientFilters = ({ filter, metadata }) => (
-  <div className="mt1 flex align-center text-grey-4 text-bold">
+  <div className="mt1 flex align-center text-medium text-bold">
     {/* $FlowFixMe */}
     {Q.getFilters({ filter }).map((f, index) => (
       <TransientFilter key={index} filter={f} metadata={metadata} />
@@ -200,55 +211,84 @@ const getIconForFilter = (filter, metadata) => {
   }
 };
 
-const suggestionClasses = cxs({
-  ":hover h3": {
-    color: "#509ee3",
+const RELATED_CONTENT = {
+  compare: {
+    title: t`Compare`,
+    icon: "compare",
   },
-  ":hover .Icon": {
-    color: "#F9D45C",
+  "zoom-in": {
+    title: t`Zoom in`,
+    icon: "zoom-in",
   },
-});
+  "zoom-out": {
+    title: t`Zoom out`,
+    icon: "zoom-out",
+  },
+  related: {
+    title: t`Related`,
+    icon: "connections",
+  },
+};
 
 const SuggestionsList = ({ suggestions, section }) => (
-  <ol className="px2">
-    {suggestions.map((s, i) => (
-      <li key={i} className={suggestionClasses}>
-        <Link
-          to={s.url}
-          className="bordered rounded bg-white shadowed mb2 p2 flex no-decoration"
-          onClick={() =>
-            MetabaseAnalytics.trackEvent(
-              "AutoDashboard",
-              "ClickRelated",
-              section,
-            )
-          }
-        >
-          <div
-            className="bg-slate-extra-light rounded flex align-center justify-center text-slate mr1 flex-no-shrink"
-            style={{ width: 48, height: 48 }}
-          >
-            <Icon name="bolt" className="Icon text-grey-1" size={22} />
-          </div>
-          <div>
-            <h3 className="m0 mb1 ml1">{s.title}</h3>
-            <p className="text-grey-4 ml1 mt0 mb0">{s.description}</p>
-          </div>
-        </Link>
+  <Box is="ol" my={1}>
+    {Object.keys(suggestions).map((s, i) => (
+      <li key={i} className="my2">
+        <SuggetsionSectionHeading>
+          {RELATED_CONTENT[s].title}
+        </SuggetsionSectionHeading>
+        {suggestions[s].length > 0 &&
+          suggestions[s].map((item, itemIndex) => (
+            <Link
+              hover={{ color: colors["brand"] }}
+              key={itemIndex}
+              to={item.url}
+              className="block hover-parent hover--visibility"
+              data-metabase-event={`Auto Dashboard;Click Related;${s}`}
+              mb={1}
+            >
+              <Card p={2} hoverable>
+                <Flex align="center">
+                  <Icon
+                    name={RELATED_CONTENT[s].icon}
+                    color={colors["accent4"]}
+                    mr={1}
+                    size={22}
+                  />
+                  <h4>{item.title}</h4>
+                  <Box ml="auto" className="hover-child">
+                    <Tooltip tooltip={item.description}>
+                      <Icon name="question" color={colors["bg-dark"]} />
+                    </Tooltip>
+                  </Box>
+                </Flex>
+              </Card>
+            </Link>
+          ))}
       </li>
     ))}
-  </ol>
+  </Box>
 );
 
+const SuggetsionSectionHeading = ({ children }) => (
+  <h5
+    style={{
+      fontWeight: 900,
+      textTransform: "uppercase",
+      color: colors["text-medium"],
+    }}
+    className="mb1"
+  >
+    {children}
+  </h5>
+);
 const SuggestionsSidebar = ({ related }) => (
-  <div className="flex flex-column bg-slate-almost-extra-light full-height">
-    <div className="py2 text-centered my3">
-      <h3 className="text-grey-3">More X-rays</h3>
-    </div>
-    {Object.entries(related).map(([section, suggestions]) => (
-      <SuggestionsList section={section} suggestions={suggestions} />
-    ))}
-  </div>
+  <Flex flexDirection="column" py={2} px={3}>
+    <Box is="h2" py={1}>
+      {t`More X-rays`}
+    </Box>
+    <SuggestionsList suggestions={related} />
+  </Flex>
 );
 
 export default AutomaticDashboardApp;
diff --git a/frontend/src/metabase/dashboard/containers/DashCardCardParameterMapper.css b/frontend/src/metabase/dashboard/containers/DashCardCardParameterMapper.css
index bba6c6bc54dac14259edf53c1335bfade0f45110..59ba15b1068ec9fc4df205a22f7e46ce000da84f 100644
--- a/frontend/src/metabase/dashboard/containers/DashCardCardParameterMapper.css
+++ b/frontend/src/metabase/dashboard/containers/DashCardCardParameterMapper.css
@@ -1,22 +1,22 @@
 :local(.button) {
   composes: flex align-center bg-white text-bold cursor-pointer from "style";
   font-size: 16px;
-  border: 2px solid var(--brand-color);
+  border: 2px solid var(--color-brand);
   border-radius: 4px;
   min-height: 30px;
   min-width: 100px;
   padding: 0.25em 0.5em 0.25em 0.5em;
-  color: var(--default-font-color);
+  color: var(--color-text-dark);
 }
 
 :local(.mapped) {
-  border-color: var(--green-color);
-  color: var(--green-color);
+  border-color: var(--color-accent1);
+  color: var(--color-accent1);
 }
 
 :local(.warn) {
-  border-color: var(--error-color) !important;
-  color: var(--error-color) !important;
+  border-color: var(--color-accent3) !important;
+  color: var(--color-error) !important;
 }
 
 :local(.disabled) {
diff --git a/frontend/src/metabase/dashboard/dashboard.js b/frontend/src/metabase/dashboard/dashboard.js
index 2a291ac02e7f69625e2eaaafe72f45b4d9f64bc0..25052a8c95de8ff9d1eb281e3655d6b2898faa17 100644
--- a/frontend/src/metabase/dashboard/dashboard.js
+++ b/frontend/src/metabase/dashboard/dashboard.js
@@ -51,6 +51,7 @@ import {
 } from "metabase/services";
 
 import { getDashboard, getDashboardComplete } from "./selectors";
+import { getMetadata } from "metabase/selectors/metadata";
 import { getCardAfterVisualizationClick } from "metabase/visualizations/lib/utils";
 
 const DATASET_SLOW_TIMEOUT = 15 * 1000;
@@ -106,6 +107,7 @@ export const REMOVE_PARAMETER = "metabase/dashboard/REMOVE_PARAMETER";
 export const SET_PARAMETER_MAPPING = "metabase/dashboard/SET_PARAMETER_MAPPING";
 export const SET_PARAMETER_NAME = "metabase/dashboard/SET_PARAMETER_NAME";
 export const SET_PARAMETER_VALUE = "metabase/dashboard/SET_PARAMETER_VALUE";
+export const SET_PARAMETER_INDEX = "metabase/dashboard/SET_PARAMETER_INDEX";
 export const SET_PARAMETER_DEFAULT_VALUE =
   "metabase/dashboard/SET_PARAMETER_DEFAULT_VALUE";
 
@@ -728,6 +730,28 @@ export const setParameterDefaultValue = createThunkAction(
   },
 );
 
+export const setParameterIndex = createThunkAction(
+  SET_PARAMETER_INDEX,
+  (parameterId, index) => (dispatch, getState) => {
+    const dashboard = getDashboard(getState());
+    const parameterIndex = _.findIndex(
+      dashboard.parameters,
+      p => p.id === parameterId,
+    );
+    if (parameterIndex >= 0) {
+      const parameters = dashboard.parameters.slice();
+      parameters.splice(index, 0, parameters.splice(parameterIndex, 1)[0]);
+      dispatch(
+        setDashboardAttributes({
+          id: dashboard.id,
+          attributes: { parameters },
+        }),
+      );
+    }
+    return { id: parameterId, index };
+  },
+);
+
 export const setParameterValue = createThunkAction(
   SET_PARAMETER_VALUE,
   (parameterId, value) => (dispatch, getState) => {
@@ -772,7 +796,7 @@ const NAVIGATE_TO_NEW_CARD = "metabase/dashboard/NAVIGATE_TO_NEW_CARD";
 export const navigateToNewCardFromDashboard = createThunkAction(
   NAVIGATE_TO_NEW_CARD,
   ({ nextCard, previousCard, dashcard }) => (dispatch, getState) => {
-    const { metadata } = getState();
+    const metadata = getMetadata(getState());
     const { dashboardId, dashboards, parameterValues } = getState().dashboard;
     const dashboard = dashboards[dashboardId];
     const cardIsDirty = !_.isEqual(
diff --git a/frontend/src/metabase/entities/collections.js b/frontend/src/metabase/entities/collections.js
index 9362ef87b7e28bd8ab0f1a5b6cc43ce04b68ae11..9d1c6124c5bd9fadd420061a09aeb237a8b68eea 100644
--- a/frontend/src/metabase/entities/collections.js
+++ b/frontend/src/metabase/entities/collections.js
@@ -1,12 +1,15 @@
-/* @flow weak */
+/* @flow */
 
 import { createEntity, undo } from "metabase/lib/entities";
-import { normal, getRandomColor } from "metabase/lib/colors";
+import colors from "metabase/lib/colors";
 import { CollectionSchema } from "metabase/schema";
 import { createSelector } from "reselect";
 
-import React from "react";
-import CollectionSelect from "metabase/containers/CollectionSelect";
+import {
+  getUser,
+  getUserDefaultCollectionId,
+  getUserPersonalCollectionId,
+} from "metabase/selectors/user";
 
 import { t } from "c-3po";
 
@@ -33,8 +36,34 @@ const Collections = createEntity({
 
   selectors: {
     getExpandedCollectionsById: createSelector(
-      [state => state.entities.collections],
-      collections => getExpandedCollectionsById(Object.values(collections)),
+      [
+        state => state.entities.collections,
+        state => state.entities.collections_list[null] || [],
+        getUser,
+      ],
+      (collections, collectionsIds, user) =>
+        getExpandedCollectionsById(
+          collectionsIds.map(id => collections[id]),
+          user && user.personal_collection_id,
+        ),
+    ),
+    getInitialCollectionId: createSelector(
+      [
+        // these are listed in order of priority
+        (state, { collectionId }) => collectionId,
+        (state, { params }) => (params ? params.collectionId : undefined),
+        (state, { location }) =>
+          location && location.query ? location.query.collectionId : undefined,
+        getUserDefaultCollectionId,
+      ],
+      (...collectionIds) => {
+        for (const collectionId of collectionIds) {
+          if (collectionId !== undefined) {
+            return canonicalCollectionId(collectionId);
+          }
+        }
+        return null;
+      },
     ),
   },
 
@@ -47,9 +76,14 @@ const Collections = createEntity({
   },
 
   form: {
-    fields: (values = {}) => [
+    fields: (
+      values = {
+        color: colors.brand,
+      },
+    ) => [
       {
         name: "name",
+        title: t`Name`,
         placeholder: "My new fantastic collection",
         validate: name =>
           (!name && t`Name is required`) ||
@@ -57,49 +91,105 @@ const Collections = createEntity({
       },
       {
         name: "description",
+        title: t`Description`,
         type: "text",
         placeholder: "It's optional but oh, so helpful",
         normalize: description => description || null, // expected to be nil or non-empty string
       },
       {
         name: "color",
-        type: "color",
-        initial: () => getRandomColor(normal),
+        title: t`Color`,
+        type: "hidden",
+        initial: () => colors.brand,
         validate: color => !color && t`Color is required`,
       },
       {
         name: "parent_id",
-        title: "Parent collection",
-        // eslint-disable-next-line react/display-name
-        type: ({ field }) => (
-          <CollectionSelect {...field} collectionId={values.id} />
-        ),
+        title: t`Collection it's saved in`,
+        type: "collection",
       },
     ],
   },
+
+  getAnalyticsMetadata(action, object, getState) {
+    const type = object && getCollectionType(object.parent_id, getState());
+    return type && `collection=${type}`;
+  },
 });
 
 export default Collections;
 
 // API requires items in "root" collection be persisted with a "null" collection ID
 // Also ensure it's parsed as a number
-export const canonicalCollectionId = collectionId =>
+export const canonicalCollectionId = (
+  collectionId: PseudoCollectionId,
+): CollectionId | null =>
   collectionId == null || collectionId === "root"
     ? null
     : parseInt(collectionId, 10);
 
+export const getCollectionType = (collectionId: string, state: {}) =>
+  collectionId === null || collectionId === "root"
+    ? "root"
+    : collectionId === getUserPersonalCollectionId(state)
+      ? "personal"
+      : collectionId !== undefined ? "other" : null;
+
 export const ROOT_COLLECTION = {
   id: "root",
-  name: "Saved items",
+  name: t`Our analytics`,
   location: "",
   path: [],
 };
 
+// the user's personal collection
+export const PERSONAL_COLLECTION = {
+  id: undefined, // to be filled in by getExpandedCollectionsById
+  name: t`My personal collection`,
+  location: "/",
+  path: ["root"],
+  can_write: true,
+};
+
+// fake collection for admins that contains all other user's collections
+export const PERSONAL_COLLECTIONS = {
+  id: "personal", // placeholder id
+  name: t`All personal collections`,
+  location: "/",
+  path: ["root"],
+  can_write: false,
+};
+
+type UserId = number;
+
+// a "real" collection
+type CollectionId = number;
+
+type Collection = {
+  id: CollectionId,
+  location?: string,
+  personal_owner_id?: UserId,
+};
+
+// includes "root" and "personal" pseudo collection IDs
+type PseudoCollectionId = CollectionId | "root" | "personal";
+
+type ExpandedCollection = {
+  id: PseudoCollectionId,
+  path: ?(string[]),
+  parent: ?ExpandedCollection,
+  children: ExpandedCollection[],
+  is_personal?: boolean,
+};
+
 // given list of collections with { id, name, location } returns a map of ids to
 // expanded collection objects like { id, name, location, path, children }
 // including a root collection
-export function getExpandedCollectionsById(collections) {
-  const collectionsById = {};
+function getExpandedCollectionsById(
+  collections: Collection[],
+  userPersonalCollectionId: ?CollectionId,
+): { [key: PseudoCollectionId]: ExpandedCollection } {
+  const collectionsById: { [key: PseudoCollectionId]: ExpandedCollection } = {};
   for (const c of collections) {
     collectionsById[c.id] = {
       ...c,
@@ -109,31 +199,77 @@ export function getExpandedCollectionsById(collections) {
           : c.location != null
             ? ["root", ...c.location.split("/").filter(l => l)]
             : null,
+      parent: null,
       children: [],
+      is_personal: c.personal_owner_id != null,
     };
   }
 
-  // make sure we have the root collection with all relevant info
+  // "Our Analytics"
   collectionsById[ROOT_COLLECTION.id] = {
-    children: [],
     ...ROOT_COLLECTION,
+    parent: null,
+    children: [],
     ...(collectionsById[ROOT_COLLECTION.id] || {}),
   };
 
+  // "My personal collection"
+  if (userPersonalCollectionId != null) {
+    collectionsById[ROOT_COLLECTION.id].children.push({
+      ...PERSONAL_COLLECTION,
+      id: userPersonalCollectionId,
+      parent: collectionsById[ROOT_COLLECTION.id],
+      children: [],
+      is_personal: true,
+    });
+  }
+
+  // "Personal Collections"
+  collectionsById[PERSONAL_COLLECTIONS.id] = {
+    ...PERSONAL_COLLECTIONS,
+    parent: collectionsById[ROOT_COLLECTION.id],
+    children: [],
+    is_personal: true,
+  };
+  collectionsById[ROOT_COLLECTION.id].children.push(
+    collectionsById[PERSONAL_COLLECTIONS.id],
+  );
+
   // iterate over original collections so we don't include ROOT_COLLECTION as
   // a child of itself
   for (const { id } of collections) {
     const c = collectionsById[id];
-    if (c.path) {
-      const parent = c.path[c.path.length - 1] || "root";
-      c.parent = collectionsById[parent];
+    // don't add root as parent of itself
+    if (c.path && c.id !== ROOT_COLLECTION.id) {
+      let parentId;
+      // move personal collections into PERSONAL_COLLECTIONS fake collection
+      if (c.personal_owner_id != null) {
+        parentId = PERSONAL_COLLECTIONS.id;
+      } else if (c.path[c.path.length - 1]) {
+        parentId = c.path[c.path.length - 1];
+      } else {
+        parentId = ROOT_COLLECTION.id;
+      }
+
+      // $FlowFixMe
+      const parent = parentId == null ? null : collectionsById[parentId];
+      c.parent = parent;
       // need to ensure the parent collection exists, it may have been filtered
       // because we're selecting a collection's parent collection and it can't
       // contain itself
-      if (collectionsById[parent]) {
-        collectionsById[parent].children.push(c);
+      if (parent) {
+        parent.children.push(c);
       }
     }
   }
+
+  // remove PERSONAL_COLLECTIONS collection if there are none or just one (the user's own)
+  if (collectionsById[PERSONAL_COLLECTIONS.id].children.length <= 1) {
+    delete collectionsById[PERSONAL_COLLECTIONS.id];
+    collectionsById[ROOT_COLLECTION.id].children = collectionsById[
+      ROOT_COLLECTION.id
+    ].children.filter(c => c.id !== PERSONAL_COLLECTIONS.id);
+  }
+
   return collectionsById;
 }
diff --git a/frontend/src/metabase/entities/containers/EntityListLoader.jsx b/frontend/src/metabase/entities/containers/EntityListLoader.jsx
index ab13658f82b9572238a567ce2a53b30525426ae9..31987dd1e3f31365ad4d1cd3fd74f1d069062f4b 100644
--- a/frontend/src/metabase/entities/containers/EntityListLoader.jsx
+++ b/frontend/src/metabase/entities/containers/EntityListLoader.jsx
@@ -4,6 +4,7 @@ import React from "react";
 import { connect } from "react-redux";
 import _ from "underscore";
 import { createSelector } from "reselect";
+import { createMemoizedSelector } from "metabase/lib/redux";
 
 import entityType from "./EntityType";
 import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
@@ -25,11 +26,24 @@ export type RenderProps = {
   reload: () => void,
 };
 
+const getEntityQuery = (state, props) =>
+  typeof props.entityQuery === "function"
+    ? props.entityQuery(state, props)
+    : props.entityQuery;
+
+// NOTE: Memoize entityQuery so we don't re-render even if a new but identical
+// object is created. This works because entityQuery must be JSON serializable
+// NOTE: Technically leaks a small amount of memory because it uses an unbounded
+// memoization cache, but that's probably ok.
+const getMemoizedEntityQuery = createMemoizedSelector(
+  [getEntityQuery],
+  entityQuery => entityQuery,
+);
+
 @entityType()
-@connect((state, { entityDef, entityQuery, ...props }) => {
-  if (typeof entityQuery === "function") {
-    entityQuery = entityQuery(state, props);
-  }
+@connect((state, props) => {
+  const { entityDef } = props;
+  const entityQuery = getMemoizedEntityQuery(state, props);
   return {
     entityQuery,
     list: entityDef.selectors.getList(state, { entityQuery }),
diff --git a/frontend/src/metabase/entities/dashboards.js b/frontend/src/metabase/entities/dashboards.js
index 8183e18011c5b0688165036e7e72907ade680ca2..232872c4ebc64a175008f119dbdfd84dec3ea53c 100644
--- a/frontend/src/metabase/entities/dashboards.js
+++ b/frontend/src/metabase/entities/dashboards.js
@@ -4,9 +4,13 @@ import { createEntity, undo } from "metabase/lib/entities";
 import * as Urls from "metabase/lib/urls";
 import { normal } from "metabase/lib/colors";
 import { assocIn } from "icepick";
+import { t } from "c-3po";
 
 import { POST, DELETE } from "metabase/lib/api";
-import { canonicalCollectionId } from "metabase/entities/collections";
+import {
+  canonicalCollectionId,
+  getCollectionType,
+} from "metabase/entities/collections";
 
 const FAVORITE_ACTION = `metabase/entities/dashboards/FAVORITE`;
 const UNFAVORITE_ACTION = `metabase/entities/dashboards/UNFAVORITE`;
@@ -18,6 +22,7 @@ const Dashboards = createEntity({
   api: {
     favorite: POST("/api/dashboard/:id/favorite"),
     unfavorite: DELETE("/api/dashboard/:id/favorite"),
+    save: POST("/api/dashboard/save"),
   },
 
   objectActions: {
@@ -56,6 +61,17 @@ const Dashboards = createEntity({
     },
   },
 
+  actions: {
+    save: dashboard => async dispatch => {
+      const savedDashboard = await Dashboards.api.save(dashboard);
+      dispatch({ type: Dashboards.actionTypes.INVALIDATE_LISTS_ACTION });
+      return {
+        type: "metabase/entities/dashboards/SAVE_DASHBOARD",
+        payload: savedDashboard,
+      };
+    },
+  },
+
   reducer: (state = {}, { type, payload, error }) => {
     if (type === FAVORITE_ACTION && !error) {
       return assocIn(state, [payload, "favorite"], true);
@@ -74,7 +90,32 @@ const Dashboards = createEntity({
   },
 
   form: {
-    fields: [{ name: "name" }, { name: "description", type: "text" }],
+    fields: [
+      {
+        name: "name",
+        title: t`Name`,
+        placeholder: t`What is the name of your dashboard?`,
+        validate: name => (!name ? "Name is required" : null),
+      },
+      {
+        name: "description",
+        title: t`Description`,
+        type: "text",
+        placeholder: t`It's optional but oh, so helpful`,
+      },
+      {
+        name: "collection_id",
+        title: t`Which collection should this go in?`,
+        type: "collection",
+        validate: colelctionId =>
+          colelctionId === undefined ? "Collection is required" : null,
+      },
+    ],
+  },
+
+  getAnalyticsMetadata(action, object, getState) {
+    const type = object && getCollectionType(object.collection_id, getState());
+    return type && `collection=${type}`;
   },
 });
 
diff --git a/frontend/src/metabase/entities/databases.js b/frontend/src/metabase/entities/databases.js
index 95373e6f79614cb22ff3d7097ad15c2cc67eef7f..e99e2bf0136127172b8ed1c4c235290a76b6a876 100644
--- a/frontend/src/metabase/entities/databases.js
+++ b/frontend/src/metabase/entities/databases.js
@@ -3,6 +3,7 @@
 import { createEntity } from "metabase/lib/entities";
 import { fetchData, createThunkAction } from "metabase/lib/redux";
 import { normalize } from "normalizr";
+import _ from "underscore";
 
 import { MetabaseApi } from "metabase/services";
 import { DatabaseSchema } from "metabase/schema";
@@ -11,7 +12,7 @@ import { DatabaseSchema } from "metabase/schema";
 export const FETCH_DATABASE_METADATA =
   "metabase/entities/database/FETCH_DATABASE_METADATA";
 
-export default createEntity({
+const Databases = createEntity({
   name: "databases",
   path: "/api/database",
   schema: DatabaseSchema,
@@ -37,6 +38,11 @@ export default createEntity({
     ),
   },
 
+  selectors: {
+    getHasSampleDataset: state =>
+      _.any(Databases.selectors.getList(state), db => db.is_sample),
+  },
+
   // FORM
   form: {
     fields: (values = {}) => [
@@ -47,6 +53,8 @@ export default createEntity({
   },
 });
 
+export default Databases;
+
 // TODO: use the info returned by the backend
 const FIELDS_BY_ENGINE = {
   h2: [{ name: "details.db" }],
diff --git a/frontend/src/metabase/entities/metrics.js b/frontend/src/metabase/entities/metrics.js
index e09e78879fa621c7d93e84d74460689ef81b74c1..73fd7795eb25b25e2a5942ca2571fe55798ef203 100644
--- a/frontend/src/metabase/entities/metrics.js
+++ b/frontend/src/metabase/entities/metrics.js
@@ -1,6 +1,7 @@
 import { createEntity } from "metabase/lib/entities";
 
 import { MetricSchema } from "metabase/schema";
+import colors from "metabase/lib/colors";
 
 export default createEntity({
   name: "metrics",
@@ -10,7 +11,7 @@ export default createEntity({
   objectSelectors: {
     getName: segment => segment && segment.name,
     getUrl: segment => null,
-    getColor: () => "#93B3C9",
+    getColor: () => colors["text-medium"],
     getIcon: question => "metric",
   },
 });
diff --git a/frontend/src/metabase/entities/pulses.js b/frontend/src/metabase/entities/pulses.js
index 46a29b2be99c18935afd0e72a5caa25552922754..c9ad22a6fb4cc46e835e5bf305aa6853be16c196 100644
--- a/frontend/src/metabase/entities/pulses.js
+++ b/frontend/src/metabase/entities/pulses.js
@@ -1,20 +1,23 @@
-import React from "react";
-
 import { createEntity, undo } from "metabase/lib/entities";
 import * as Urls from "metabase/lib/urls";
 import { normal } from "metabase/lib/colors";
 
-import { canonicalCollectionId } from "metabase/entities/collections";
-
-import CollectionSelect from "metabase/containers/CollectionSelect";
+import {
+  canonicalCollectionId,
+  getCollectionType,
+} from "metabase/entities/collections";
 
 const Pulses = createEntity({
   name: "pulses",
   path: "/api/pulse",
 
   objectActions: {
-    // FIXME: not implemented in backend
-    // setArchived: ({ id }, archived) => Pulses.actions.update({ id, archived }),
+    setArchived: ({ id }, archived, opts) =>
+      Pulses.actions.update(
+        { id },
+        { archived },
+        undo(opts, "pulse", archived ? "archived" : "unarchived"),
+      ),
 
     setCollection: ({ id }, collection, opts) =>
       Pulses.actions.update(
@@ -22,6 +25,16 @@ const Pulses = createEntity({
         { collection_id: canonicalCollectionId(collection && collection.id) },
         undo(opts, "pulse", "moved"),
       ),
+
+    setPinned: ({ id }, pinned, opts) =>
+      Pulses.actions.update(
+        { id },
+        {
+          collection_position:
+            typeof pinned === "number" ? pinned : pinned ? 1 : null,
+        },
+        opts,
+      ),
   },
 
   objectSelectors: {
@@ -37,11 +50,15 @@ const Pulses = createEntity({
       {
         name: "collection_id",
         title: "Collection",
-        // eslint-disable-next-line react/display-name
-        type: ({ field }) => <CollectionSelect {...field} />,
+        type: "collection",
       },
     ],
   },
+
+  getAnalyticsMetadata(action, object, getState) {
+    const type = object && getCollectionType(object.collection_id, getState());
+    return type && `collection=${type}`;
+  },
 });
 
 export default Pulses;
diff --git a/frontend/src/metabase/entities/questions.js b/frontend/src/metabase/entities/questions.js
index 93596181d90fb6baf25d1f52320b06c4ed77077a..c1d35b53a001d8ac4fceede2d3d1a99325461b1e 100644
--- a/frontend/src/metabase/entities/questions.js
+++ b/frontend/src/metabase/entities/questions.js
@@ -1,13 +1,15 @@
 /* @flow */
 
-import React from "react";
+import { assocIn } from "icepick";
 
 import { createEntity, undo } from "metabase/lib/entities";
 import * as Urls from "metabase/lib/urls";
-import { assocIn } from "icepick";
+import colors from "metabase/lib/colors";
 
-import CollectionSelect from "metabase/containers/CollectionSelect";
-import { canonicalCollectionId } from "metabase/entities/collections";
+import {
+  canonicalCollectionId,
+  getCollectionType,
+} from "metabase/entities/collections";
 
 import { POST, DELETE } from "metabase/lib/api";
 
@@ -62,8 +64,10 @@ const Questions = createEntity({
   objectSelectors: {
     getName: question => question && question.name,
     getUrl: question => question && Urls.question(question.id),
-    getColor: () => "#93B3C9",
-    getIcon: question => "beaker",
+    getColor: () => colors["text-medium"],
+    getIcon: question =>
+      (require("metabase/visualizations").default.get(question.display) || {})
+        .iconName || "beaker",
   },
 
   reducer: (state = {}, { type, payload, error }) => {
@@ -82,11 +86,31 @@ const Questions = createEntity({
       {
         name: "collection_id",
         title: "Collection",
-        // eslint-disable-next-line react/display-name
-        type: ({ field }) => <CollectionSelect {...field} />,
+        type: "collection",
       },
     ],
   },
+
+  // NOTE: keep in sync with src/metabase/api/card.clj
+  writableProperties: [
+    "name",
+    "dataset_query",
+    "display",
+    "description",
+    "visualization_settings",
+    "archived",
+    "enable_embedding",
+    "embedding_params",
+    "collection_id",
+    "collection_position",
+    "result_metadata",
+    "metadata_checksum",
+  ],
+
+  getAnalyticsMetadata(action, object, getState) {
+    const type = object && getCollectionType(object.collection_id, getState());
+    return type && `collection=${type}`;
+  },
 });
 
 export default Questions;
diff --git a/frontend/src/metabase/entities/segments.js b/frontend/src/metabase/entities/segments.js
index 058e4d520e7d37b95055a793a6a504edc1d070d5..5ad8e34c405e606fa8ecd3f23e8551cf3d4a49ec 100644
--- a/frontend/src/metabase/entities/segments.js
+++ b/frontend/src/metabase/entities/segments.js
@@ -3,6 +3,7 @@
 import { createEntity } from "metabase/lib/entities";
 
 import { SegmentSchema } from "metabase/schema";
+import colors from "metabase/lib/colors";
 
 export default createEntity({
   name: "segments",
@@ -12,7 +13,7 @@ export default createEntity({
   objectSelectors: {
     getName: segment => segment && segment.name,
     getUrl: segment => null,
-    getColor: () => "#93B3C9",
+    getColor: () => colors["text-medium"],
     getIcon: question => "segment",
   },
 });
diff --git a/frontend/src/metabase/hoc/AutoExpanding.jsx b/frontend/src/metabase/hoc/AutoExpanding.jsx
index 7d809610d4bfd4fb6629dd3c01f891c9e6ea3eb0..7665564fc0411f3fc5b67d80cdf90535d6bbae58 100644
--- a/frontend/src/metabase/hoc/AutoExpanding.jsx
+++ b/frontend/src/metabase/hoc/AutoExpanding.jsx
@@ -8,7 +8,7 @@ import ExplicitSize from "metabase/components/ExplicitSize";
 // beyond their initial size we want to fix their size to be larger so it doesn't
 // jump around, etc
 export default ComposedComponent =>
-  @ExplicitSize
+  @ExplicitSize()
   class AutoExpanding extends React.Component {
     state = {
       expand: false,
diff --git a/frontend/src/metabase/hoc/ListSearch.jsx b/frontend/src/metabase/hoc/ListSearch.jsx
deleted file mode 100644
index 10778576416aaa8aba31a119a0f75feeb9280867..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/hoc/ListSearch.jsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import React from "react";
-
-import { caseInsensitiveSearch } from "metabase/lib/string";
-import _ from "underscore";
-
-// Higher order component for filtering a list
-//
-// Injects searchText and onSetSearchText props, and filters down a list prop
-// ("list" by default)
-//
-// Composes with EntityListLoader, ListSelect, etc
-const listSearch = ({
-  listProp = "list",
-  properties = ["name"],
-} = {}) => ComposedComponent =>
-  class ListSearch extends React.Component {
-    state = {
-      searchText: "",
-    };
-    render() {
-      const { ...props } = this.props;
-      const { searchText } = this.state;
-      props[listProp] =
-        props[listProp] &&
-        props[listProp].filter(item =>
-          _.any(properties, p => caseInsensitiveSearch(item[p], searchText)),
-        );
-
-      return (
-        <ComposedComponent
-          {...props}
-          searchText={searchText}
-          onSetSearchText={searchText => this.setState({ searchText })}
-        />
-      );
-    }
-  };
-
-export default listSearch;
diff --git a/frontend/src/metabase/hoc/ScrollToTop.js b/frontend/src/metabase/hoc/ScrollToTop.js
new file mode 100644
index 0000000000000000000000000000000000000000..dacffd445ff436f416a28d9d5133b0b08195781e
--- /dev/null
+++ b/frontend/src/metabase/hoc/ScrollToTop.js
@@ -0,0 +1,18 @@
+import React from "react";
+import { withRouter } from "react-router";
+
+@withRouter
+class ScrollToTop extends React.Component {
+  componentDidUpdate(prevProps) {
+    // Compare location.pathame to see if we're on a different URL. Do this to ensure
+    // that query strings don't cause a scroll to the top
+    if (this.props.location.pathname !== prevProps.location.pathname) {
+      window.scrollTo(0, 0);
+    }
+  }
+  render() {
+    return this.props.children;
+  }
+}
+
+export default ScrollToTop;
diff --git a/frontend/src/metabase/home/components/Activity.jsx b/frontend/src/metabase/home/components/Activity.jsx
index c4fc62ccd71c2711bcd6ffc09fcb5b5328b4f0d9..b86041aee782969a627f7cb60587b772bcc6aee8 100644
--- a/frontend/src/metabase/home/components/Activity.jsx
+++ b/frontend/src/metabase/home/components/Activity.jsx
@@ -21,7 +21,7 @@ export default class Activity extends Component {
       "bg-error",
       "bg-green",
       "bg-gold",
-      "bg-grey-2",
+      "bg-medium",
     ];
   }
 
@@ -528,7 +528,7 @@ export default class Activity extends Component {
                   <div className="text-normal mt3 mb1">
                     {t`Hmmm, looks like nothing has happened yet.`}
                   </div>
-                  <div className="text-normal text-grey-2">
+                  <div className="text-normal text-light">
                     {t`Save a question and get this baby going!`}
                   </div>
                 </div>
diff --git a/frontend/src/metabase/home/components/ActivityItem.jsx b/frontend/src/metabase/home/components/ActivityItem.jsx
index 53af487187923da4eb53e214df5a724ea878463a..c67f278a7fcf99de7130cf6d022e4bb165beb630 100644
--- a/frontend/src/metabase/home/components/ActivityItem.jsx
+++ b/frontend/src/metabase/home/components/ActivityItem.jsx
@@ -3,6 +3,7 @@ import PropTypes from "prop-types";
 import Icon from "metabase/components/Icon.jsx";
 import IconBorder from "metabase/components/IconBorder.jsx";
 import UserAvatar from "metabase/components/UserAvatar.jsx";
+import colors from "metabase/lib/colors";
 
 export default class ActivityItem extends Component {
   static propTypes = {
@@ -21,21 +22,21 @@ export default class ActivityItem extends Component {
             <UserAvatar
               user={item.user}
               background={userColors}
-              style={{ color: "#fff", borderWidth: "0" }}
+              style={{ color: colors["text-white"], borderWidth: 0 }}
             />
           ) : (
-            <IconBorder style={{ color: "#B8C0C8" }}>
+            <IconBorder style={{ color: colors["text-light"] }}>
               <Icon name="sync" size={16} />
             </IconBorder>
           )}
         </span>
 
         <div className="ml2 full flex align-center">
-          <div className="text-grey-4">
+          <div className="text-medium">
             <span className="text-dark">{description.userName}</span>&nbsp;
             {description.summary}
           </div>
-          <div className="flex-align-right text-right text-grey-2">
+          <div className="flex-align-right text-right text-light">
             {description.timeSince}
           </div>
         </div>
diff --git a/frontend/src/metabase/home/components/ActivityStory.jsx b/frontend/src/metabase/home/components/ActivityStory.jsx
index 6b3eecdbfcb748726079ba5ee633ce7aa9b7a273..0e378fd2414ce25b8d398d5e8ac5095ae274449c 100644
--- a/frontend/src/metabase/home/components/ActivityStory.jsx
+++ b/frontend/src/metabase/home/components/ActivityStory.jsx
@@ -1,6 +1,7 @@
 import React, { Component } from "react";
 import PropTypes from "prop-types";
 import { Link } from "react-router";
+import colors from "metabase/lib/colors";
 
 export default class ActivityStory extends Component {
   constructor(props, context) {
@@ -8,7 +9,7 @@ export default class ActivityStory extends Component {
 
     this.styles = {
       borderWidth: "2px",
-      borderColor: "#DFE8EA",
+      borderColor: colors["border"],
     };
   }
 
@@ -29,7 +30,7 @@ export default class ActivityStory extends Component {
         style={{
           borderWidth: "3px",
           marginLeft: "22px",
-          borderColor: "#F2F5F6",
+          borderColor: colors["border"],
         }}
       >
         <div className="flex full ml4 bordered rounded p2" style={this.styles}>
diff --git a/frontend/src/metabase/home/components/NewUserOnboardingModal.jsx b/frontend/src/metabase/home/components/NewUserOnboardingModal.jsx
index 6867bb0b14388a68d06dd0657223f2a68411898a..bb064a97cf9851d193b0dcb2a361843069e0493d 100644
--- a/frontend/src/metabase/home/components/NewUserOnboardingModal.jsx
+++ b/frontend/src/metabase/home/components/NewUserOnboardingModal.jsx
@@ -4,6 +4,7 @@ import StepIndicators from "metabase/components/StepIndicators";
 import RetinaImage from "react-retina-image";
 import { t } from "c-3po";
 import MetabaseSettings from "metabase/lib/settings";
+import colors from "metabase/lib/colors";
 
 type Props = {
   onClose: () => void,
@@ -106,8 +107,8 @@ const OnboardingImages = ({ currentStep }, { currentStep: object }) => (
   <div
     style={{
       position: "relative",
-      backgroundColor: "#F5F9FE",
-      borderBottom: "1px solid #DCE1E4",
+      backgroundColor: colors["bg-medium"],
+      borderBottom: `1px solid ${colors["border"]}`,
       height: 254,
       paddingTop: "3em",
       paddingBottom: "3em",
diff --git a/frontend/src/metabase/home/components/RecentViews.jsx b/frontend/src/metabase/home/components/RecentViews.jsx
index 6696cf92a6e4827ba4259f969cd26a6a9a8503f8..b20e30329c4f747ef33420c3457c86942691e1de 100644
--- a/frontend/src/metabase/home/components/RecentViews.jsx
+++ b/frontend/src/metabase/home/components/RecentViews.jsx
@@ -1,9 +1,10 @@
 import React, { Component } from "react";
 import PropTypes from "prop-types";
-import { Link } from "react-router";
 import { t } from "c-3po";
 
 import Icon from "metabase/components/Icon.jsx";
+import Ellipsified from "metabase/components/Ellipsified";
+import Link from "metabase/components/Link";
 import SidebarSection from "./SidebarSection.jsx";
 import * as Urls from "metabase/lib/urls";
 
@@ -56,18 +57,19 @@ export default class RecentViews extends Component {
                     data-metabase-event={
                       "Recent Views;" + item.model + ";" + item.cnt
                     }
-                    className="ml1 flex-full link"
+                    ml={1}
+                    className="link overflow-hidden"
                   >
-                    {item.model_object.name}
+                    <Ellipsified>{item.model_object.name}</Ellipsified>
                   </Link>
                 </li>
               );
             })}
           </ul>
         ) : (
-          <div className="flex flex-column layout-centered text-normal text-grey-2">
+          <div className="flex flex-column layout-centered text-normal text-light">
             <p
-              className="p3 text-centered text-grey-2"
+              className="p3 text-centered text-light"
               style={{ maxWidth: "100%" }}
             >
               {t`You haven't looked at any dashboards or questions recently`}
diff --git a/frontend/src/metabase/home/components/SidebarSection.jsx b/frontend/src/metabase/home/components/SidebarSection.jsx
index 2816ac1887252d6a0625b80c54efbf2c8e012145..64de827720c4dbe2eef75a261a3f13c8fd1ae185 100644
--- a/frontend/src/metabase/home/components/SidebarSection.jsx
+++ b/frontend/src/metabase/home/components/SidebarSection.jsx
@@ -1,6 +1,7 @@
 import React from "react";
 
 import Icon from "metabase/components/Icon.jsx";
+import colors from "metabase/lib/colors";
 
 const SidebarSection = ({ title, icon, extra, children }) => (
   <div className="px2 pt1">
@@ -9,7 +10,10 @@ const SidebarSection = ({ title, icon, extra, children }) => (
       <span className="pl1 Sidebar-header">{title}</span>
       {extra && <span className="float-right">{extra}</span>}
     </div>
-    <div className="rounded bg-white" style={{ border: "1px solid #E5E5E5" }}>
+    <div
+      className="rounded bg-white"
+      style={{ border: `1px solid ${colors["border"]}` }}
+    >
       {children}
     </div>
   </div>
diff --git a/frontend/src/metabase/home/components/Smile.jsx b/frontend/src/metabase/home/components/Smile.jsx
deleted file mode 100644
index 79c1c09127c4efb5670655748b8c701191ac0eec..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/home/components/Smile.jsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import React, { Component } from "react";
-
-export default class Smile extends Component {
-  render() {
-    const styles = {
-      width: "48px",
-      height: "48px",
-      backgroundImage: 'url("app/assets/img/smile.svg")',
-    };
-    return <div style={styles} className="hide md-show" />;
-  }
-}
diff --git a/frontend/src/metabase/home/containers/ArchiveApp.jsx b/frontend/src/metabase/home/containers/ArchiveApp.jsx
index f228427d6c02f8ca6ce75e5261fc47891e9922c5..2aba7ab44f1696d8b2986fac882245e12cbf99a0 100644
--- a/frontend/src/metabase/home/containers/ArchiveApp.jsx
+++ b/frontend/src/metabase/home/containers/ArchiveApp.jsx
@@ -76,9 +76,11 @@ export default class ArchiveApp extends Component {
           </Card>
         </Box>
         <BulkActionBar showing={selected.length > 0}>
-          <SelectionControls {...this.props} />
-          <BulkActionControls {...this.props} />
-          <Box ml="auto">{t`${selected.length} items selected`}</Box>
+          <Flex align="center" py={2} px={4}>
+            <SelectionControls {...this.props} />
+            <BulkActionControls {...this.props} />
+            <Box ml="auto">{t`${selected.length} items selected`}</Box>
+          </Flex>
         </BulkActionBar>
       </Box>
     );
diff --git a/frontend/src/metabase/home/containers/SearchApp.jsx b/frontend/src/metabase/home/containers/SearchApp.jsx
index e535386df6c8d9e27f79cee9ff063535d6890f7c..b32a2f33ec96d7b8ab81f8c7824c893c626a93e6 100644
--- a/frontend/src/metabase/home/containers/SearchApp.jsx
+++ b/frontend/src/metabase/home/containers/SearchApp.jsx
@@ -9,63 +9,156 @@ import { Box, Flex } from "grid-styled";
 import EntityListLoader from "metabase/entities/containers/EntityListLoader";
 
 import Card from "metabase/components/Card";
+import EmptyState from "metabase/components/EmptyState";
 import EntityItem from "metabase/components/EntityItem";
 import Subhead from "metabase/components/Subhead";
+import ItemTypeFilterBar, {
+  FILTERS,
+} from "metabase/components/ItemTypeFilterBar";
 
-import { entityTypeForModel } from "metabase/schema";
+const PAGE_PADDING = [1, 2, 4];
 
 export default class SearchApp extends React.Component {
   render() {
+    const { location } = this.props;
     return (
-      <Box mx={4}>
-        <Flex align="center" mb={2} py={3}>
-          <Subhead>{jt`Results for "${this.props.location.query.q}"`}</Subhead>
-        </Flex>
-        <Box w={2 / 3}>
+      <Box mx={PAGE_PADDING}>
+        {location.query.q && (
+          <Flex align="center" mb={2} py={[2, 3]}>
+            <Subhead>{jt`Results for "${location.query.q}"`}</Subhead>
+          </Flex>
+        )}
+        <Box w={[1, 2 / 3]}>
+          <ItemTypeFilterBar
+            analyticsContext={`Search Results`}
+            filters={FILTERS.concat({
+              name: t`Collections`,
+              filter: "collection",
+              icon: "all",
+            })}
+          />
           <EntityListLoader
             entityType="search"
-            entityQuery={this.props.location.query}
+            entityQuery={location.query}
             wrapped
           >
             {({ list }) => {
               if (list.length === 0) {
                 return (
-                  <Flex align="center" justify="center" my={4} py={4}>
-                    <Box>
-                      <img src="../app/assets/img/no_results.svg" />
-                    </Box>
-                    <Box mt={4}>
-                      <Subhead>{t`It's quiet around here...`}</Subhead>
-                      <Text
-                      >{t`Metabase couldn't find any results for this.`}</Text>
-                    </Box>
-                  </Flex>
+                  <Card>
+                    <EmptyState
+                      title={t`No results`}
+                      message={t`Metabase couldn't find any results for your search.`}
+                      illustrationElement={
+                        <img src="../app/assets/img/no_results.svg" />
+                      }
+                    />
+                  </Card>
                 );
               }
+
+              const types = _.chain(
+                location.query.type
+                  ? list.filter(l => l.model === location.query.type)
+                  : list,
+              )
+                .groupBy("model")
+                .value();
+
               return (
                 <Box>
-                  {_.chain(list)
-                    .groupBy("model")
-                    .pairs()
-                    .value()
-                    .map(([model, items]) => (
-                      <Box mt={2} mb={3}>
-                        <div className="text-uppercase text-grey-4 text-small text-bold my1">
-                          {entityTypeForModel(model)}
-                        </div>
-                        <Card>
-                          {items.map(item => (
-                            <Link to={item.getUrl()}>
-                              <EntityItem
-                                name={item.getName()}
-                                iconName={item.getIcon()}
-                                iconColor={item.getColor()}
-                              />
-                            </Link>
-                          ))}
-                        </Card>
-                      </Box>
-                    ))}
+                  {types.dashboard && (
+                    <Box mt={2} mb={3}>
+                      <div className="text-uppercase text-medium text-small text-bold my1">
+                        {t`Dashboards`}
+                      </div>
+                      <Card>
+                        {types.dashboard.map(item => (
+                          <Link
+                            to={item.getUrl()}
+                            key={item.id}
+                            data-metabase-event="Search Results;Item Click;Dashboard"
+                          >
+                            <EntityItem
+                              variant="list"
+                              name={item.getName()}
+                              iconName={item.getIcon()}
+                              iconColor={item.getColor()}
+                            />
+                          </Link>
+                        ))}
+                      </Card>
+                    </Box>
+                  )}
+                  {types.collection && (
+                    <Box mt={2} mb={3}>
+                      <div className="text-uppercase text-medium text-small text-bold my1">
+                        {t`Collections`}
+                      </div>
+                      <Card>
+                        {types.collection.map(item => (
+                          <Link
+                            to={item.getUrl()}
+                            key={item.id}
+                            data-metabase-event="Search Results;Item Click;Collection"
+                          >
+                            <EntityItem
+                              variant="list"
+                              name={item.getName()}
+                              iconName={item.getIcon()}
+                              iconColor={item.getColor()}
+                            />
+                          </Link>
+                        ))}
+                      </Card>
+                    </Box>
+                  )}
+                  {types.card && (
+                    <Box mt={2} mb={3}>
+                      <div className="text-uppercase text-medium text-small text-bold my1">
+                        {t`Questions`}
+                      </div>
+                      <Card>
+                        {types.card.map(item => (
+                          <Link
+                            to={item.getUrl()}
+                            key={item.id}
+                            data-metabase-event="Search Results;Item Click;Question"
+                          >
+                            <EntityItem
+                              variant="list"
+                              name={item.getName()}
+                              iconName={item.getIcon()}
+                              iconColor={item.getColor()}
+                            />
+                          </Link>
+                        ))}
+                      </Card>
+                    </Box>
+                  )}
+                  {types.pulse && (
+                    <Box mt={2} mb={3}>
+                      <div className="text-uppercase text-medium text-small text-bold my1">
+                        {t`Pulse`}
+                      </div>
+                      <Card>
+                        {types.pulse.map(item => (
+                          <Link
+                            to={item.getUrl()}
+                            key={item.id}
+                            data-metabase-event="Search Results;Item Click;Pulse"
+                          >
+                            <EntityItem
+                              variant="list"
+                              name={item.getName()}
+                              iconName={item.getIcon()}
+                              iconColor={item.getColor()}
+                            />
+                          </Link>
+                        ))}
+                      </Card>
+                    </Box>
+                  )}
                 </Box>
               );
             }}
diff --git a/frontend/src/metabase/icon_paths.js b/frontend/src/metabase/icon_paths.js
index d3b5c6d8b0df32b1825d23de5b3b2394b1f3876d..7b71cdcf03edc6f4eb9ee1c313a9c6906ac8284c 100644
--- a/frontend/src/metabase/icon_paths.js
+++ b/frontend/src/metabase/icon_paths.js
@@ -1,4 +1,5 @@
 /* @flow weak */
+/* eslint-disable no-color-literals */
 
 /*
     Metabase Icon Paths
@@ -45,6 +46,8 @@ export const ICON_PATHS = {
     "M2 23.467h6.4V32H2v-8.533zm10.667-12.8h6.4V32h-6.4V10.667zM23.333 0h6.4v32h-6.4V0z",
   beaker:
     "M4.31736354,31.1631075 C3.93810558,30.6054137 3.89343681,29.6635358 4.20559962,29.0817181 L11.806982,14.9140486 L11.8069821,10.5816524 L10.7015144,10.4653256 C10.0309495,10.394763 9.48734928,9.78799739 9.48734928,9.12166999 L9.48734928,7.34972895 C9.48734928,6.67821106 10.0368737,6.13383825 10.7172248,6.13383825 L21.8462005,6.13383825 C22.525442,6.13383825 23.0760761,6.68340155 23.0760761,7.34972895 L23.0760761,9.12166999 C23.0760761,9.79318788 22.5250158,10.3375607 21.856025,10.3375607 L20.9787023,10.3375607 L20.9787024,14.9281806 L28.77277,29.0827118 C29.0983515,29.6739888 29.0709073,30.6193105 28.7174156,31.1846409 L28.852457,30.9686726 C28.4963041,31.538259 27.6541076,32 26.9865771,32 L6.10749779,32 C5.43315365,32 4.58248747,31.5529687 4.19978245,30.9902061 L4.31736354,31.1631075 Z M15.5771418,17.6040443 C16.5170398,17.6040443 17.2789777,16.8377777 17.2789777,15.89254 C17.2789777,14.9473023 16.5170398,14.1810358 15.5771418,14.1810358 C14.6372438,14.1810358 13.8753059,14.9473023 13.8753059,15.89254 C13.8753059,16.8377777 14.6372438,17.6040443 15.5771418,17.6040443 Z M16.5496195,12.8974079 C17.8587633,12.8974079 18.9200339,11.830108 18.9200339,10.5135268 C18.9200339,9.1969457 17.8587633,8.1296458 16.5496195,8.1296458 C15.2404758,8.1296458 14.1792052,9.1969457 14.1792052,10.5135268 C14.1792052,11.830108 15.2404758,12.8974079 16.5496195,12.8974079 Z M5.71098553,30.2209651 L10.9595331,20.5151267 C10.9595331,20.5151267 12.6834557,21.2672852 14.3734184,21.2672852 C16.0633811,21.2672852 16.8198616,19.2872624 17.588452,18.6901539 C18.3570425,18.0930453 19.9467191,17.1113296 19.9467191,17.1113296 L27.0506095,30.1110325 L5.71098553,30.2209651 Z M13.6608671,4.37817079 C14.4114211,4.37817079 15.0198654,3.78121712 15.0198654,3.04483745 C15.0198654,2.30845779 14.4114211,1.71150412 13.6608671,1.71150412 C12.9103132,1.71150412 12.3018689,2.30845779 12.3018689,3.04483745 C12.3018689,3.78121712 12.9103132,4.37817079 13.6608671,4.37817079 Z M17.9214578,2.45333328 C18.6119674,2.45333328 19.1717361,1.90413592 19.1717361,1.22666664 C19.1717361,0.549197362 18.6119674,0 17.9214578,0 C17.2309481,0 16.6711794,0.549197362 16.6711794,1.22666664 C16.6711794,1.90413592 17.2309481,2.45333328 17.9214578,2.45333328 Z",
+  bell:
+    "M14.254 5.105c-7.422.874-8.136 7.388-8.136 11.12 0 4.007 0 5.61-.824 6.411-.549.535-1.647.802-3.294.802v4.006h28v-4.006c-1.647 0-2.47 0-3.294-.802-.55-.534-.824-3.205-.824-8.013-.493-5.763-3.205-8.936-8.136-9.518a2.365 2.365 0 0 0 .725-1.701C18.47 2.076 17.364 1 16 1s-2.47 1.076-2.47 2.404c0 .664.276 1.266.724 1.7zM11.849 29c.383 1.556 1.793 2.333 4.229 2.333s3.845-.777 4.229-2.333h-8.458z",
   bolt: "M21.697 0L8 16.809l7.549 2.538L11.687 32l12.652-16.6-7.695-2.317z",
   breakout:
     "M24.47 1H32v7.53h-7.53V1zm0 11.294H32v7.53h-7.53v-7.53zm0 11.294H32v7.53h-7.53v-7.53zM0 1h9.412v30.118H0V1zm11.731 13.714c.166-.183.452-.177.452-.177h6.475s-1.601-2.053-2.07-2.806c-.469-.753-.604-1.368 0-1.905.603-.536 1.226-.281 1.878.497.652.779 2.772 3.485 3.355 4.214.583.73.65 1.965 0 2.835-.65.87-2.65 4.043-3.163 4.65-.514.607-1.123.713-1.732.295-.609-.419-.838-1.187-.338-1.872.5-.684 2.07-3.073 2.07-3.073h-6.475s-.27 0-.46-.312-.151-.612-.151-.612l.007-1.246s-.014-.306.152-.488z",
@@ -75,13 +78,8 @@ export const ICON_PATHS = {
     "M4 8 L8 4 L16 12 L24 4 L28 8 L20 16 L28 24 L24 28 L16 20 L8 28 L4 24 L12 16 z ",
   collection:
     "M16.5695046,2.82779686 L15.5639388,2.83217072 L30.4703127,11.5065092 L30.4818076,9.80229623 L15.5754337,18.2115855 L16.5436335,18.2077098 L1.65289961,9.96407638 L1.67877073,11.6677911 L16.5695046,2.82779686 Z M0.691634577,11.6826271 L15.5823685,19.9262606 C15.8836872,20.0930731 16.2506087,20.0916044 16.5505684,19.9223849 L31.4569423,11.5130957 C32.1196316,11.1392458 32.1260238,10.1915465 31.4684372,9.80888276 L16.5620632,1.1345443 C16.2511162,0.953597567 15.8658421,0.955273376 15.5564974,1.13891816 L0.665763463,9.97891239 C0.0118284022,10.3671258 0.0262104889,11.3142428 0.691634577,11.6826271 Z M15.5699489,25.798061 L16.0547338,26.0652615 L16.536759,25.7931643 L31.4991818,17.3470627 C31.973977,17.0790467 32.1404815,16.4788587 31.8710802,16.0065052 C31.6016788,15.5341517 30.9983884,15.3685033 30.5235933,15.6365193 L15.5611705,24.0826209 L16.5279806,24.0777242 L1.46763754,15.7768642 C0.99012406,15.5136715 0.388560187,15.6854222 0.124007019,16.16048 C-0.14054615,16.6355379 0.0320922897,17.2340083 0.509605765,17.497201 L15.5699489,25.798061 Z M15.5699489,31.7327994 L16.0547338,32 L16.536759,31.7279028 L31.4991818,23.2818011 C31.973977,23.0137852 32.1404815,22.4135972 31.8710802,21.9412437 C31.6016788,21.4688901 30.9983884,21.3032418 30.5235933,21.5712578 L15.5611705,30.0173594 L16.5279806,30.0124627 L1.46763754,21.7116027 C0.99012406,21.44841 0.388560187,21.6201606 0.124007019,22.0952185 C-0.14054615,22.5702764 0.0320922897,23.1687467 0.509605765,23.4319394 L15.5699489,31.7327994 Z",
-  compare: {
-    path:
-      "M8.514 23.486C3.587 21.992 0 17.416 0 12 0 5.373 5.373 0 12 0c5.415 0 9.992 3.587 11.486 8.514C28.413 10.008 32 14.584 32 20c0 6.627-5.373 12-12 12-5.415 0-9.992-3.587-11.486-8.514zm2.293.455A10.003 10.003 0 0 0 20 30c5.523 0 10-4.477 10-10 0-4.123-2.496-7.664-6.059-9.193.04.392.059.79.059 1.193 0 6.627-5.373 12-12 12-.403 0-.8-.02-1.193-.059z",
-    attrs: {
-      fillRule: "nonzero",
-    },
-  },
+  compare:
+    "M24.5815137,18.6109617 L32.110976,18.6109617 L32.110976,25.7224662 L24.5815137,25.7224662 L24.5815137,31.7951059 L14.9536477,22.1672398 L24.5815137,12.5393738 L24.5815137,18.6109617 Z M7.52979828,20.2568769 L7.52941566,14.1855356 L0.000277209616,14.1851279 L-3.37507799e-14,7.07317197 L7.52913845,7.0735796 L7.5287557,1 L17.1577154,10.6289597 L7.52979828,20.2568769 Z",
   compass_needle: {
     path:
       "M0 32l10.706-21.064L32 0 21.22 20.89 0 32zm16.092-12.945a3.013 3.013 0 0 0 3.017-3.009 3.013 3.013 0 0 0-3.017-3.008 3.013 3.013 0 0 0-3.017 3.008 3.013 3.013 0 0 0 3.017 3.009z",
@@ -108,6 +106,8 @@ export const ICON_PATHS = {
     "M27 19a3 3 0 1 1 0-6 3 3 0 0 1 0 6zM16 8a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0 22a3 3 0 1 1 0-6 3 3 0 0 1 0 6zM5 19a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm11 0a3 3 0 1 1 0-6 3 3 0 0 1 0 6z",
   costextended:
     "M27,19 C25.3431458,19 24,17.6568542 24,16 C24,14.3431458 25.3431458,13 27,13 C28.6568542,13 30,14.3431458 30,16 C30,17.6568542 28.6568542,19 27,19 Z M16,8 C14.3431458,8 13,6.65685425 13,5 C13,3.34314575 14.3431458,2 16,2 C17.6568542,2 19,3.34314575 19,5 C19,6.65685425 17.6568542,8 16,8 Z M16,30 C14.3431458,30 13,28.6568542 13,27 C13,25.3431458 14.3431458,24 16,24 C17.6568542,24 19,25.3431458 19,27 C19,28.6568542 17.6568542,30 16,30 Z M5,19 C3.34314575,19 2,17.6568542 2,16 C2,14.3431458 3.34314575,13 5,13 C6.65685425,13 8,14.3431458 8,16 C8,17.6568542 6.65685425,19 5,19 Z M16,19 C14.3431458,19 13,17.6568542 13,16 C13,14.3431458 14.3431458,13 16,13 C17.6568542,13 19,14.3431458 19,16 C19,17.6568542 17.6568542,19 16,19 Z M10,12 C8.8954305,12 8,11.1045695 8,10 C8,8.8954305 8.8954305,8 10,8 C11.1045695,8 12,8.8954305 12,10 C12,11.1045695 11.1045695,12 10,12 Z M22,12 C20.8954305,12 20,11.1045695 20,10 C20,8.8954305 20.8954305,8 22,8 C23.1045695,8 24,8.8954305 24,10 C24,11.1045695 23.1045695,12 22,12 Z M22,24 C20.8954305,24 20,23.1045695 20,22 C20,20.8954305 20.8954305,20 22,20 C23.1045695,20 24,20.8954305 24,22 C24,23.1045695 23.1045695,24 22,24 Z M10,24 C8.8954305,24 8,23.1045695 8,22 C8,20.8954305 8.8954305,20 10,20 C11.1045695,20 12,20.8954305 12,22 C12,23.1045695 11.1045695,24 10,24 Z",
+  csv:
+    "M28 10.105v18.728A3.166 3.166 0 0 1 24.834 32H6.166A3.163 3.163 0 0 1 3 28.844V3.156A3.163 3.163 0 0 1 6.16 0h13.553V10.105H28zm-.215-1.684h-6.4V.311l6.4 8.11zM17 13v2h2v-2h-2zm0 4v2h2v-2h-2zm4-4v2h2v-2h-2zM7 13v2h7v-2H7zm14 4v2h2v-2h-2zM7 17v2h7v-2H7zm10 4v2h2v-2h-2zm4 0v2h2v-2h-2zM7 21v2h7v-2H7z",
   database:
     "M1.18285296e-08,10.5127919 C-1.47856568e-08,7.95412848 1.18285298e-08,4.57337284 1.18285298e-08,4.57337284 C1.18285298e-08,4.57337284 1.58371041,5.75351864e-10 15.6571342,0 C29.730558,-5.7535027e-10 31.8900148,4.13849684 31.8900148,4.57337284 L31.8900148,10.4843058 C31.8900148,10.4843058 30.4448001,15.1365942 16.4659751,15.1365944 C2.48715012,15.1365947 2.14244494e-08,11.4353349 1.18285296e-08,10.5127919 Z M0.305419478,21.1290071 C0.305419478,21.1290071 0.0405133833,21.2033291 0.0405133833,21.8492606 L0.0405133833,27.3032816 C0.0405133833,27.3032816 1.46515486,31.941655 15.9641228,31.941655 C30.4630908,31.941655 32,27.3446712 32,27.3446712 C32,27.3446712 32,21.7986104 32,21.7986105 C32,21.2073557 31.6620557,21.0987647 31.6620557,21.0987647 C31.6620557,21.0987647 29.7146434,25.22314 16.0318829,25.22314 C2.34912233,25.22314 0.305419478,21.1290071 0.305419478,21.1290071 Z M0.305419478,12.656577 C0.305419478,12.656577 0.0405133833,12.730899 0.0405133833,13.3768305 L0.0405133833,18.8308514 C0.0405133833,18.8308514 1.46515486,23.4692249 15.9641228,23.4692249 C30.4630908,23.4692249 32,18.8722411 32,18.8722411 C32,18.8722411 32,13.3261803 32,13.3261803 C32,12.7349256 31.6620557,12.6263346 31.6620557,12.6263346 C31.6620557,12.6263346 29.7146434,16.7507099 16.0318829,16.7507099 C2.34912233,16.7507099 0.305419478,12.656577 0.305419478,12.656577 Z",
   dash: "M0 13h32v6.61H0z",
@@ -170,10 +170,14 @@ export const ICON_PATHS = {
   },
   folder:
     "M0 5l.01 21.658a2 2 0 0 0 2 1.999H30a2 2 0 0 0 2-2V7.705a2 2 0 0 0-2-2H17.51a1 1 0 0 1-.924-.615l-.614-1.474A1 1 0 0 0 15.049 3H1.999a2 2 0 0 0-2 2z",
+  gauge:
+    "M5.197 29.803A15.958 15.958 0 0 1 0 18C0 9.163 7.163 2 16 2s16 7.163 16 16a15.96 15.96 0 0 1-5.344 11.936L22.983 26.5A10.978 10.978 0 0 0 27 18c0-6.075-4.925-11-11-11S5 11.925 5 18c0 3.292 1.446 6.246 3.738 8.262l-3.54 3.54zM13 21.25a3.774 3.774 0 0 1 1.122-5.975L23 11l-4.34 9.347a3.455 3.455 0 0 1-5.66.903z",
   gear:
     "M14 0 H18 L19 6 L20.707 6.707 L26 3.293 L28.707 6 L25.293 11.293 L26 13 L32 14 V18 L26 19 L25.293 20.707 L28.707 26 L26 28.707 L20.707 25.293 L19 26 L18 32 L14 32 L13 26 L11.293 25.293 L6 28.707 L3.293 26 L6.707 20.707 L6 19 L0 18 L0 14 L6 13 L6.707 11.293 L3.293 6 L6 3.293 L11.293 6.707 L13 6 L14 0 z M16 10 A6 6 0 0 0 16 22 A6 6 0 0 0 16 10",
   grabber:
     "M0,5 L32,5 L32,9.26666667 L0,9.26666667 L0,5 Z M0,13.5333333 L32,13.5333333 L32,17.8 L0,17.8 L0,13.5333333 Z M0,22.0666667 L32,22.0666667 L32,26.3333333 L0,26.3333333 L0,22.0666667 Z",
+  grabber2:
+    "M9.692 7.385a3.692 3.692 0 1 1 0-7.385 3.692 3.692 0 0 1 0 7.385zm0 12.307a3.692 3.692 0 1 1 0-7.384 3.692 3.692 0 0 1 0 7.384zM22 7.385A3.692 3.692 0 1 1 22 0a3.692 3.692 0 0 1 0 7.385zm0 12.307a3.692 3.692 0 1 1 0-7.384 3.692 3.692 0 0 1 0 7.384zM9.692 32a3.692 3.692 0 1 1 0-7.385 3.692 3.692 0 0 1 0 7.385zM22 32a3.692 3.692 0 1 1 0-7.385A3.692 3.692 0 0 1 22 32z",
   grid:
     "M2 2 L10 2 L10 10 L2 10z M12 2 L20 2 L20 10 L12 10z M22 2 L30 2 L30 10 L22 10z M2 12 L10 12 L10 20 L2 20z M12 12 L20 12 L20 20 L12 20z M22 12 L30 12 L30 20 L22 20z M2 22 L10 22 L10 30 L2 30z M12 22 L20 22 L20 30 L12 30z M22 22 L30 22 L30 30 L22 30z",
   google: {
@@ -198,6 +202,8 @@ export const ICON_PATHS = {
   },
   io:
     "M1,9 L6,9 L6,24 L1,24 L1,9 Z M31,16 C31,11.581722 27.418278,8 23,8 C18.581722,8 15,11.581722 15,16 C15,20.418278 18.581722,24 23,24 C27.418278,24 31,20.418278 31,16 Z M19,16 C19,13.790861 20.790861,12 23,12 C25.209139,12 27,13.790861 27,16 C27,18.209139 25.209139,20 23,20 C20.790861,20 19,18.209139 19,16 Z M15.3815029,9 L13.4537572,9 L7,23.5 L8.92774566,23.5 L15.3815029,9 Z",
+  json:
+    "M28 10.105v18.728A3.166 3.166 0 0 1 24.834 32H6.166A3.163 3.163 0 0 1 3 28.844V3.156A3.163 3.163 0 0 1 6.16 0h13.553V10.105H28zm-.215-1.684h-6.4V.311l6.4 8.11zM10.894 19.233v-.218c1.162-.13 1.79-.718 1.79-1.703v-1.394c0-.964.322-1.333 1.19-1.333h.3v-1.45h-.505c-2.03 0-2.885.766-2.885 2.55v1.094c0 1.005-.451 1.388-1.613 1.395v1.9c1.169.007 1.613.39 1.613 1.395v1.066c0 1.805.862 2.584 2.885 2.584h.506v-1.45h-.301c-.861 0-1.19-.361-1.19-1.332v-1.401c0-.992-.628-1.573-1.79-1.703zm8.184-.212v.22c-1.162.122-1.791.71-1.791 1.701v1.395c0 .964-.321 1.333-1.19 1.333h-.3v1.45h.505c2.03 0 2.892-.766 2.892-2.55v-1.094c0-1.012.444-1.388 1.607-1.395v-1.9c-1.17-.014-1.607-.39-1.607-1.395v-1.073c0-1.798-.861-2.577-2.892-2.577h-.505v1.449h.3c.862 0 1.19.362 1.19 1.326v1.408c0 .985.629 1.573 1.79 1.702z",
   key: {
     path:
       "M11.5035746,7.9975248 C10.8617389,5.26208051 13.0105798,1.44695394 16.9897081,1.44695394 C20.919315,1.44695394 23.1811258,5.37076315 22.2565255,8.42469226 C21.3223229,7.86427598 20.2283376,7.54198814 19.0589133,7.54198814 C17.3567818,7.54198814 15.8144729,8.22477622 14.6920713,9.33083544 C14.4930673,9.31165867 14.2913185,9.30184676 14.087273,9.30184676 C10.654935,9.30184676 7.87247532,12.0782325 7.87247532,15.5030779 C7.87247532,17.1058665 8.48187104,18.5666337 9.48208198,19.6672763 L8.98356958,20.658345 L9.19925633,22.7713505 L7.5350473,23.4587525 C7.37507672,23.5248284 7.30219953,23.707739 7.37031308,23.8681037 L7.95501877,25.2447188 L6.28291833,25.7863476 C6.10329817,25.8445303 6.01548404,26.0233452 6.06755757,26.1919683 L6.54426059,27.7356153 L5.02460911,28.2609385 C4.86686602,28.3154681 4.7743984,28.501653 4.83652351,28.6704172 L6.04508836,31.95351 C6.10987939,32.1295162 6.29662279,32.2151174 6.46814592,32.160881 L9.48965349,31.2054672 C9.66187554,31.1510098 9.86840241,30.9790422 9.95250524,30.8208731 L14.8228902,21.6613229 C15.8820565,21.5366928 16.8596786,21.1462953 17.6869404,20.558796 C17.5652123,20.567429 17.4424042,20.5718139 17.318643,20.5718139 C14.2753735,20.5718139 11.8083161,17.9204625 11.8083161,14.6498548 C11.8083161,12.518229 12.8562751,10.6496514 14.428709,9.60671162 C13.4433608,10.7041074 12.8441157,12.1538355 12.8441157,13.7432193 C12.8441157,16.9974306 15.3562245,19.6661883 18.5509945,19.9240384 L19.1273026,21.5699573 L20.7971002,22.8826221 L20.1355191,24.5572635 C20.0719252,24.7182369 20.1528753,24.8977207 20.3155476,24.9601226 L21.7119724,25.4957977 L20.9400489,27.0748531 C20.8571275,27.2444782 20.9247553,27.4318616 21.082226,27.5115385 L22.5237784,28.2409344 L21.8460256,29.6990003 C21.7756734,29.8503507 21.8453702,30.0462011 22.0099247,30.1187455 L25.2111237,31.5300046 C25.3827397,31.6056621 25.5740388,31.5307937 25.6541745,31.3697345 L27.0658228,28.5325576 C27.1462849,28.3708422 27.1660474,28.1028205 27.1106928,27.9324485 L23.8023823,17.7500271 C24.7201964,16.6692906 25.273711,15.270754 25.273711,13.7432193 C25.273711,12.0364592 24.582689,10.4907436 23.4645818,9.36943333 C25.0880384,5.38579616 22.187534,0 16.9897081,0 C12.1196563,0 9.42801686,4.46934651 10.0266074,7.9975248 L11.5035746,7.9975248 Z M19.0589133,14.7767578 C20.203026,14.7767578 21.1305126,13.8512959 21.1305126,12.7096808 C21.1305126,11.5680656 20.203026,10.6426037 19.0589133,10.6426037 C17.9148007,10.6426037 16.9873141,11.5680656 16.9873141,12.7096808 C16.9873141,13.8512959 17.9148007,14.7767578 19.0589133,14.7767578 Z",
@@ -246,7 +252,7 @@ export const ICON_PATHS = {
   permissionsLimited:
     "M0,16 C0,7.163444 7.163444,0 16,0 C24.836556,0 32,7.163444 32,16 C32,24.836556 24.836556,32 16,32 C7.163444,32 0,24.836556 0,16 Z M29,16 C29,8.82029825 23.1797017,3 16,3 C8.82029825,3 3,8.82029825 3,16 C3,23.1797017 8.82029825,29 16,29 C23.1797017,29 29,23.1797017 29,16 Z M16,5 C11.0100706,5.11743299 5.14533409,7.90852303 5,15.5 C4.85466591,23.091477 11.0100706,26.882567 16,27 L16,5 Z",
   person:
-    "M16.12 20.392c-4.38 0-7.932-4.117-7.932-9.196S11.739 2 16.12 2c4.38 0 7.932 4.117 7.932 9.196 0 5.08-3.551 9.196-7.932 9.196zm14.644 10.51H1.476c0-4.218 2.608-7.932 6.563-10.1 2.088 2.185 4.938 3.532 8.081 3.532s5.993-1.347 8.081-3.533c3.955 2.169 6.563 5.883 6.563 10.101z",
+    "M16.068.005c5.181-.185 7.295 4.545 7.295 7.258s-1.34 6.71-3.607 8.77c-.5.456-.408 3.34.686 3.808 2.294.982 8.57 2.97 8.808 7.065.265 4.558-7.968 5.022-13.043 5.022-5.075 0-13.207-.62-13.207-4.45 0-1.776.178-2.944 3.106-4.92 2.927-1.978 5.16-2.462 5.645-2.763.486-.3 1.384-1.861.8-3.606 0 0-3.518-3.842-3.518-8.074 0-4.232 1.853-7.925 7.035-8.11z",
   pie:
     "M15.181 15.435V.021a15.94 15.94 0 0 1 11.42 3.995l-11.42 11.42zm1.131 1.384H31.98a15.941 15.941 0 0 0-4.114-11.553L16.312 16.819zm15.438 2.013H13.168V.25C5.682 1.587 0 8.13 0 16c0 8.837 7.163 16 16 16 7.87 0 14.413-5.682 15.75-13.168z",
   pin:
@@ -339,8 +345,12 @@ export const ICON_PATHS = {
   },
   x:
     "m11.271709,16 l-3.19744231e-13,4.728291 l4.728291,0 l16,11.271709 l27.271709,2.39808173e-13 l32,4.728291 l20.728291,16 l31.1615012,26.4332102 l26.4332102,31.1615012 l16,20.728291 l5.56678976,31.1615012 l0.838498756,26.4332102 l11.271709,16 z",
-  zoom:
+  "zoom-in":
     "M12.416 12.454V8.37h3.256v4.083h4.07v3.266h-4.07v4.083h-3.256V15.72h-4.07v-3.266h4.07zm10.389 13.28c-5.582 4.178-13.543 3.718-18.632-1.37-5.58-5.581-5.595-14.615-.031-20.179 5.563-5.563 14.597-5.55 20.178.031 5.068 5.068 5.545 12.985 1.422 18.563l5.661 5.661a2.08 2.08 0 0 1 .003 2.949 2.085 2.085 0 0 1-2.95-.003l-5.651-5.652zm-1.486-4.371c3.895-3.895 3.885-10.218-.021-14.125-3.906-3.906-10.23-3.916-14.125-.021-3.894 3.894-3.885 10.218.022 14.124 3.906 3.907 10.23 3.916 14.124.022z",
+  xlsx:
+    "M28 10.105v18.728A3.166 3.166 0 0 1 24.834 32H6.166A3.163 3.163 0 0 1 3 28.844V3.156A3.163 3.163 0 0 1 6.16 0h13.553V10.105H28zm-.215-1.684h-6.4V.311l6.4 8.11zM9.446 25h2.427l2.796-3.37h.055L16.207 25h2.256l-2.18-4.888 4.32-4.976h-2.468l-2.769 3.343h-.054l-1.477-3.343h-2.256l2.18 4.874L9.447 25z",
+  "zoom-out":
+    "M22.8048272,25.7342559 C17.2227958,29.9120922 9.26170192,29.4524812 4.17264358,24.3634229 C-1.40785877,18.7829205 -1.42162182,9.74890155 4.14190296,4.18537677 C9.70542774,-1.37814801 18.7394467,-1.36438497 24.319949,4.21611739 C29.3880584,9.28422669 29.8647559,17.2007749 25.7421403,22.7792454 L31.4029793,28.4400843 C32.2180024,29.2551074 32.2248604,30.569663 31.4056282,31.3888951 C30.5920681,32.2024552 29.2715215,32.2009502 28.4568175,31.3862462 L22.8048272,25.7342559 Z M21.3193717,21.3628455 C25.213839,17.4683781 25.2042049,11.1445649 21.2978532,7.23821321 C17.3915016,3.33186156 11.0676883,3.32222743 7.17322097,7.21669477 C3.27875362,11.1111621 3.28838776,17.4349754 7.1947394,21.341327 C11.1010911,25.2476787 17.4249043,25.2573128 21.3193717,21.3628455 Z M8.34528717,12.453545 L19.7423242,12.453545 L19.7423242,15.7197782 L8.34528717,15.7197782 L8.34528717,12.453545 Z",
   slack: {
     img: "app/assets/img/slack.png",
   },
diff --git a/frontend/src/metabase/internal/components/ColorsApp.jsx b/frontend/src/metabase/internal/components/ColorsApp.jsx
index a10f3e3b7358ffc8b3c3a85fe6253097f4492670..81179fcfa3e533cc147a1473ddf560a7a87c662b 100644
--- a/frontend/src/metabase/internal/components/ColorsApp.jsx
+++ b/frontend/src/metabase/internal/components/ColorsApp.jsx
@@ -4,7 +4,7 @@ import { Box, Flex } from "grid-styled";
 import CopyToClipboard from "react-copy-to-clipboard";
 import Subhead from "metabase/components/Subhead";
 
-import { normal, saturated, harmony } from "metabase/lib/colors";
+import colors, { harmony } from "metabase/lib/colors";
 
 import withToast from "metabase/hoc/Toast";
 
@@ -44,22 +44,14 @@ let colorStyles = require("!style-loader!css-loader?modules!postcss-loader!metab
 const ColorsApp = () => (
   <div className="wrapper">
     <Box my={2}>
-      <Subhead my={3}>Normal</Subhead>
+      <Subhead my={3}>App colors</Subhead>
       <Flex wrap>
-        {Object.entries(normal).map(([name, value]) => (
-          <ColorSwatch color={value} name={name} />
+        {Object.entries(colors).map(([name, color], index) => (
+          <ColorSwatch color={color} name={name} />
         ))}
       </Flex>
     </Box>
-    <Box my={2}>
-      <Subhead my={3}>Saturated</Subhead>
-      <Flex wrap>
-        {Object.entries(saturated).map(([name, value]) => (
-          <ColorSwatch color={value} name={name} />
-        ))}
-      </Flex>
-    </Box>
-    <Box my={2}>
+    <Box>
       <Subhead my={3}>Chart colors</Subhead>
       <Flex wrap>
         {harmony.map((color, index) => (
diff --git a/frontend/src/metabase/internal/components/ComponentsApp.jsx b/frontend/src/metabase/internal/components/ComponentsApp.jsx
index 9b5d5455a5a5a71fd0f6287ce4505c182b1aaf69..761c82057bde8e54487cfd7011a7b9c054a84bb2 100644
--- a/frontend/src/metabase/internal/components/ComponentsApp.jsx
+++ b/frontend/src/metabase/internal/components/ComponentsApp.jsx
@@ -48,16 +48,13 @@ export default class ComponentsApp extends Component {
             ))}
           </ul>
         </nav>
-        <div
-          className="bg-slate-extra-light flex-full"
-          style={{ flex: "66.66%" }}
-        >
+        <div className="bg-light flex-full bg-white" style={{ flex: "66.66%" }}>
           <div className="p4">
             {COMPONENTS.filter(
               ({ component, description, examples }) =>
                 !componentName || componentName === slugify(component.name),
-            ).map(({ component, description, examples }) => (
-              <div id={component.name}>
+            ).map(({ component, description, examples }, index) => (
+              <div id={component.name} key={index}>
                 <h2>
                   <Link
                     to={`_internal/components/${slugify(component.name)}`}
diff --git a/frontend/src/metabase/internal/routes.js b/frontend/src/metabase/internal/routes.js
index 82cbd13ec1bb2edcab36ad4bbaf7f43164ee4d29..78896744c2498d6d0bfb4d780720b2cc22b14471 100644
--- a/frontend/src/metabase/internal/routes.js
+++ b/frontend/src/metabase/internal/routes.js
@@ -3,6 +3,15 @@
 import React from "react";
 import { Link, Route, IndexRoute } from "react-router";
 
+import {
+  Archived,
+  GenericError,
+  NotFound,
+  Unauthorized,
+} from "metabase/containers/ErrorPages";
+
+const ErrorWithDetails = () => <GenericError details="Example error message" />;
+
 // $FlowFixMe: doesn't know about require.context
 const req = require.context(
   "metabase/internal/components",
@@ -64,5 +73,12 @@ export default (
           <Route path={name.toLowerCase()} component={Component} />
         )),
     )}
+    <Route path="errors">
+      <Route path="404" component={NotFound} />
+      <Route path="archived" component={Archived} />
+      <Route path="unauthorized" component={Unauthorized} />
+      <Route path="generic" component={GenericError} />
+      <Route path="details" component={ErrorWithDetails} />
+    </Route>
   </Route>
 );
diff --git a/frontend/src/metabase/lib/ace/theme-metabase.js b/frontend/src/metabase/lib/ace/theme-metabase.js
index 64b52410d885cb0476c1e7d5223312965c66f95b..d54a51d97116a40710072032e306573c712171a4 100644
--- a/frontend/src/metabase/lib/ace/theme-metabase.js
+++ b/frontend/src/metabase/lib/ace/theme-metabase.js
@@ -1,5 +1,6 @@
 /*global ace*/
-/* eslint "import/no-commonjs": 0 */
+/* eslint-disable import/no-commonjs */
+/* eslint-disable no-color-literals */
 ace.define(
   "ace/theme/metabase",
   ["require", "exports", "module", "ace/lib/dom"],
diff --git a/frontend/src/metabase/lib/card.js b/frontend/src/metabase/lib/card.js
index a01aebd6e0ac35ca72e140f76d443076758d94df..cfb481b071acf4046dc7787e6346da8714ee4e4b 100644
--- a/frontend/src/metabase/lib/card.js
+++ b/frontend/src/metabase/lib/card.js
@@ -47,7 +47,7 @@ export function isCardDirty(card, originalCard) {
   if (!card) {
     return false;
   } else if (!card.id) {
-    if (card.dataset_query.query && card.dataset_query.query.source_table) {
+    if (card.dataset_query.query && card.dataset_query.query["source-table"]) {
       return true;
     } else if (
       card.dataset_query.native &&
diff --git a/frontend/src/metabase/lib/colors.js b/frontend/src/metabase/lib/colors.js
index 93807810d9fb93798c78eb42f41f5c3c21112ea9..09fa1a88d8e38521c557fcd35c074a1200e2cf8d 100644
--- a/frontend/src/metabase/lib/colors.js
+++ b/frontend/src/metabase/lib/colors.js
@@ -1,82 +1,117 @@
 // @flow
 
 import d3 from "d3";
+import Color from "color";
+import { Harmonizer } from "color-harmony";
 
 type ColorName = string;
-type Color = string;
-type ColorFamily = { [name: ColorName]: Color };
+type ColorString = string;
+type ColorFamily = { [name: ColorName]: ColorString };
 
-export const normal = {
-  blue: "#509EE3",
-  green: "#9CC177",
-  purple: "#A989C5",
-  red: "#EF8C8C",
-  yellow: "#f9d45c",
-  orange: "#F1B556",
-  teal: "#A6E7F3",
-  indigo: "#7172AD",
-  gray: "#7B8797",
-  grey1: "#DCE1E4",
-  grey2: "#93A1AB",
-  grey3: "#2E353B",
-  text: "#2E353B",
+// NOTE: DO NOT ADD COLORS WITHOUT EXTREMELY GOOD REASON AND DESIGN REVIEW
+// NOTE: KEEP SYNCRONIZED WITH COLORS.CSS
+/* eslint-disable no-color-literals */
+const colors = {
+  brand: "#509EE3",
+  accent1: "#9CC177",
+  accent2: "#A989C5",
+  accent3: "#EF8C8C",
+  accent4: "#F9D45C",
+  accent5: "#F1B556",
+  accent6: "#A6E7F3",
+  accent7: "#7172AD",
+  white: "#FFFFFF",
+  black: "#2E353B",
+  success: "#84BB4C",
+  error: "#ED6E6E",
+  warning: "#F9CF48",
+  "text-dark": "#2E353B",
+  "text-medium": "#74838F",
+  "text-light": "#C7CFD4",
+  "text-white": "#FFFFFF",
+  "bg-black": "#2E353B",
+  "bg-dark": "#93A1AB",
+  "bg-medium": "#EDF2F5",
+  "bg-light": "#F9FBFC",
+  "bg-white": "#FFFFFF",
+  shadow: "rgba(0,0,0,0.08)",
+  border: "#D7DBDE",
 };
+/* eslint-enable no-color-literals */
+export default colors;
 
-export const saturated = {
-  blue: "#2D86D4",
-  green: "#84BB4C",
-  purple: "#885AB1",
-  red: "#ED6E6E",
-  yellow: "#F9CF48",
-};
+export const harmony = [];
 
-export const desaturated = {
-  blue: "#72AFE5",
-  green: "#A8C987",
-  purple: "#B8A2CC",
-  red: "#EEA5A5",
-  yellow: "#F7D97B",
-};
+// DEPRECATED: we should remove these and use `colors` directly
+// compute satured/desaturated variants using "color" lib if absolutely required
+export const normal = {};
+export const saturated = {};
+export const desaturated = {};
+
+// make sure to do the initial "sync"
+syncColors();
+
+export function syncColors() {
+  syncHarmony();
+  syncDeprecatedColorFamilies();
+}
 
-export const harmony = [
-  "#509ee3",
-  "#9cc177",
-  "#a989c5",
-  "#ef8c8c",
-  "#f9d45c",
-  "#F1B556",
-  "#A6E7F3",
-  "#7172AD",
-  "#7B8797",
-  "#6450e3",
-  "#55e350",
-  "#e35850",
-  "#77c183",
-  "#7d77c1",
-  "#c589b9",
-  "#bec589",
-  "#89c3c5",
-  "#c17777",
-  "#899bc5",
-  "#efce8c",
-  "#50e3ae",
-  "#be8cef",
-  "#8cefc6",
-  "#ef8cde",
-  "#b5f95c",
-  "#5cc2f9",
-  "#f95cd0",
-  "#c1a877",
-  "#f95c67",
-];
+function syncHarmony() {
+  const harmonizer = new Harmonizer();
+  const initialColors = [
+    colors["brand"],
+    colors["accent1"],
+    colors["accent2"],
+    colors["accent3"],
+    colors["accent4"],
+    colors["accent5"],
+    colors["accent6"],
+    colors["accent7"],
+  ];
+  harmony.splice(0, harmony.length);
+  // round 0 includes brand and all accents
+  harmony.push(...initialColors);
+  // rounds 1-4 generated harmony
+  // only harmonize brand and accents 1 through 4
+  const initialColorHarmonies = initialColors
+    .slice(0, 5)
+    .map(color => harmonizer.harmonize(color, "fiveToneD"));
+  for (let roundIndex = 1; roundIndex < 5; roundIndex++) {
+    for (
+      let colorIndex = 0;
+      colorIndex < initialColorHarmonies.length;
+      colorIndex++
+    ) {
+      harmony.push(initialColorHarmonies[colorIndex][roundIndex]);
+    }
+  }
+}
 
-export const getRandomColor = (family: ColorFamily): Color => {
+// syncs deprecated color families for legacy code
+function syncDeprecatedColorFamilies() {
+  // normal + saturated + desaturated
+  normal.blue = saturated.blue = desaturated.blue = colors["brand"];
+  normal.green = saturated.green = desaturated.green = colors["accent1"];
+  normal.purple = saturated.purple = desaturated.purple = colors["accent2"];
+  normal.red = saturated.red = desaturated.red = colors["accent3"];
+  normal.yellow = saturated.yellow = desaturated.yellow = colors["accent4"];
+  normal.orange = colors["accent5"];
+  normal.teal = colors["accent6"];
+  normal.indigo = colors["accent7"];
+  normal.gray = colors["text-medium"];
+  normal.grey1 = colors["text-light"];
+  normal.grey2 = colors["text-medium"];
+  normal.grey3 = colors["text-dark"];
+  normal.text = colors["text-dark"];
+}
+
+export const getRandomColor = (family: ColorFamily): ColorString => {
   // $FlowFixMe: Object.values doesn't preserve the type :-/
-  const colors: Color[] = Object.values(family);
+  const colors: ColorString[] = Object.values(family);
   return colors[Math.floor(Math.random() * colors.length)];
 };
 
-type ColorScale = (input: number) => Color;
+type ColorScale = (input: number) => ColorString;
 
 export const getColorScale = (
   extent: [number, number],
@@ -92,3 +127,13 @@ export const getColorScale = (
     )
     .range(colors);
 };
+
+export const alpha = (color: ColorString, alpha: number): ColorString =>
+  Color(color)
+    .alpha(alpha)
+    .string();
+
+export const darken = (color: ColorString, factor: number): ColorString =>
+  Color(color)
+    .darken(factor)
+    .string();
diff --git a/frontend/src/metabase/lib/dataset.js b/frontend/src/metabase/lib/dataset.js
index 02e819c304123f3eef00b43b22a83ac45869e8e2..51ae58f0c5d83d763161a5f21c21109f6e13ba7d 100644
--- a/frontend/src/metabase/lib/dataset.js
+++ b/frontend/src/metabase/lib/dataset.js
@@ -1,6 +1,21 @@
+/* @flow */
+
 import _ from "underscore";
 
-import type { Value, Column, DatasetData } from "metabase/meta/types/Dataset";
+import type {
+  Value,
+  Column,
+  ColumnName,
+  DatasetData,
+} from "metabase/meta/types/Dataset";
+import type { Card } from "metabase/meta/types/Card";
+import type { ConcreteField } from "metabase/meta/types/Query";
+
+type ColumnSetting = {
+  name: ColumnName,
+  fieldRef?: ConcreteField,
+  enabled: boolean,
+};
 
 // Many aggregations result in [[null]] if there are no rows to aggregate after filters
 export const datasetContainsNoResults = (data: DatasetData): boolean =>
@@ -11,9 +26,109 @@ export const datasetContainsNoResults = (data: DatasetData): boolean =>
  */
 export const rangeForValue = (
   value: Value,
-  column: Column,
+  column: ?Column,
 ): ?[number, number] => {
-  if (column && column.binning_info && column.binning_info.bin_width) {
+  if (
+    typeof value === "number" &&
+    column &&
+    column.binning_info &&
+    column.binning_info.bin_width
+  ) {
     return [value, value + column.binning_info.bin_width];
   }
 };
+
+/**
+ * Returns a MBQL field reference (ConcreteField) for a given result dataset column
+ * @param  {Column} column Dataset result column
+ * @return {?ConcreteField} MBQL field reference
+ */
+export function fieldRefForColumn(column: Column): ?ConcreteField {
+  if (column.id != null) {
+    if (Array.isArray(column.id)) {
+      // $FlowFixMe: sometimes col.id is a field reference (e.x. nested queries), if so just return it
+      return column.id;
+    } else if (column.fk_field_id != null) {
+      return ["fk->", column.fk_field_id, column.id];
+    } else {
+      return ["field-id", column.id];
+    }
+  } else if (column["expression-name"] != null) {
+    return ["expression", column["expression-name"]];
+  } else {
+    return null;
+  }
+}
+
+/**
+ * Finds the column object from the dataset results for the given `table.columns` column setting
+ * @param  {Column[]} columns             Dataset results columns
+ * @param  {ColumnSetting} columnSetting  A "column setting" from the `table.columns` settings
+ * @return {?Column}                      A result column
+ */
+export function findColumnForColumnSetting(
+  columns: Column[],
+  columnSetting: ColumnSetting,
+): ?Column {
+  const index = findColumnIndexForColumnSetting(columns, columnSetting);
+  if (index >= 0) {
+    return columns[index];
+  } else {
+    return null;
+  }
+}
+
+export function findColumnIndexForColumnSetting(
+  columns: Column[],
+  columnSetting: ColumnSetting,
+): number {
+  const { fieldRef } = columnSetting;
+  // first try to find by fieldRef
+  if (fieldRef != null) {
+    const index = _.findIndex(columns, col =>
+      _.isEqual(fieldRef, fieldRefForColumn(col)),
+    );
+    if (index >= 0) {
+      return index;
+    }
+  }
+  // if that fails, find by column name
+  return _.findIndex(columns, col => col.name === columnSetting.name);
+}
+
+/**
+ * Synchronizes the "table.columns" visualization setting to the structured
+ * query's `fields`
+ * @param  {[type]} card Card to synchronize `fields`. Mutates value
+ * @param  {[type]} cols Columns in last run results
+ */
+export function syncQueryFields(card: Card, cols: Column[]): void {
+  if (
+    card.dataset_query.type === "query" &&
+    card.visualization_settings["table.columns"]
+  ) {
+    const visibleColumns = card.visualization_settings["table.columns"]
+      .filter(columnSetting => columnSetting.enabled)
+      .map(columnSetting => findColumnForColumnSetting(cols, columnSetting));
+    const fields = visibleColumns
+      .map(column => column && fieldRefForColumn(column))
+      .filter(field => field);
+    if (!_.isEqual(card.dataset_query.query.fields, fields)) {
+      console.log("fields actual", card.dataset_query.query.fields);
+      console.log("fields expected", fields);
+      card.dataset_query.query.fields = fields;
+    }
+  }
+}
+
+export function getExistingFields(card: Card, cols: Column[]): ConcreteField[] {
+  const query = card.dataset_query.query;
+  if (query.fields && query.fields > 0) {
+    return query.fields;
+  } else if (!query.aggregation && !query.breakout) {
+    // $FlowFixMe:
+    return cols.map(col => fieldRefForColumn(col)).filter(id => id != null);
+  } else {
+    return [];
+  }
+}
diff --git a/frontend/src/metabase/lib/entities.js b/frontend/src/metabase/lib/entities.js
index 125da1e7014cd65c964ea8ac0ba70ec893eb2e6d..c282cf436ea2ca8330323c47aa2fec0b4d147530 100644
--- a/frontend/src/metabase/lib/entities.js
+++ b/frontend/src/metabase/lib/entities.js
@@ -17,6 +17,8 @@ import { normalize, denormalize, schema } from "normalizr";
 import { getIn, dissocIn, merge } from "icepick";
 import _ from "underscore";
 
+import MetabaseAnalytics from "metabase/lib/analytics";
+
 // entity defintions export the following properties (`name`, and `api` or `path` are required)
 //
 // name: plural, like "questions" or "dashboards"
@@ -58,6 +60,9 @@ type EntityDefinition = {
   wrapEntity?: (object: EntityObject) => any,
   form?: any,
   actionShouldInvalidateLists?: (action: Action) => boolean,
+
+  // list of properties for this object which should be persisted
+  writableProperties?: string[],
 };
 
 type EntityObject = any;
@@ -136,6 +141,11 @@ export type Entity = {
 
   requestsReducer: Reducer,
   actionShouldInvalidateLists: (action: Action) => boolean,
+
+  writableProperties?: string[],
+  getAnalyticsMetadata?: () => any,
+
+  HACK_getObjectFromAction: (action: Action) => any,
 };
 
 export function createEntity(def: EntityDefinition): Entity {
@@ -169,12 +179,20 @@ export function createEntity(def: EntityDefinition): Entity {
   const getListStatePath = entityQuery =>
     ["entities", entity.name + "_list"].concat(getIdForQuery(entityQuery));
 
+  const getWritableProperties = object =>
+    entity.writableProperties != null
+      ? _.pick(object, "id", ...entity.writableProperties)
+      : object;
+
   // ACTION TYPES
   const CREATE_ACTION = `metabase/entities/${entity.name}/CREATE`;
   const FETCH_ACTION = `metabase/entities/${entity.name}/FETCH`;
   const UPDATE_ACTION = `metabase/entities/${entity.name}/UPDATE`;
   const DELETE_ACTION = `metabase/entities/${entity.name}/DELETE`;
   const FETCH_LIST_ACTION = `metabase/entities/${entity.name}/FETCH_LIST`;
+  const INVALIDATE_LISTS_ACTION = `metabase/entities/${
+    entity.name
+  }/INVALIDATE_LISTS_ACTION`;
 
   entity.actionTypes = {
     CREATE: CREATE_ACTION,
@@ -182,6 +200,7 @@ export function createEntity(def: EntityDefinition): Entity {
     UPDATE: UPDATE_ACTION,
     DELETE: DELETE_ACTION,
     FETCH_LIST: FETCH_LIST_ACTION,
+    INVALIDATE_LISTS_ACTION: INVALIDATE_LISTS_ACTION,
     ...(entity.actionTypes || {}),
   };
 
@@ -189,11 +208,12 @@ export function createEntity(def: EntityDefinition): Entity {
     create: createThunkAction(
       CREATE_ACTION,
       entityObject => async (dispatch, getState) => {
+        trackAction("create", entityObject, getState);
         const statePath = ["entities", entity.name, "create"];
         try {
           dispatch(setRequestState({ statePath, state: "LOADING" }));
           const result = normalize(
-            await entity.api.create(entityObject),
+            await entity.api.create(getWritableProperties(entityObject)),
             entity.schema,
           );
           dispatch(setRequestState({ statePath, state: "LOADED" }));
@@ -233,6 +253,7 @@ export function createEntity(def: EntityDefinition): Entity {
         dispatch,
         getState,
       ) => {
+        trackAction("update", updatedObject, getState);
         // save the original object for undo
         const originalObject = entity.selectors.getObject(getState(), {
           entityId: entityObject.id,
@@ -248,7 +269,7 @@ export function createEntity(def: EntityDefinition): Entity {
         try {
           dispatch(setRequestState({ statePath, state: "LOADING" }));
           const result = normalize(
-            await entity.api.update(entityObject),
+            await entity.api.update(getWritableProperties(entityObject)),
             entity.schema,
           );
           dispatch(setRequestState({ statePath, state: "LOADED" }));
@@ -289,6 +310,7 @@ export function createEntity(def: EntityDefinition): Entity {
     delete: createThunkAction(
       DELETE_ACTION,
       entityObject => async (dispatch, getState) => {
+        trackAction("delete", getState);
         const statePath = [...getObjectStatePath(entityObject.id), "delete"];
         try {
           dispatch(setRequestState({ statePath, state: "LOADING" }));
@@ -336,6 +358,37 @@ export function createEntity(def: EntityDefinition): Entity {
     ...(def.actions || {}),
   };
 
+  // HACK: the above actions return the normalizr results
+  // (i.e. { entities, result }) rather than the loaded object(s), except
+  // for fetch and fetchList when the data is cached, in which case it returns
+  // the noralized object.
+  //
+  // This is a problem when we use the result of one of the actions as though
+  // though the action creator was an API client.
+  //
+  // For now just use this function until we figure out a cleaner way to do
+  // this. It will make it easy to find instances where we use the result of an
+  // action, and ensures a consistent result
+  //
+  // NOTE: this returns the normalized object(s), nested objects defined in
+  // the schema will be replaced with IDs.
+  //
+  // NOTE: A possible solution is to have an `updateEntities` action which is
+  // dispatched by the actions with the normalized data so that we can return
+  // the denormalized data from the action itself.
+  //
+  entity.HACK_getObjectFromAction = ({ payload }) => {
+    if (payload && "entities" in payload && "result" in payload) {
+      if (Array.isArray(payload.result)) {
+        return payload.result.map(id => payload.entities[entity.name][id]);
+      } else {
+        return payload.entities[entity.name][payload.result];
+      }
+    } else {
+      return payload;
+    }
+  };
+
   // SELECTORS
 
   const getEntities = state => state.entities;
@@ -470,7 +523,8 @@ export function createEntity(def: EntityDefinition): Entity {
     entity.actionShouldInvalidateLists = action =>
       action.type === CREATE_ACTION ||
       action.type === DELETE_ACTION ||
-      action.type === UPDATE_ACTION;
+      action.type === UPDATE_ACTION ||
+      action.type === INVALIDATE_LISTS_ACTION;
   }
 
   entity.requestsReducer = (state, action) => {
@@ -528,6 +582,20 @@ export function createEntity(def: EntityDefinition): Entity {
       new EntityWrapper(object, dispatch);
   }
 
+  function trackAction(action, object, getState) {
+    try {
+      MetabaseAnalytics.trackEvent(
+        "entity actions",
+        entity.name,
+        action,
+        entity.getAnalyticsMetadata &&
+          entity.getAnalyticsMetadata(action, object, getState),
+      );
+    } catch (e) {
+      console.warn("trackAction threw an error:", e);
+    }
+  }
+
   return entity;
 }
 
diff --git a/frontend/src/metabase/lib/expressions/index.js b/frontend/src/metabase/lib/expressions/index.js
index 73300e09dcce8c207be785e1bf1f8acefbcbf7b0..943846b3b0fe874d8bd52269ac6509448c719ba1 100644
--- a/frontend/src/metabase/lib/expressions/index.js
+++ b/frontend/src/metabase/lib/expressions/index.js
@@ -68,7 +68,7 @@ export function isMetric(expr) {
   return (
     Array.isArray(expr) &&
     expr.length === 2 &&
-    expr[0] === "METRIC" &&
+    expr[0] === "metric" &&
     typeof expr[1] === "number"
   );
 }
diff --git a/frontend/src/metabase/lib/expressions/parser.js b/frontend/src/metabase/lib/expressions/parser.js
index 2cd918ad3fe0d140095542e2a620388a032132d5..d747864cdfe1b52c3b8259b22b6d98905799f668 100644
--- a/frontend/src/metabase/lib/expressions/parser.js
+++ b/frontend/src/metabase/lib/expressions/parser.js
@@ -231,7 +231,7 @@ class ExpressionsParserMBQL extends ExpressionsParser {
     return arg == null ? [agg] : [agg, arg];
   }
   _metricReference(metricName, metricId) {
-    return ["METRIC", metricId];
+    return ["metric", metricId];
   }
   _fieldReference(fieldName, fieldId) {
     return ["field-id", fieldId];
diff --git a/frontend/src/metabase/lib/formatting.js b/frontend/src/metabase/lib/formatting.js
index aaecec0d4f322965cb202bcb2a18d273a9a8b3e5..97215f5fe77a661bcbe0ab9afd6a03e07d34ebcb 100644
--- a/frontend/src/metabase/lib/formatting.js
+++ b/frontend/src/metabase/lib/formatting.js
@@ -38,6 +38,8 @@ export type FormattingOptions = {
   comma?: boolean,
   compact?: boolean,
   round?: boolean,
+  // always format as the start value rather than the range, e.x. for bar histogram
+  noRange?: boolean,
 };
 
 const DEFAULT_NUMBER_OPTIONS: FormattingOptions = {
@@ -225,10 +227,10 @@ export function formatTimeWithUnit(
     case "day": // January 1, 2015
       return m.format(`${getMonthFormat(options)} D, YYYY`);
     case "week": // 1st - 2015
-      if (options.type === "tooltip") {
+      if (options.type === "tooltip" && !options.noRange) {
         // tooltip show range like "January 1 - 7, 2017"
         return formatTimeRangeWithUnit(value, unit, options);
-      } else if (options.type === "cell") {
+      } else if (options.type === "cell" && !options.noRange) {
         // table cells show range like "Jan 1, 2017 - Jan 7, 2017"
         return formatTimeRangeWithUnit(value, unit, options);
       } else if (options.type === "axis") {
@@ -371,7 +373,7 @@ export function formatValue(value: Value, options: FormattingOptions = {}) {
   } else if (typeof value === "number") {
     const formatter = isCoordinate(column) ? formatCoordinate : formatNumber;
     const range = rangeForValue(value, options.column);
-    if (range) {
+    if (range && !options.noRange) {
       return formatRange(range, formatter, options);
     } else {
       return formatter(value, options);
@@ -467,7 +469,7 @@ export function assignUserColors(
     "bg-error",
     "bg-green",
     "bg-gold",
-    "bg-grey-2",
+    "bg-medium",
   ],
 ) {
   let assignments = {};
diff --git a/frontend/src/metabase/lib/groups.js b/frontend/src/metabase/lib/groups.js
index 1daa3403101358a4037d6e0ebbaa6b4eee42aa35..e9b510ca168b2b24dfd833f938d5f365ba6ffd05 100644
--- a/frontend/src/metabase/lib/groups.js
+++ b/frontend/src/metabase/lib/groups.js
@@ -1,3 +1,11 @@
+import { t } from "c-3po";
+
+const SPECIAL_GROUP_NAMES = new Map([
+  ["All Users", t`All Users`],
+  ["Administrators", t`Administrators`],
+  ["MetaBot", t`MetaBot`],
+]);
+
 export function isDefaultGroup(group) {
   return group.name === "All Users";
 }
@@ -21,5 +29,13 @@ export function canEditMembership(group) {
 export function getGroupColor(group) {
   return isAdminGroup(group)
     ? "text-purple"
-    : isDefaultGroup(group) ? "text-grey-4" : "text-brand";
+    : isDefaultGroup(group) ? "text-medium" : "text-brand";
+}
+
+export function getGroupNameLocalized(group) {
+  if (SPECIAL_GROUP_NAMES.has(group.name)) {
+    return SPECIAL_GROUP_NAMES.get(group.name);
+  } else {
+    return group.name;
+  }
 }
diff --git a/frontend/src/metabase/lib/i18n.js b/frontend/src/metabase/lib/i18n.js
index d5b6ee32fa636eb085692e6313ad4a9a0aa8af1a..3824c5308a4dcf49d0e97c49af80383075e64a5d 100644
--- a/frontend/src/metabase/lib/i18n.js
+++ b/frontend/src/metabase/lib/i18n.js
@@ -1,11 +1,16 @@
 import { addLocale, useLocale } from "c-3po";
-import { I18NApi } from "metabase/services";
 
-export async function loadLocalization(locale) {
-  // load and parse the locale
-  const translationsObject = await I18NApi.locale({ locale });
-  setLocalization(translationsObject);
-}
+// NOTE: loadLocalization not currently used, and we need to be sure to set the
+// initial localization before loading any files, so don't load metabase/services
+// just in case
+
+// import { I18NApi } from "metabase/services";
+//
+// export async function loadLocalization(locale) {
+//   // load and parse the locale
+//   const translationsObject = await I18NApi.locale({ locale });
+//   setLocalization(translationsObject);
+// }
 
 export function setLocalization(translationsObject) {
   const locale = translationsObject.headers.language;
@@ -27,3 +32,8 @@ function addMsgIds(translationsObject) {
     }
   }
 }
+
+// set the initial localization
+if (window.MetabaseLocalization) {
+  setLocalization(window.MetabaseLocalization);
+}
diff --git a/frontend/src/metabase/lib/query.js b/frontend/src/metabase/lib/query.js
index 04814a59d0d0299ae9a7780dfd725be0bb22d9a3..7ded4d94c50287cf00ff1acddba6f12ee5b1bc07 100644
--- a/frontend/src/metabase/lib/query.js
+++ b/frontend/src/metabase/lib/query.js
@@ -21,7 +21,7 @@ export const NEW_QUERY_TEMPLATES = {
     database: null,
     type: "query",
     query: {
-      source_table: null,
+      "source-table": null,
     },
   },
   native: {
@@ -41,7 +41,7 @@ export function createQuery(type = "query", databaseId, tableId) {
   }
 
   if (type === "query" && databaseId && tableId) {
-    dataset_query.query.source_table = tableId;
+    dataset_query.query["source-table"] = tableId;
   }
 
   return dataset_query;
@@ -91,7 +91,7 @@ const Query = {
   canRun(query, tableMetadata) {
     if (
       !query ||
-      query.source_table == null ||
+      query["source-table"] == null ||
       !Query.hasValidAggregation(query)
     ) {
       return false;
@@ -144,18 +144,18 @@ const Query = {
       _.all(filter, a => a != null),
     );
     if (filters.length > 0) {
-      query.filter = ["AND", ...filters];
+      query.filter = ["and", ...filters];
     } else {
       delete query.filter;
     }
 
-    if (query.order_by) {
-      query.order_by = query.order_by
+    if (query["order-by"]) {
+      query["order-by"] = query["order-by"]
         .map(s => {
-          let field = s[0];
+          const [direction, field] = s;
 
           // remove incomplete sorts
-          if (!Query.isValidField(field) || s[1] == null) {
+          if (!Query.isValidField(field) || direction == null) {
             return null;
           }
 
@@ -175,17 +175,17 @@ const Query = {
               Query.isSameField(b, field, false),
             );
             if (targetMatches.length > 0) {
-              // query processor expect the order_by clause to match the breakout's datetime-field unit or fk-> target,
+              // query processor expect the order-by clause to match the breakout's datetime-field unit or fk-> target,
               // so just replace it with the one that matches the target field
               // NOTE: if we have more than one breakout for the same target field this could match the wrong one
               if (targetMatches.length > 1) {
                 console.warn(
                   "Sort clause matches more than one breakout field",
-                  s[0],
+                  field,
                   targetMatches,
                 );
               }
-              return [targetMatches[0], s[1]];
+              return [direction, targetMatches[0]];
             }
           } else if (Query.isBareRows(query)) {
             return s;
@@ -196,8 +196,8 @@ const Query = {
         })
         .filter(s => s != null);
 
-      if (query.order_by.length === 0) {
-        delete query.order_by;
+      if (query["order-by"].length === 0) {
+        delete query["order-by"];
       }
     }
 
@@ -249,7 +249,7 @@ const Query = {
   },
 
   isSegmentFilter(filter) {
-    return Array.isArray(filter) && filter[0] === "SEGMENT";
+    return Array.isArray(filter) && filter[0] === "segment";
   },
 
   canAddLimitAndSort(query) {
@@ -671,8 +671,8 @@ const Query = {
   },
 
   getFilterDescription(tableMetadata, query, options) {
-    // getFilters returns list of filters without the implied "AND"
-    let filters = ["AND"].concat(Query.getFilters(query));
+    // getFilters returns list of filters without the implied "and"
+    let filters = ["and"].concat(Query.getFilters(query));
     if (filters && filters.length > 1) {
       return [
         t`Filtered by `,
@@ -682,12 +682,12 @@ const Query = {
   },
 
   getFilterClauseDescription(tableMetadata, filter, options) {
-    if (mbqlEq(filter[0], "AND") || mbqlEq(filter[0], "OR")) {
+    if (mbqlEq(filter[0], "and") || mbqlEq(filter[0], "or")) {
       let clauses = filter
         .slice(1)
         .map(f => Query.getFilterClauseDescription(tableMetadata, f, options));
       return conjunctList(clauses, filter[0].toLowerCase());
-    } else if (filter[0] === "SEGMENT") {
+    } else if (filter[0] === "segment") {
       let segment = _.findWhere(tableMetadata.segments, { id: filter[1] });
       let name = segment ? segment.name : "[Unknown Segment]";
       return options.jsx ? (
@@ -700,13 +700,17 @@ const Query = {
     }
   },
 
-  getOrderByDescription(tableMetadata, { order_by }, options) {
-    if (order_by && order_by.length > 0) {
+  getOrderByDescription(tableMetadata, query, options) {
+    const orderBy = query["order-by"];
+    if (orderBy && orderBy.length > 0) {
       return [
         t`Sorted by `,
         joinList(
-          order_by.map(
-            o => Query.getFieldName(tableMetadata, o[0], options) + " " + o[1],
+          orderBy.map(
+            ([direction, field]) =>
+              Query.getFieldName(tableMetadata, field, options) +
+              " " +
+              (direction === "asc" ? "ascending" : "descending"),
           ),
           " and ",
         ),
@@ -732,7 +736,7 @@ const Query = {
         "aggregation",
         "breakout",
         "filter",
-        "order_by",
+        "order-by",
         "limit",
       ],
       ...options,
@@ -743,7 +747,7 @@ const Query = {
       aggregation: Query.getAggregationDescription,
       breakout: Query.getBreakoutDescription,
       filter: Query.getFilterDescription,
-      order_by: Query.getOrderByDescription,
+      "order-by": Query.getOrderByDescription,
       limit: Query.getLimitDescription,
     };
 
@@ -927,7 +931,7 @@ export const AggregationClause = {
       aggregation &&
       aggregation.length > 0 &&
       aggregation[0] &&
-      aggregation[0] !== "METRIC"
+      aggregation[0] !== "metric"
     ) {
       return [aggregation[0], fieldId];
     } else {
diff --git a/frontend/src/metabase/lib/query/query.js b/frontend/src/metabase/lib/query/query.js
index ee356c62bb8fd641292e4ed17bf0015aef6f0d9e..b6d089666c434e06d3663acdddfa0ea53903e02c 100644
--- a/frontend/src/metabase/lib/query/query.js
+++ b/frontend/src/metabase/lib/query/query.js
@@ -14,6 +14,7 @@ import type {
   ExpressionClause,
   ExpressionName,
   Expression,
+  FieldsClause,
 } from "metabase/meta/types/Query";
 import type { TableMetadata } from "metabase/meta/types/Metadata";
 
@@ -84,15 +85,18 @@ export const canAddFilter = (query: SQ) => F.canAddFilter(query.filter);
 
 // ORDER_BY
 
-export const getOrderBys = (query: SQ) => O.getOrderBys(query.order_by);
-export const addOrderBy = (query: SQ, order_by: OrderBy) =>
-  setOrderByClause(query, O.addOrderBy(query.order_by, order_by));
-export const updateOrderBy = (query: SQ, index: number, order_by: OrderBy) =>
-  setOrderByClause(query, O.updateOrderBy(query.order_by, index, order_by));
+export const getOrderBys = (query: SQ) => O.getOrderBys(query["order-by"]);
+export const addOrderBy = (query: SQ, orderBy: OrderBy) =>
+  setOrderByClause(query, O.addOrderBy(query["order-by"], orderBy));
+export const updateOrderBy = (query: SQ, index: number, orderBy: OrderBy) =>
+  setOrderByClause(query, O.updateOrderBy(query["order-by"], index, orderBy));
 export const removeOrderBy = (query: SQ, index: number) =>
-  setOrderByClause(query, O.removeOrderBy(query.order_by, index));
+  setOrderByClause(query, O.removeOrderBy(query["order-by"], index));
 export const clearOrderBy = (query: SQ) =>
-  setOrderByClause(query, O.clearOrderBy(query.order_by));
+  setOrderByClause(query, O.clearOrderBy(query["order-by"]));
+
+// FIELD
+export const clearFields = (query: SQ) => setFieldsClause(query, null);
 
 // LIMIT
 
@@ -140,31 +144,37 @@ function setAggregationClause(
 ): SQ {
   let wasBareRows = A.isBareRows(query.aggregation);
   let isBareRows = A.isBareRows(aggregationClause);
-  // when switching to or from bare rows clear out any sorting clauses
+  // when switching to or from bare rows clear out any sorting and fields clauses
   if (isBareRows !== wasBareRows) {
-    clearOrderBy(query);
+    query = clearFields(query);
+    query = clearOrderBy(query);
   }
   // for bare rows we always clear out any dimensions because they don't make sense
   if (isBareRows) {
-    clearBreakouts(query);
+    query = clearBreakouts(query);
   }
   return setClause("aggregation", query, aggregationClause);
 }
 function setBreakoutClause(query: SQ, breakoutClause: ?BreakoutClause): SQ {
   let breakoutIds = B.getBreakouts(breakoutClause).filter(id => id != null);
   for (const [index, sort] of getOrderBys(query).entries()) {
-    let sortId = Query.getFieldTargetId(sort[0]);
+    let sortId = Query.getFieldTargetId(sort[1]);
     if (sortId != null && !_.contains(breakoutIds, sortId)) {
       query = removeOrderBy(query, index);
     }
   }
+  // clear fields when changing breakouts
+  query = clearFields(query);
   return setClause("breakout", query, breakoutClause);
 }
 function setFilterClause(query: SQ, filterClause: ?FilterClause): SQ {
   return setClause("filter", query, filterClause);
 }
 function setOrderByClause(query: SQ, orderByClause: ?OrderByClause): SQ {
-  return setClause("order_by", query, orderByClause);
+  return setClause("order-by", query, orderByClause);
+}
+function setFieldsClause(query: SQ, fieldsClause: ?FieldsClause): SQ {
+  return setClause("fields", query, fieldsClause);
 }
 function setLimitClause(query: SQ, limitClause: ?LimitClause): SQ {
   return setClause("limit", query, limitClause);
@@ -183,9 +193,11 @@ type FilterClauseName =
   | "filter"
   | "aggregation"
   | "breakout"
-  | "order_by"
+  | "order-by"
   | "limit"
-  | "expressions";
+  | "expressions"
+  | "fields";
+
 function setClause(clauseName: FilterClauseName, query: SQ, clause: ?any): SQ {
   query = { ...query };
   if (clause == null) {
diff --git a/frontend/src/metabase/lib/redux.js b/frontend/src/metabase/lib/redux.js
index 1840f4b6ab1eb7d7fd983e12b5c91acdb6b0dd1a..0ef70bb5691bf9c4a581b775b1ed1c6f346f0c33 100644
--- a/frontend/src/metabase/lib/redux.js
+++ b/frontend/src/metabase/lib/redux.js
@@ -8,6 +8,9 @@ import { setRequestState, clearRequestState } from "metabase/redux/requests";
 export { combineReducers } from "redux";
 export { handleActions, createAction } from "redux-actions";
 
+import { createSelectorCreator } from "reselect";
+import memoize from "lodash.memoize";
+
 // similar to createAction but accepts a (redux-thunk style) thunk and dispatches based on whether
 // the promise returned from the thunk resolves or rejects, similar to redux-promise
 export function createThunkAction(actionType, actionThunkCreator) {
@@ -192,3 +195,8 @@ export const formDomOnlyProps = ({
   defaultValue,
   ...domProps
 }) => domProps;
+
+export const createMemoizedSelector = createSelectorCreator(
+  memoize,
+  (...args) => JSON.stringify(args),
+);
diff --git a/frontend/src/metabase/lib/settings.js b/frontend/src/metabase/lib/settings.js
index b4ce8ba18b232ae7bb0afc4288a7573e4c8dd6e9..980d226d328c5188d68cf1d48a1604ce1373ee7b 100644
--- a/frontend/src/metabase/lib/settings.js
+++ b/frontend/src/metabase/lib/settings.js
@@ -80,7 +80,11 @@ const MetabaseSettings = {
     );
   },
 
-  passwordComplexity: function(capitalize) {
+  // returns a map that looks like {total: 6, digit: 1}
+  passwordComplexityRequirements: () => mb_settings.password_complexity,
+
+  // returns a description of password complexity requirements rather than the actual map of requirements
+  passwordComplexityDescription: function(capitalize) {
     const complexity = this.get("password_complexity");
 
     const clauseDescription = function(clause) {
diff --git a/frontend/src/metabase/lib/urls.js b/frontend/src/metabase/lib/urls.js
index bf64dfeabc7f0fff73983c480c036ce4195421b8..b99732ce9d7b2cea62c470eaa2442268ff2668db 100644
--- a/frontend/src/metabase/lib/urls.js
+++ b/frontend/src/metabase/lib/urls.js
@@ -11,8 +11,7 @@ export const newQuestion = () => "/question/new";
 export const newDashboard = collectionId =>
   `collection/${collectionId}/new_dashboard`;
 
-export const newPulse = collectionId =>
-  `/pulse/create?collectionId=${collectionId}`;
+export const newPulse = () => `/pulse/create`;
 
 export const newCollection = collectionId =>
   `collection/${collectionId}/new_collection`;
@@ -22,16 +21,8 @@ export function question(cardId, hash = "", query = "") {
     hash = serializeCardForUrl(hash);
   }
   if (query && typeof query === "object") {
-    query = Object.entries(query)
-      .map(kv => {
-        if (Array.isArray(kv[1])) {
-          return kv[1]
-            .map(v => `${encodeURIComponent(kv[0])}=${encodeURIComponent(v)}`)
-            .join("&");
-        } else {
-          return kv.map(encodeURIComponent).join("=");
-        }
-      })
+    query = extractQueryParams(query)
+      .map(kv => kv.map(encodeURIComponent).join("="))
       .join("&");
   }
   if (hash && hash.charAt(0) !== "#") {
@@ -46,13 +37,26 @@ export function question(cardId, hash = "", query = "") {
     : `/question${query}${hash}`;
 }
 
+export const extractQueryParams = (query: Object): Array => {
+  return [].concat(...Object.entries(query).map(flattenParam));
+};
+
+const flattenParam = ([key, value]) => {
+  if (value instanceof Array) {
+    return value.map(p => [key, p]);
+  }
+  return [[key, value]];
+};
+
 export function plainQuestion() {
   return Question.create({ metadata: null }).getUrl();
 }
 
 export function dashboard(dashboardId, { addCardWithId } = {}) {
   return addCardWithId != null
-    ? `/dashboard/${dashboardId}#add=${addCardWithId}`
+    ? // NOTE: no-color-literals rule thinks #add is a color, oops
+      // eslint-disable-next-line no-color-literals
+      `/dashboard/${dashboardId}#add=${addCardWithId}`
     : `/dashboard/${dashboardId}`;
 }
 
@@ -70,7 +74,7 @@ export function modelToUrl(model, modelId) {
 }
 
 export function pulse(pulseId) {
-  return `/pulse/#${pulseId}`;
+  return `/pulse/${pulseId}`;
 }
 
 export function pulseEdit(pulseId) {
@@ -95,6 +99,10 @@ export function collection(collectionId) {
   return `/collection/${collectionId || "root"}`;
 }
 
+export function collectionPermissions(collectionId) {
+  return `/collection/${collectionId || "root"}/permissions`;
+}
+
 export function label(label) {
   return `/questions/search?label=${encodeURIComponent(label.slug)}`;
 }
@@ -120,3 +128,7 @@ export function embedDashboard(token) {
 export function userCollection(userCollectionId) {
   return `/collection/${userCollectionId}/`;
 }
+
+export function accountSettings() {
+  return `/user/edit_current`;
+}
diff --git a/frontend/src/metabase/lib/utils.js b/frontend/src/metabase/lib/utils.js
index a1077a37bffb2bf78e8ad408cb34bf5163d9fa37..e69b7d9f502b74a9b769b23afd730a8f094d9450 100644
--- a/frontend/src/metabase/lib/utils.js
+++ b/frontend/src/metabase/lib/utils.js
@@ -1,5 +1,39 @@
 import generatePassword from "password-generator";
 import { t } from "c-3po";
+import MetabaseSettings from "metabase/lib/settings";
+
+const LAYOUT_PROPS = [
+  "m",
+  "ml",
+  "mr",
+  "mt",
+  "mb",
+  "mx",
+  "my",
+  "p",
+  "pl",
+  "pr",
+  "pt",
+  "pb",
+  "px",
+  "py",
+  "bg",
+  "color",
+  "hover",
+  "bordered",
+];
+
+export function stripLayoutProps(props) {
+  const newProps = props;
+
+  LAYOUT_PROPS.map(l => {
+    if (Object.keys(newProps).includes(l)) {
+      delete newProps[l];
+    }
+  });
+
+  return newProps;
+}
 
 function s4() {
   return Math.floor((1 + Math.random()) * 0x10000)
@@ -9,20 +43,22 @@ function s4() {
 
 // provides functions for building urls to things we care about
 let MetabaseUtils = {
-  generatePassword: function(length, complexity) {
-    const len = length || 14;
-
-    if (!complexity) {
-      return generatePassword(len, false);
-    } else {
-      let password = "";
-      let tries = 0;
-      while (!isStrongEnough(password) && tries < 100) {
-        password = generatePassword(len, false, /[\w\d\?\-]/);
-        tries++;
-      }
-      return password;
+  // generate a password that satisfies `complexity` requirements, by default the ones that come back in the
+  // `password_complexity` Setting; must be a map like {total: 6, number: 1}
+  generatePassword: function(complexity) {
+    complexity =
+      complexity || MetabaseSettings.passwordComplexityRequirements() || {};
+    // generated password must be at least `complexity.total`, but can be longer
+    // so hard code a minimum of 14
+    const len = Math.max(complexity.total || 0, 14);
+
+    let password = "";
+    let tries = 0;
+    while (!isStrongEnough(password) && tries < 100) {
+      password = generatePassword(len, false, /[\w\d\?\-]/);
+      tries++;
     }
+    return password;
 
     function isStrongEnough(password) {
       let uc = password.match(/([A-Z])/g);
diff --git a/frontend/src/metabase/meta/Card.js b/frontend/src/metabase/meta/Card.js
index 944183a9c13d52ee8d974b14341363087323d543..39ea5eb32ba31dbb33caad7b0e117a6328f74124 100644
--- a/frontend/src/metabase/meta/Card.js
+++ b/frontend/src/metabase/meta/Card.js
@@ -43,7 +43,7 @@ export const STRUCTURED_QUERY_TEMPLATE: StructuredDatasetQuery = {
   type: "query",
   database: null,
   query: {
-    source_table: null,
+    "source-table": null,
     aggregation: undefined,
     breakout: undefined,
     filter: undefined,
@@ -55,7 +55,7 @@ export const NATIVE_QUERY_TEMPLATE: NativeDatasetQuery = {
   database: null,
   native: {
     query: "",
-    template_tags: {},
+    "template-tags": {},
   },
 };
 
@@ -72,7 +72,7 @@ export function canRun(card: Card): boolean {
     const query = getQuery(card);
     return (
       query != null &&
-      query.source_table != undefined &&
+      query["source-table"] != undefined &&
       Query.hasValidAggregation(query)
     );
   } else if (card.dataset_query.type === "native") {
@@ -124,8 +124,8 @@ export function getTableMetadata(
   metadata: Metadata,
 ): ?TableMetadata {
   const query = getQuery(card);
-  if (query && query.source_table != null) {
-    return metadata.tables[query.source_table] || null;
+  if (query && query["source-table"] != null) {
+    return metadata.tables[query["source-table"]] || null;
   }
   return null;
 }
@@ -134,8 +134,8 @@ export function getTemplateTags(card: ?Card): Array<TemplateTag> {
   return card &&
     card.dataset_query &&
     card.dataset_query.type === "native" &&
-    card.dataset_query.native.template_tags
-    ? Object.values(card.dataset_query.native.template_tags)
+    card.dataset_query.native["template-tags"]
+    ? Object.values(card.dataset_query.native["template-tags"])
     : [];
 }
 
diff --git a/frontend/src/metabase/meta/Dashboard.js b/frontend/src/metabase/meta/Dashboard.js
index fe301310e7b0e406e146fcd2563ccc717ab8c7f8..6a84ab5a58c474e6b8f010fa643999f99c0c99e3 100644
--- a/frontend/src/metabase/meta/Dashboard.js
+++ b/frontend/src/metabase/meta/Dashboard.js
@@ -176,8 +176,8 @@ export function getCardDimensions(
 ): Array<Dimension> {
   if (card.dataset_query.type === "query") {
     const table =
-      card.dataset_query.query.source_table != null
-        ? metadata.tables[card.dataset_query.query.source_table]
+      card.dataset_query.query["source-table"] != null
+        ? metadata.tables[card.dataset_query.query["source-table"]]
         : null;
     if (table) {
       return getTableDimensions(table, 1, filter);
@@ -251,7 +251,7 @@ export function getCardVariables(
     for (const tag of getTemplateTags(card)) {
       if (!filter || filter(tag)) {
         variables.push({
-          name: tag.display_name || tag.name,
+          name: tag["display-name"] || tag.name,
           type: tag.type,
           target: ["template-tag", tag.name],
         });
diff --git a/frontend/src/metabase/meta/Parameter.js b/frontend/src/metabase/meta/Parameter.js
index f28d264f4cf14c0f9d7ebc7dd019b4931776f32a..29bc2779d7bf0d2334d44dfde588e27f50435235 100644
--- a/frontend/src/metabase/meta/Parameter.js
+++ b/frontend/src/metabase/meta/Parameter.js
@@ -29,17 +29,19 @@ import { isNumericBaseType } from "metabase/lib/schema_metadata";
 export function getTemplateTagParameters(tags: TemplateTag[]): Parameter[] {
   return tags
     .filter(
-      tag => tag.type != null && (tag.widget_type || tag.type !== "dimension"),
+      tag =>
+        tag.type != null && (tag["widget-type"] || tag.type !== "dimension"),
     )
     .map(tag => ({
       id: tag.id,
       type:
-        tag.widget_type || (tag.type === "date" ? "date/single" : "category"),
+        tag["widget-type"] ||
+        (tag.type === "date" ? "date/single" : "category"),
       target:
         tag.type === "dimension"
           ? ["dimension", ["template-tag", tag.name]]
           : ["variable", ["template-tag", tag.name]],
-      name: tag.display_name,
+      name: tag["display-name"],
       slug: tag.name,
       default: tag.default,
     }));
@@ -68,7 +70,7 @@ export function getParameterTargetFieldId(
     if (Array.isArray(dimension) && mbqlEq(dimension[0], "template-tag")) {
       if (datasetQuery.type === "native") {
         let templateTag =
-          datasetQuery.native.template_tags[String(dimension[1])];
+          datasetQuery.native["template-tags"][String(dimension[1])];
         if (templateTag && templateTag.type === "dimension") {
           return Q.getFieldTargetId(templateTag.dimension);
         }
diff --git a/frontend/src/metabase/meta/types/Dashboard.js b/frontend/src/metabase/meta/types/Dashboard.js
index e48cd9358eacbdb649651ce3fe96ac8b49a2e769..d09bed096de8e43249c40c052762c6b5c3de3943 100644
--- a/frontend/src/metabase/meta/types/Dashboard.js
+++ b/frontend/src/metabase/meta/types/Dashboard.js
@@ -18,6 +18,7 @@ export type Dashboard = {
   show_in_getting_started?: boolean,
   // incomplete
   parameters: Array<Parameter>,
+  collection_id: ?number,
 };
 
 // TODO Atte Keinänen 4/5/16: After upgrading Flow, use spread operator `...Dashboard`
@@ -28,6 +29,7 @@ export type DashboardWithCards = {
   ordered_cards: Array<DashCard>,
   // incomplete
   parameters: Array<Parameter>,
+  collection_id: ?number,
 };
 
 export type DashCardId = number;
diff --git a/frontend/src/metabase/meta/types/Dataset.js b/frontend/src/metabase/meta/types/Dataset.js
index 48c73d4d326c0a7a6d587b6d6aba858c04c02f10..dc09def520d990d743be807b4ca4a288f71a9db9 100644
--- a/frontend/src/metabase/meta/types/Dataset.js
+++ b/frontend/src/metabase/meta/types/Dataset.js
@@ -21,6 +21,8 @@ export type Column = {
   source?: "fields" | "aggregation" | "breakout",
   unit?: DatetimeUnit,
   binning_info?: BinningInfo,
+  fk_field_id?: FieldId,
+  "expression-name"?: any,
 };
 
 export type Value = string | number | ISO8601Time | boolean | null | {};
diff --git a/frontend/src/metabase/meta/types/Field.js b/frontend/src/metabase/meta/types/Field.js
index 48e48f5046575645b53fc3d206252d24a3da3906..fad4559c934cd3f93e267c64b6839fbeb86925cc 100644
--- a/frontend/src/metabase/meta/types/Field.js
+++ b/frontend/src/metabase/meta/types/Field.js
@@ -29,8 +29,6 @@ export type Field = {
   position: number,
   parent_id: ?FieldId,
 
-  // raw_column_id:   number // unused?
-
   table_id: TableId,
 
   fk_target_field_id: ?FieldId,
diff --git a/frontend/src/metabase/meta/types/Query.js b/frontend/src/metabase/meta/types/Query.js
index abf0897ab6d30b3f6e480b67f203f64e25b4c517..222c126907116f549167916686a613c77c0365b0 100644
--- a/frontend/src/metabase/meta/types/Query.js
+++ b/frontend/src/metabase/meta/types/Query.js
@@ -54,10 +54,10 @@ export type TemplateTagType = "text" | "number" | "date" | "dimension";
 export type TemplateTag = {
   id: TemplateTagId,
   name: TemplateTagName,
-  display_name: string,
+  "display-name": string,
   type: TemplateTagType,
   dimension?: LocalFieldReference,
-  widget_type?: ParameterType,
+  "widget-type"?: ParameterType,
   required?: boolean,
   default?: string,
 };
@@ -66,15 +66,15 @@ export type TemplateTags = { [key: TemplateTagName]: TemplateTag };
 
 export type NativeQuery = {
   query: string,
-  template_tags: TemplateTags,
+  "template-tags": TemplateTags,
 };
 
 export type StructuredQuery = {
-  source_table: ?TableId,
+  "source-table": ?TableId,
   aggregation?: AggregationClause,
   breakout?: BreakoutClause,
   filter?: FilterClause,
-  order_by?: OrderByClause,
+  "order-by"?: OrderByClause,
   limit?: LimitClause,
   expressions?: ExpressionClause,
   fields?: FieldsClause,
@@ -117,7 +117,7 @@ type MinAgg = ["min", ConcreteField];
 type MaxAgg = ["max", ConcreteField];
 
 // NOTE: currently the backend expects METRIC to be uppercase
-type MetricAgg = ["METRIC", MetricId];
+type MetricAgg = ["metric", MetricId];
 
 export type BreakoutClause = Array<Breakout>;
 export type Breakout = ConcreteField;
@@ -204,10 +204,10 @@ export type TimeIntervalFilterOptions = {
 export type FilterOptions = StringFilterOptions | TimeIntervalFilterOptions;
 
 // NOTE: currently the backend expects SEGMENT to be uppercase
-export type SegmentFilter = ["SEGMENT", SegmentId];
+export type SegmentFilter = ["segment", SegmentId];
 
 export type OrderByClause = Array<OrderBy>;
-export type OrderBy = [Field, "descending" | "ascending"];
+export type OrderBy = ["asc" | "desc", Field];
 
 export type LimitClause = number;
 
@@ -271,4 +271,4 @@ export type Expression = [
 export type ExpressionOperator = "+" | "-" | "*" | "/";
 export type ExpressionOperand = ConcreteField | NumericLiteral | Expression;
 
-export type FieldsClause = Field[];
+export type FieldsClause = ConcreteField[];
diff --git a/frontend/src/metabase/meta/types/Table.js b/frontend/src/metabase/meta/types/Table.js
index 76d337b964e105edf9afac12a30cfb42f2b7b623..97846c5b9a8db2b04cc6c14c11fd1cfa1083f65c 100644
--- a/frontend/src/metabase/meta/types/Table.js
+++ b/frontend/src/metabase/meta/types/Table.js
@@ -25,7 +25,6 @@ export type Table = {
 
   // entity_name:          null // unused?
   // entity_type:          null // unused?
-  // raw_table_id:         number, // unused?
 
   fields: Field[],
   segments: Segment[],
diff --git a/frontend/src/metabase/meta/types/Visualization.js b/frontend/src/metabase/meta/types/Visualization.js
index 610b49564fb7928d264c7e39b9da9002eed336b0..d01c82bfdfc96ae101fde2996eba554991688dba 100644
--- a/frontend/src/metabase/meta/types/Visualization.js
+++ b/frontend/src/metabase/meta/types/Visualization.js
@@ -81,9 +81,13 @@ export type VisualizationProps = {
     height: number,
   },
 
+  width: number,
+  height: number,
+
   showTitle: boolean,
   isDashboard: boolean,
   isEditing: boolean,
+  isSettings: boolean,
   actionButtons: Node,
 
   onRender: ({
diff --git a/frontend/src/metabase/nav/components/ProfileLink.jsx b/frontend/src/metabase/nav/components/ProfileLink.jsx
index 951875fab9a63c1ce4783b1d92fef0444f57db55..40e29bff3beaf3d930eecb220d2a932749392415 100644
--- a/frontend/src/metabase/nav/components/ProfileLink.jsx
+++ b/frontend/src/metabase/nav/components/ProfileLink.jsx
@@ -1,196 +1,105 @@
 import React, { Component } from "react";
 import PropTypes from "prop-types";
-import { Link } from "react-router";
+import { Box } from "grid-styled";
 
-import OnClickOutsideWrapper from "metabase/components/OnClickOutsideWrapper";
 import { t } from "c-3po";
-import cx from "classnames";
 import _ from "underscore";
 import { capitalize } from "metabase/lib/formatting";
 
 import MetabaseSettings from "metabase/lib/settings";
-import Modal from "metabase/components/Modal.jsx";
-import Logs from "metabase/components/Logs.jsx";
+import * as Urls from "metabase/lib/urls";
+import Modal from "metabase/components/Modal";
+import Logs from "metabase/components/Logs";
 
-import UserAvatar from "metabase/components/UserAvatar.jsx";
-import Icon from "metabase/components/Icon.jsx";
-import LogoIcon from "metabase/components/LogoIcon.jsx";
+import LogoIcon from "metabase/components/LogoIcon";
+import EntityMenu from "metabase/components/EntityMenu";
 
+// generate the proper set of list items for the current user
+// based on whether they're an admin or not
 export default class ProfileLink extends Component {
-  constructor(props, context) {
-    super(props, context);
-
-    this.state = {
-      dropdownOpen: false,
-      modalOpen: null,
-    };
-
-    _.bindAll(
-      this,
-      "toggleDropdown",
-      "closeDropdown",
-      "openModal",
-      "closeModal",
-    );
-  }
+  state = {
+    dropdownOpen: false,
+  };
 
   static propTypes = {
     user: PropTypes.object.isRequired,
     context: PropTypes.string.isRequired,
   };
 
-  toggleDropdown() {
-    this.setState({ dropdownOpen: !this.state.dropdownOpen });
-  }
-
-  closeDropdown() {
-    this.setState({ dropdownOpen: false });
-  }
-
-  openModal(modalName) {
+  openModal = modalName => {
     this.setState({ dropdownOpen: false, modalOpen: modalName });
-  }
+  };
 
-  closeModal() {
+  closeModal = () => {
     this.setState({ modalOpen: null });
-  }
+  };
+
+  generateOptionsForUser = () => {
+    const { tag } = MetabaseSettings.get("version");
+    const admin = this.props.user.is_superuser;
+    const adminContext = this.props.context === "admin";
+    return [
+      {
+        title: t`Account settings`,
+        icon: null,
+        link: Urls.accountSettings(),
+        event: `Navbar;Profile Dropdown;Edit Profile`,
+      },
+      ...(admin && [
+        {
+          title: adminContext ? t`Exit admin` : t`Admin`,
+          icon: null,
+          link: adminContext ? "/" : "/admin",
+          event: `Navbar;Profile Dropdown;${
+            adminContext ? "Exit Admin" : "Enter Admin"
+          }`,
+        },
+      ]),
+      ...(admin && [
+        {
+          title: t`Logs`,
+          icon: null,
+          action: () => this.openModal("logs"),
+          event: `Navbar;Profile Dropdown;Debugging ${tag}`,
+        },
+      ]),
+      {
+        title: t`Help`,
+        icon: null,
+        // HACK - for some reason if you use // react router treats the link
+        // as a non local route
+        link: `//metabase.com/docs/${tag}`,
+        externalLink: true,
+        event: `Navbar;Profile Dropdown;About ${tag}`,
+      },
+      {
+        title: t`About Metabase`,
+        icon: null,
+        action: () => this.openModal("about"),
+        event: `Navbar;Profile Dropdown;About ${tag}`,
+      },
+      {
+        title: t`Sign out`,
+        icon: null,
+        link: "auth/logout",
+        event: `Navbar;Profile Dropdown;Logout`,
+      },
+    ];
+  };
 
   render() {
-    const { user, context } = this.props;
-    const { modalOpen, dropdownOpen } = this.state;
+    const { modalOpen } = this.state;
     const { tag, date, ...versionExtra } = MetabaseSettings.get("version");
-
-    let dropDownClasses = cx({
-      NavDropdown: true,
-      "inline-block": true,
-      "cursor-pointer": true,
-      open: dropdownOpen,
-    });
-
     return (
-      <div className={dropDownClasses}>
-        <a
-          data-metabase-event={"Navbar;Profile Dropdown;Toggle"}
-          className="NavDropdown-button NavItem flex align-center p2 transition-background"
-          onClick={this.toggleDropdown}
-        >
-          <div className="NavDropdown-button-layer">
-            <div className="flex align-center">
-              <UserAvatar user={user} />
-              <Icon
-                name="chevrondown"
-                className="Dropdown-chevron ml1"
-                size={8}
-              />
-            </div>
-          </div>
-        </a>
-
-        {dropdownOpen ? (
-          <OnClickOutsideWrapper handleDismissal={this.closeDropdown}>
-            <div className="NavDropdown-content right">
-              <ul className="NavDropdown-content-layer">
-                {!user.google_auth && !user.ldap_auth ? (
-                  <li>
-                    <Link
-                      to="/user/edit_current"
-                      data-metabase-event={
-                        "Navbar;Profile Dropdown;Edit Profile"
-                      }
-                      onClick={this.closeDropdown}
-                      className="Dropdown-item block text-white no-decoration"
-                    >
-                      {t`Account Settings`}
-                    </Link>
-                  </li>
-                ) : null}
-
-                {user.is_superuser && context !== "admin" ? (
-                  <li>
-                    <Link
-                      to="/admin"
-                      data-metabase-event={
-                        "Navbar;Profile Dropdown;Enter Admin"
-                      }
-                      onClick={this.closeDropdown}
-                      className="Dropdown-item block text-white no-decoration"
-                    >
-                      {t`Admin Panel`}
-                    </Link>
-                  </li>
-                ) : null}
-
-                {user.is_superuser && context === "admin" ? (
-                  <li>
-                    <Link
-                      to="/"
-                      data-metabase-event={"Navbar;Profile Dropdown;Exit Admin"}
-                      onClick={this.closeDropdown}
-                      className="Dropdown-item block text-white no-decoration"
-                    >
-                      {t`Exit Admin`}
-                    </Link>
-                  </li>
-                ) : null}
-
-                <li>
-                  <a
-                    data-metabase-event={"Navbar;Profile Dropdown;Help " + tag}
-                    className="Dropdown-item block text-white no-decoration"
-                    href={"http://www.metabase.com/docs/" + tag}
-                    target="_blank"
-                  >
-                    {t`Help`}
-                  </a>
-                </li>
-
-                {user.is_superuser && (
-                  <li>
-                    <a
-                      data-metabase-event={
-                        "Navbar;Profile Dropdown;Debugging " + tag
-                      }
-                      onClick={this.openModal.bind(this, "logs")}
-                      className="Dropdown-item block text-white no-decoration"
-                    >
-                      {t`Logs`}
-                    </a>
-                  </li>
-                )}
-
-                <li>
-                  <a
-                    data-metabase-event={"Navbar;Profile Dropdown;About " + tag}
-                    onClick={this.openModal.bind(this, "about")}
-                    className="Dropdown-item block text-white no-decoration"
-                  >
-                    {t`About Metabase`}
-                  </a>
-                </li>
-
-                <li className="border-top border-light">
-                  <Link
-                    to="/auth/logout"
-                    data-metabase-event={"Navbar;Profile Dropdown;Logout"}
-                    className="Dropdown-item block text-white no-decoration"
-                  >
-                    {t`Sign out`}
-                  </Link>
-                </li>
-              </ul>
-            </div>
-          </OnClickOutsideWrapper>
-        ) : null}
-
+      <Box>
+        <EntityMenu
+          tooltip={t`Settings`}
+          items={this.generateOptionsForUser()}
+          triggerIcon="gear"
+        />
         {modalOpen === "about" ? (
           <Modal small onClose={this.closeModal}>
             <div className="px4 pt4 pb2 text-centered relative">
-              <span
-                className="absolute top right p4 text-normal text-grey-3 cursor-pointer"
-                onClick={this.closeModal}
-              >
-                <Icon name={"close"} size={16} />
-              </span>
               <div className="text-brand pb2">
                 <LogoIcon width={48} height={48} />
               </div>
@@ -201,13 +110,13 @@ export default class ProfileLink extends Component {
                 <h3 className="text-dark mb1">
                   {t`You're on version`} {tag}
                 </h3>
-                <p className="text-grey-3 text-bold">
+                <p className="text-medium text-bold">
                   {t`Built on`} {date}
                 </p>
                 {!/^v\d+\.\d+\.\d+$/.test(tag) && (
                   <div>
                     {_.map(versionExtra, (value, key) => (
-                      <p key={key} className="text-grey-3 text-bold">
+                      <p key={key} className="text-medium text-bold">
                         {capitalize(key)}: {value}
                       </p>
                     ))}
@@ -217,7 +126,7 @@ export default class ProfileLink extends Component {
             </div>
             <div
               style={{ borderWidth: "2px" }}
-              className="p2 h5 text-centered text-grey-3 border-top"
+              className="p2 h5 text-centered text-medium border-top"
             >
               <span className="block">
                 <span className="text-bold">Metabase</span>{" "}
@@ -231,7 +140,7 @@ export default class ProfileLink extends Component {
             <Logs onClose={this.closeModal} />
           </Modal>
         ) : null}
-      </div>
+      </Box>
     );
   }
 }
diff --git a/frontend/src/metabase/nav/containers/Navbar.jsx b/frontend/src/metabase/nav/containers/Navbar.jsx
index 270d631362fc4109fd24b4239c34a2f73ffe23a0..85c75f935cadbd2f3ffbe8faa54302ecd995174f 100644
--- a/frontend/src/metabase/nav/containers/Navbar.jsx
+++ b/frontend/src/metabase/nav/containers/Navbar.jsx
@@ -5,20 +5,30 @@ import { t } from "c-3po";
 import { Box, Flex } from "grid-styled";
 import styled from "styled-components";
 import { space, width } from "styled-system";
+import colors from "metabase/lib/colors";
+import color from "color";
 
 import { connect } from "react-redux";
 import { push } from "react-router-redux";
 
+import * as Urls from "metabase/lib/urls";
+
 import Button from "metabase/components/Button.jsx";
-import Icon from "metabase/components/Icon.jsx";
+import Icon, { IconWrapper } from "metabase/components/Icon";
 import Link from "metabase/components/Link";
 import LogoIcon from "metabase/components/LogoIcon.jsx";
 import Tooltip from "metabase/components/Tooltip";
+import EntityMenu from "metabase/components/EntityMenu";
 import OnClickOutsideWrapper from "metabase/components/OnClickOutsideWrapper";
 
+import Modal from "metabase/components/Modal";
+
+import CreateDashboardModal from "metabase/components/CreateDashboardModal";
+
 import ProfileLink from "metabase/nav/components/ProfileLink.jsx";
 
 import { getPath, getContext, getUser } from "../selectors";
+import { entityListLoader } from "metabase/entities/containers/EntityListLoader";
 
 const mapStateToProps = (state, props) => ({
   path: getPath(state, props),
@@ -44,11 +54,23 @@ const AdminNavItem = ({ name, path, currentPath }) => (
   </li>
 );
 
+const DefaultSearchColor = color(colors.brand)
+  .lighten(0.07)
+  .string();
+const ActiveSearchColor = color(colors.brand)
+  .lighten(0.1)
+  .string();
+
 const SearchWrapper = Flex.extend`
-  ${width} border-radius: 6px;
+  ${width} background-color: ${props =>
+      props.active ? ActiveSearchColor : DefaultSearchColor};
+  border-radius: 6px;
   align-items: center;
-  border: 1px solid transparent;
+  color: white;
   transition: background 300ms ease-in;
+  &:hover {
+    background-color: ${ActiveSearchColor};
+  }
 `;
 
 const SearchInput = styled.input`
@@ -61,7 +83,7 @@ const SearchInput = styled.input`
     outline: none;
   }
   &::placeholder {
-    color: rgba(255, 255, 255, 0.85);
+    color: ${colors["text-white"]};
   }
 `;
 
@@ -89,30 +111,30 @@ class SearchBar extends React.Component {
   }
 
   render() {
+    const { active, searchText } = this.state;
     return (
       <OnClickOutsideWrapper
         handleDismissal={() => this.setState({ active: false })}
       >
         <SearchWrapper
-          className={cx("search-bar", {
-            "search-bar--active": this.state.active,
-          })}
           onClick={() => this.setState({ active: true })}
-          active={this.state.active}
+          active={active}
         >
           <Icon name="search" ml={2} />
           <SearchInput
             w={1}
-            p={2}
-            value={this.state.searchText}
-            placeholder="Search for anything..."
+            py={2}
+            pr={2}
+            pl={1}
+            value={searchText}
+            placeholder={t`Search` + "…"}
             onClick={() => this.setState({ active: true })}
             onChange={e => this.setState({ searchText: e.target.value })}
             onKeyPress={e => {
-              if (e.key === "Enter") {
+              if (e.key === "Enter" && (searchText || "").trim().length > 0) {
                 this.props.onChangeLocation({
                   pathname: "search",
-                  query: { q: this.state.searchText },
+                  query: { q: searchText },
                 });
               }
             }}
@@ -123,6 +145,13 @@ class SearchBar extends React.Component {
   }
 }
 
+const MODAL_NEW_DASHBOARD = "MODAL_NEW_DASHBOARD";
+
+@entityListLoader({
+  entityType: "databases",
+  // set this to false to prevent a potential spinner on the main nav
+  loadingAndErrorWrapper: false,
+})
 @connect(mapStateToProps, mapDispatchToProps)
 export default class Navbar extends Component {
   state = {
@@ -157,7 +186,9 @@ export default class Navbar extends Component {
 
   renderAdminNav() {
     return (
-      <nav className={cx("Nav AdminNav sm-py1")}>
+      // NOTE: DO NOT REMOVE `Nav` CLASS FOR NOW, USED BY MODALS, FULLSCREEN DASHBOARD, ETC
+      // TODO: hide nav using state in redux instead?
+      <nav className={"Nav AdminNav sm-py1"}>
         <div className="sm-pl4 flex align-center pr1">
           <div className="NavTitle flex align-center">
             <Icon name={"gear"} className="AdminGear" size={22} />
@@ -194,12 +225,15 @@ export default class Navbar extends Component {
 
           <ProfileLink {...this.props} />
         </div>
+        {this.renderModal()}
       </nav>
     );
   }
 
   renderEmptyNav() {
     return (
+      // NOTE: DO NOT REMOVE `Nav` CLASS FOR NOW, USED BY MODALS, FULLSCREEN DASHBOARD, ETC
+      // TODO: hide nav using state in redux instead?
       <nav className="Nav sm-py1 relative">
         <ul className="wrapper flex align-center">
           <li>
@@ -212,22 +246,33 @@ export default class Navbar extends Component {
             </Link>
           </li>
         </ul>
+        {this.renderModal()}
       </nav>
     );
   }
 
   renderMainNav() {
+    const hasDataAccess =
+      this.props.databases && this.props.databases.length > 0;
     return (
-      <Flex className="Nav relative bg-brand text-white z4" align="center">
-        <Box>
-          <Link
-            to="/"
-            data-metabase-event={"Navbar;Logo"}
-            className="LogoNavItem NavItem cursor-pointer relative z2 flex align-center transition-background justify-center"
-          >
-            <LogoIcon dark />
-          </Link>
-        </Box>
+      <Flex
+        // NOTE: DO NOT REMOVE `Nav` CLASS FOR NOW, USED BY MODALS, FULLSCREEN DASHBOARD, ETC
+        // TODO: hide nav using state in redux instead?
+        className="Nav relative bg-brand text-white z3"
+        align="center"
+        py={1}
+        pr={2}
+      >
+        <Link
+          to="/"
+          data-metabase-event={"Navbar;Logo"}
+          className="relative cursor-pointer z2 rounded flex justify-center transition-background"
+          p={1}
+          mx={1}
+          hover={{ backgroundColor: DefaultSearchColor }}
+        >
+          <LogoIcon dark />
+        </Link>
         <Flex
           className="absolute top left right bottom z1"
           px={4}
@@ -240,33 +285,77 @@ export default class Navbar extends Component {
             />
           </Box>
         </Flex>
-        <Flex align="center" ml="auto" className="z4">
-          <Link to="question/new" mx={1}>
-            <Button medium color="#509ee3">
-              New question
-            </Button>
-          </Link>
-          <Link to="collection/root" mx={1}>
-            <Box p={1} bg="#69ABE6" className="text-bold rounded">
-              Saved items
-            </Box>
-          </Link>
-          <Tooltip tooltip={t`Reference`}>
-            <Link to="reference" mx={1}>
-              <Icon name="reference" />
+        <Flex ml="auto" align="center" className="relative z2">
+          {hasDataAccess && (
+            <Link
+              to={Urls.newQuestion()}
+              mx={2}
+              className="hide sm-show"
+              data-metabase-event={`NavBar;New Question`}
+            >
+              <Button medium>{t`Ask a question`}</Button>
             </Link>
-          </Tooltip>
+          )}
+          <EntityMenu
+            tooltip={t`Create`}
+            className="hide sm-show"
+            triggerIcon="add"
+            items={[
+              {
+                title: t`New dashboard`,
+                icon: `dashboard`,
+                action: () => this.setModal(MODAL_NEW_DASHBOARD),
+                event: `NavBar;New Dashboard Click;`,
+              },
+              {
+                title: t`New pulse`,
+                icon: `pulse`,
+                link: Urls.newPulse(),
+                event: `NavBar;New Pulse Click;`,
+              },
+            ]}
+          />
+          {hasDataAccess && (
+            <Tooltip tooltip={t`Reference`}>
+              <Link to="reference" data-metabase-event={`NavBar;Reference`}>
+                <IconWrapper>
+                  <Icon name="reference" />
+                </IconWrapper>
+              </Link>
+            </Tooltip>
+          )}
           <Tooltip tooltip={t`Activity`}>
-            <Link to="activity" mx={1}>
-              <Icon name="alert" />
+            <Link to="activity" data-metabase-event={`NavBar;Activity`}>
+              <IconWrapper>
+                <Icon name="bell" />
+              </IconWrapper>
             </Link>
           </Tooltip>
           <ProfileLink {...this.props} />
         </Flex>
+        {this.renderModal()}
       </Flex>
     );
   }
 
+  renderModal() {
+    const { modal } = this.state;
+    if (modal) {
+      return (
+        <Modal onClose={() => this.setState({ modal: null })}>
+          {modal === MODAL_NEW_DASHBOARD ? (
+            <CreateDashboardModal
+              createDashboard={this.props.createDashboard}
+              onClose={() => this.setState({ modal: null })}
+            />
+          ) : null}
+        </Modal>
+      );
+    } else {
+      return null;
+    }
+  }
+
   render() {
     const { context, user } = this.props;
 
diff --git a/frontend/src/metabase/new_query/components/NewQueryOption.jsx b/frontend/src/metabase/new_query/components/NewQueryOption.jsx
index cb4c6f322e691e7a40b6333b9594139803f93bc8..0412426cffaedbb78c78d47861e45004056266e0 100644
--- a/frontend/src/metabase/new_query/components/NewQueryOption.jsx
+++ b/frontend/src/metabase/new_query/components/NewQueryOption.jsx
@@ -1,6 +1,7 @@
 import React, { Component } from "react";
 import cx from "classnames";
 import { Link } from "react-router";
+import colors from "metabase/lib/colors";
 
 export default class NewQueryOption extends Component {
   props: {
@@ -24,8 +25,8 @@ export default class NewQueryOption extends Component {
         style={{
           boxSizing: "border-box",
           boxShadow: hover
-            ? "0 3px 8px 0 rgba(220,220,220,0.50)"
-            : "0 1px 3px 0 rgba(220,220,220,0.50)",
+            ? `0 3px 8px 0 ${colors["text-light"]}`
+            : `0 1px 3px 0 ${colors["text-light"]}`,
           height: 340,
         }}
         onMouseOver={() => this.setState({ hover: true })}
@@ -49,7 +50,7 @@ export default class NewQueryOption extends Component {
           <h2 className={cx("transition-all", { "text-brand": hover })}>
             {title}
           </h2>
-          <p className={"text-grey-4 text-small"}>{description}</p>
+          <p className={"text-medium text-small"}>{description}</p>
         </div>
       </Link>
     );
diff --git a/frontend/src/metabase/new_query/containers/MetricSearch.jsx b/frontend/src/metabase/new_query/containers/MetricSearch.jsx
index 8d4e31797c18f19c3e1b9c7393f5a033dd147db1..5b774037089bfd5ca0ba299d3e775873e32feefb 100644
--- a/frontend/src/metabase/new_query/containers/MetricSearch.jsx
+++ b/frontend/src/metabase/new_query/containers/MetricSearch.jsx
@@ -9,6 +9,9 @@ import { t } from "c-3po";
 import type { Metric } from "metabase/meta/types/Metric";
 import type Metadata from "metabase-lib/lib/metadata/Metadata";
 import EmptyState from "metabase/components/EmptyState";
+import { Flex } from "grid-styled";
+
+import fitViewPort from "metabase/hoc/FitViewPort";
 
 import type { StructuredQuery } from "metabase/meta/types/Query";
 import { getCurrentQuery } from "metabase/new_query/selectors";
@@ -87,25 +90,30 @@ export default class MetricSearch extends Component {
               />
             );
           } else {
-            return (
-              <div className="mt2 flex-full flex align-center justify-center">
-                <EmptyState
-                  message={
-                    <span>
-                      {t`Defining common metrics for your team makes it even easier to ask questions`}
-                    </span>
-                  }
-                  image="app/img/metrics_illustration"
-                  action={t`How to create metrics`}
-                  link="http://www.metabase.com/docs/latest/administration-guide/07-segments-and-metrics.html"
-                  className="mt2"
-                  imageClassName="mln2"
-                />
-              </div>
-            );
+            return <MetricEmptyState />;
           }
         }}
       </LoadingAndErrorWrapper>
     );
   }
 }
+
+const MetricEmptyState = fitViewPort(({ fitClassNames }) => (
+  <Flex
+    mt={2}
+    align="center"
+    flexDirection="column"
+    justify="center"
+    className={fitClassNames}
+  >
+    <EmptyState
+      message={t`Defining common metrics for your team makes it even easier to ask questions`}
+      title={t`No metrics`}
+      image="app/img/metrics_illustration"
+      action={t`How to create metrics`}
+      link="http://www.metabase.com/docs/latest/administration-guide/07-segments-and-metrics.html"
+      className="mt2"
+      imageClassName="mln2"
+    />
+  </Flex>
+));
diff --git a/frontend/src/metabase/new_query/containers/NewQueryOptions.jsx b/frontend/src/metabase/new_query/containers/NewQueryOptions.jsx
index fa6f42e1d51ada5fef5fa5399c022d4e6dda7bbd..c199de7914b107cf824b763b805b0c16442ccfdb 100644
--- a/frontend/src/metabase/new_query/containers/NewQueryOptions.jsx
+++ b/frontend/src/metabase/new_query/containers/NewQueryOptions.jsx
@@ -127,49 +127,44 @@ export class NewQueryOptions extends Component {
     }
 
     return (
-      <div className="full-height flex">
-        <div className="wrapper wrapper--trim lg-wrapper--trim xl-wrapper--trim  px4 mt4 mb2 align-center">
-          <div
-            className="flex align-center justify-center"
-            style={{ minHeight: "100%" }}
-          >
-            <ol className="Grid Grid--guttersXl Grid--full sm-Grid--normal">
-              {showMetricOption && (
-                <li className="Grid-cell">
-                  <NewQueryOption
-                    image="app/img/questions_illustration"
-                    title={t`Metrics`}
-                    description={t`See data over time, as a map, or pivoted to help you understand trends or changes.`}
-                    to={metricSearchUrl}
-                  />
-                </li>
-              )}
+      <div className="full-height flex align-center justify-center">
+        <div className="wrapper wrapper--trim lg-wrapper--trim xl-wrapper--trim ">
+          <ol className="Grid Grid--guttersXl Grid--full sm-Grid--normal">
+            {showMetricOption && (
               <li className="Grid-cell">
-                {/*TODO: Move illustrations to the new location in file hierarchy. At the same time put an end to the equal-size-@2x ridicule. */}
                 <NewQueryOption
-                  image="app/img/query_builder_illustration"
-                  title={
-                    showCustomInsteadOfNewQuestionText
-                      ? t`Custom`
-                      : t`New question`
-                  }
-                  description={t`Use the simple question builder to see trends, lists of things, or to create your own metrics.`}
-                  width={180}
-                  to={this.getGuiQueryUrl}
+                  image="app/img/questions_illustration"
+                  title={t`Metrics`}
+                  description={t`See data over time, as a map, or pivoted to help you understand trends or changes.`}
+                  to={metricSearchUrl}
                 />
               </li>
-              {showSQLOption && (
-                <li className="Grid-cell">
-                  <NewQueryOption
-                    image="app/img/sql_illustration"
-                    title={t`Native query`}
-                    description={t`For more complicated questions, you can write your own SQL or native query.`}
-                    to={this.getNativeQueryUrl}
-                  />
-                </li>
-              )}
-            </ol>
-          </div>
+            )}
+            <li className="Grid-cell">
+              {/*TODO: Move illustrations to the new location in file hierarchy. At the same time put an end to the equal-size-@2x ridicule. */}
+              <NewQueryOption
+                image="app/img/query_builder_illustration"
+                title={
+                  showCustomInsteadOfNewQuestionText
+                    ? t`Custom`
+                    : t`New question`
+                }
+                description={t`Use the simple question builder to see trends, lists of things, or to create your own metrics.`}
+                width={180}
+                to={this.getGuiQueryUrl}
+              />
+            </li>
+            {showSQLOption && (
+              <li className="Grid-cell">
+                <NewQueryOption
+                  image="app/img/sql_illustration"
+                  title={t`Native query`}
+                  description={t`For more complicated questions, you can write your own SQL or native query.`}
+                  to={this.getNativeQueryUrl}
+                />
+              </li>
+            )}
+          </ol>
         </div>
       </div>
     );
diff --git a/frontend/src/metabase/parameters/components/ParameterWidget.css b/frontend/src/metabase/parameters/components/ParameterWidget.css
index 827be3d222cae0c717a78746240ce3f5ce5645ae..1c11ba5cf98f8c3ed6f2447fcf6981dd1fa0514f 100644
--- a/frontend/src/metabase/parameters/components/ParameterWidget.css
+++ b/frontend/src/metabase/parameters/components/ParameterWidget.css
@@ -1,7 +1,7 @@
 :local(.container) {
   composes: flex align-center from "style";
   transition: opacity 500ms linear;
-  border: 2px solid var(--grey-1);
+  border: 2px solid var(--color-border);
   margin-right: 0.85em;
   margin-bottom: 0.5em;
   padding: 0.25em 1em 0.25em 1em;
@@ -28,14 +28,14 @@
   font-weight: 600;
   min-height: 30px;
   min-width: 150px;
-  color: var(--grey-4);
+  color: var(--color-text-medium);
 }
 
 :local(.nameInput) {
   composes: flex align-center from "style";
   min-height: 30px;
   min-width: 150px;
-  color: var(--grey-4);
+  color: var(--color-text-medium);
   border: none;
   font-size: 1em;
   font-weight: 600;
@@ -58,8 +58,8 @@
 
 :local(.parameter.selected) {
   font-weight: bold;
-  color: var(--brand-color);
-  border-color: var(--brand-color);
+  color: var(--color-brand);
+  border-color: var(--color-brand);
 }
 
 :local(.parameter.noPopover) input {
@@ -78,25 +78,25 @@
 :local(.parameter.noPopover.selected) input {
   width: 127px;
   font-weight: bold;
-  color: var(--brand-color);
+  color: var(--color-brand);
 }
 
 :local(.parameter.noPopover) input:focus {
   outline: none;
-  color: var(--default-font-color);
+  color: var(--color-text-dark);
   width: 127px;
 }
 :local(.parameter.noPopover) input::-webkit-input-placeholder {
-  color: var(--grey-4);
+  color: var(--color-text-medium);
 }
 :local(.parameter.noPopover) input:-moz-placeholder {
-  color: var(--grey-4);
+  color: var(--color-text-medium);
 }
 :local(.parameter.noPopover) input::-moz-placeholder {
-  color: var(--grey-4);
+  color: var(--color-text-medium);
 }
 :local(.parameter.noPopover) input:-ms-input-placeholder {
-  color: var(--grey-4);
+  color: var(--color-text-medium);
 }
 
 :local(.input) {
@@ -111,7 +111,7 @@
   composes: mr1 from "style";
   font-size: 16px;
   font-weight: bold;
-  color: var(--grey-4);
+  color: var(--color-text-medium);
 }
 
 :local(.parameterButtons) {
@@ -126,11 +126,11 @@
 }
 
 :local(.editButton:hover) {
-  color: var(--brand-color);
+  color: var(--color-brand);
 }
 
 :local(.removeButton:hover) {
-  color: var(--error-color);
+  color: var(--color-error);
 }
 
 :local(.editNameIconContainer) {
diff --git a/frontend/src/metabase/parameters/components/ParameterWidget.jsx b/frontend/src/metabase/parameters/components/ParameterWidget.jsx
index 14ba7fcce10806b1e54b67fbd3e2c961e4fa5e45..c9d5337421909a158b1ea98e2603d5a48c873adc 100644
--- a/frontend/src/metabase/parameters/components/ParameterWidget.jsx
+++ b/frontend/src/metabase/parameters/components/ParameterWidget.jsx
@@ -65,6 +65,7 @@ export default class ParameterWidget extends Component {
       setValue,
       setDefaultValue,
       remove,
+      children,
     } = this.props;
 
     const isEditingDashboard = isEditing;
@@ -90,6 +91,7 @@ export default class ParameterWidget extends Component {
             parameter.name,
             isFullscreen,
           )}
+          {children}
         </FieldSet>
       );
     };
@@ -119,6 +121,7 @@ export default class ParameterWidget extends Component {
             }}
             autoFocus
           />
+          {children}
         </FieldSet>
       );
     };
@@ -153,6 +156,7 @@ export default class ParameterWidget extends Component {
             parameter.name,
             isFullscreen,
           )}
+          {children}
         </FieldSet>
       );
     };
@@ -177,6 +181,7 @@ export default class ParameterWidget extends Component {
               <span className="ml1">{t`Remove`}</span>
             </div>
           </div>
+          {children}
         </FieldSet>
       );
     };
@@ -189,7 +194,7 @@ export default class ParameterWidget extends Component {
       } else {
         return <span className="hide" />;
       }
-    } else if (!isEditingDashboard) {
+    } else if (!isEditingDashboard || !setEditingParameter) {
       return renderFieldInNormalMode();
     } else if (isEditingParameter) {
       if (this.state.isEditingName) {
diff --git a/frontend/src/metabase/parameters/components/Parameters.jsx b/frontend/src/metabase/parameters/components/Parameters.jsx
index 93e31ca306c139ae86d2bdb4615137cb455c788a..98910f52ee035dcdb0643d270cb3fe4e670699e8 100644
--- a/frontend/src/metabase/parameters/components/Parameters.jsx
+++ b/frontend/src/metabase/parameters/components/Parameters.jsx
@@ -2,7 +2,9 @@
 
 import React, { Component } from "react";
 
-import ParameterWidget from "./ParameterWidget.jsx";
+import StaticParameterWidget from "./ParameterWidget.jsx";
+import Icon from "metabase/components/Icon";
+import colors from "metabase/lib/colors";
 
 import querystring from "querystring";
 import cx from "classnames";
@@ -36,6 +38,7 @@ type Props = {
     parameterId: ParameterId,
     defaultValue: string,
   ) => void,
+  setParameterIndex?: (parameterId: ParameterId, index: number) => void,
   removeParameter?: (parameterId: ParameterId) => void,
   setEditingParameter?: (parameterId: ParameterId) => void,
 };
@@ -98,6 +101,19 @@ export default class Parameters extends Component {
     }
   }
 
+  handleSortEnd = ({
+    oldIndex,
+    newIndex,
+  }: {
+    oldIndex: number,
+    newIndex: number,
+  }) => {
+    const { parameters, setParameterIndex } = this.props;
+    if (setParameterIndex) {
+      setParameterIndex(parameters[oldIndex].id, newIndex);
+    }
+  };
+
   render() {
     const {
       className,
@@ -110,6 +126,7 @@ export default class Parameters extends Component {
       setParameterName,
       setParameterValue,
       setParameterDefaultValue,
+      setParameterIndex,
       removeParameter,
       vertical,
       commitImmediately,
@@ -117,19 +134,35 @@ export default class Parameters extends Component {
 
     const parameters = this._parametersWithValues();
 
+    let ParameterWidget;
+    let ParameterWidgetList;
+    if (isEditing) {
+      ParameterWidget = SortableParameterWidget;
+      ParameterWidgetList = SortableParameterWidgetList;
+    } else {
+      ParameterWidget = StaticParameterWidget;
+      ParameterWidgetList = StaticParameterWidgetList;
+    }
+
     return (
-      <div
+      <ParameterWidgetList
         className={cx(
           className,
           "flex align-end flex-wrap",
           vertical ? "flex-column" : "flex-row",
           { mt1: isQB },
         )}
+        axis="x"
+        distance={9}
+        onSortEnd={this.handleSortEnd}
       >
-        {parameters.map(parameter => (
+        {parameters.map((parameter, index) => (
           <ParameterWidget
-            className={vertical ? "mb2" : null}
             key={parameter.id}
+            index={index}
+            className={cx("relative hover-parent hover--visibility", {
+              mb2: vertical,
+            })}
             isEditing={isEditing}
             isFullscreen={isFullscreen}
             isNightMode={isNightMode}
@@ -150,9 +183,42 @@ export default class Parameters extends Component {
             }
             remove={removeParameter && (() => removeParameter(parameter.id))}
             commitImmediately={commitImmediately}
-          />
+          >
+            {/* show drag handle if editing and setParameterIndex provided */}
+            {isEditing && setParameterIndex ? (
+              <SortableParameterHandle />
+            ) : null}
+          </ParameterWidget>
         ))}
-      </div>
+      </ParameterWidgetList>
     );
   }
 }
+import {
+  SortableContainer,
+  SortableElement,
+  SortableHandle,
+} from "react-sortable-hoc";
+
+const StaticParameterWidgetList = ({ children, ...props }) => {
+  return <div {...props}>{children}</div>;
+};
+
+const SortableParameterHandle = SortableHandle(() => (
+  <div
+    className="absolute top bottom left flex layout-centered hover-child cursor-grab"
+    style={{
+      color: colors["border"],
+      // width should match the left padding of the ParameterWidget container class so that it's centered
+      width: "1em",
+      marginLeft: "1px",
+    }}
+  >
+    <Icon name="grabber2" size={12} />
+  </div>
+));
+
+const SortableParameterWidget = SortableElement(StaticParameterWidget);
+const SortableParameterWidgetList = SortableContainer(
+  StaticParameterWidgetList,
+);
diff --git a/frontend/src/metabase/public/components/EmbedFrame.css b/frontend/src/metabase/public/components/EmbedFrame.css
index 091bdc5e76904b0c28bf4231317ce323f9518417..35865ff14f20616d1ea76498f433f771f9b4b003 100644
--- a/frontend/src/metabase/public/components/EmbedFrame.css
+++ b/frontend/src/metabase/public/components/EmbedFrame.css
@@ -4,34 +4,34 @@
 
 .EmbedFrame-header,
 .EmbedFrame-footer {
-  color: var(--dark-color);
+  color: var(--color-text-dark);
   background-color: white;
 }
 
 .Theme--night.EmbedFrame {
-  background-color: rgb(54, 58, 61);
-  border: 1px solid rgb(46, 49, 52);
+  background-color: var(--color-bg-black);
+  border: 1px solid var(--color-accent2);
 }
 
 .Theme--night .EmbedFrame-header,
 .Theme--night .EmbedFrame-footer {
-  color: var(--night-mode-color);
-  background-color: rgb(54, 58, 61);
-  border-color: rgb(46, 49, 52);
+  color: color(var(--color-text-white) alpha(-14%));
+  background-color: var(--color-bg-black);
+  border-color: var(--color-accent2);
 }
 
 .Theme--night.EmbedFrame .fullscreen-night-text {
-  color: var(--night-mode-color);
+  color: color(var(--color-text-white) alpha(-14%));
   transition: color 1s linear;
 }
 
 .Theme--night.EmbedFrame svg text {
-  fill: var(--night-mode-color) !important;
+  fill: color(var(--color-text-white) alpha(-14%)) !important;
 }
 
 .Theme--night.EmbedFrame .DashCard .Card {
-  background-color: rgb(54, 58, 61);
-  border: 1px solid rgb(46, 49, 52);
+  background-color: var(--color-bg-black);
+  border: 1px solid var(--color-bg-dark);
 }
 
 .Theme--night.EmbedFrame .enable-dots-onhover .dc-tooltip circle.dot:hover,
diff --git a/frontend/src/metabase/public/components/EmbedFrame.jsx b/frontend/src/metabase/public/components/EmbedFrame.jsx
index 96a9b1c80890f2151a62eb538e9599503f0a41c6..93a4ae4587f2dabc8a34c99d8ab967e4a7debf82 100644
--- a/frontend/src/metabase/public/components/EmbedFrame.jsx
+++ b/frontend/src/metabase/public/components/EmbedFrame.jsx
@@ -143,7 +143,7 @@ export default class EmbedFrame extends Component {
               <LogoBadge dark={theme} />
             )}
             {actionButtons && (
-              <div className="flex-align-right text-grey-3">
+              <div className="flex-align-right text-medium">
                 {actionButtons}
               </div>
             )}
diff --git a/frontend/src/metabase/public/components/LogoBadge.jsx b/frontend/src/metabase/public/components/LogoBadge.jsx
index 2ccdd5dd7f83222086ac36c776da0723b01ae1b7..83eaa83e9455ba5115d86a275df97fbf698432de 100644
--- a/frontend/src/metabase/public/components/LogoBadge.jsx
+++ b/frontend/src/metabase/public/components/LogoBadge.jsx
@@ -17,7 +17,7 @@ const LogoBadge = ({ dark }: Props) => (
   >
     <LogoIcon size={28} dark={dark} />
     <span className="text-small">
-      <span className="ml1 text-grey-3">Powered by</span>{" "}
+      <span className="ml1 text-medium">Powered by</span>{" "}
       <span className={cx({ "text-brand": !dark }, { "text-white": dark })}>
         Metabase
       </span>
diff --git a/frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx b/frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx
index c1eb9c26002950991fae5be9607cc70ed02a4791..64ad10bfe22ee3c6b1a80d97abd473ff2964897b 100644
--- a/frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx
+++ b/frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx
@@ -1,15 +1,17 @@
 /* @flow */
 
 import React from "react";
+import { t } from "c-3po";
+import cx from "classnames";
 
 import Icon from "metabase/components/Icon";
 import Button from "metabase/components/Button";
 import Parameters from "metabase/parameters/components/Parameters";
 import Select, { Option } from "metabase/components/Select";
-import { t } from "c-3po";
-import DisplayOptionsPane from "./DisplayOptionsPane";
 
-import cx from "classnames";
+import colors from "metabase/lib/colors";
+
+import DisplayOptionsPane from "./DisplayOptionsPane";
 
 const getIconForParameter = parameter =>
   parameter.type === "category"
@@ -62,10 +64,7 @@ const AdvancedSettingsPane = ({
   onChangeParameterValue,
 }: Props) => (
   <div
-    className={cx(
-      className,
-      "p4 full-height flex flex-column bg-slate-extra-light",
-    )}
+    className={cx(className, "p4 full-height flex flex-column bg-light")}
     style={{ width: 400 }}
   >
     <Section title={t`Style`}>
@@ -88,7 +87,7 @@ const AdvancedSettingsPane = ({
             <Icon
               name={getIconForParameter(parameter)}
               className="mr2"
-              style={{ color: "#DFE8EA" }}
+              style={{ color: colors["text-light"] }}
             />
             <h3>{parameter.name}</h3>
             <Select
diff --git a/frontend/src/metabase/public/components/widgets/EmbedModalContent.jsx b/frontend/src/metabase/public/components/widgets/EmbedModalContent.jsx
index 0347fe3a44f919cb9734d88b7ee2e7027e0751eb..f3e3c90a25eb98ee8441a765f6657fff836908ec 100644
--- a/frontend/src/metabase/public/components/widgets/EmbedModalContent.jsx
+++ b/frontend/src/metabase/public/components/widgets/EmbedModalContent.jsx
@@ -4,6 +4,8 @@ import React, { Component } from "react";
 import { connect } from "react-redux";
 import { titleize } from "inflection";
 
+import { t } from "c-3po";
+
 import Icon from "metabase/components/Icon";
 
 import SharingPane from "./SharingPane";
@@ -14,6 +16,7 @@ import {
   getUnsignedPreviewUrl,
   getSignedToken,
 } from "metabase/public/lib/embed";
+import colors from "metabase/lib/colors";
 
 import {
   getSiteUrl,
@@ -176,7 +179,7 @@ export default class EmbedModalContent extends Component {
           style={{
             boxShadow:
               embedType === "application"
-                ? "0px 8px 15px -9px rgba(0,0,0,0.2)"
+                ? `0px 8px 15px -9px ${colors["text-dark"]}`
                 : undefined,
           }}
         >
@@ -187,7 +190,7 @@ export default class EmbedModalContent extends Component {
             />
           </h2>
           <Icon
-            className="text-grey-2 text-grey-4-hover cursor-pointer p2 ml-auto"
+            className="text-light text-medium-hover cursor-pointer p2 ml-auto"
             name="close"
             size={24}
             onClick={() => {
@@ -284,8 +287,8 @@ export const EmbedTitle = ({
   onClick: () => any,
 }) => (
   <a className="flex align-center" onClick={onClick}>
-    <span className="text-brand-hover">Sharing</span>
-    {type && <Icon name="chevronright" className="mx1 text-grey-3" />}
+    <span className="text-brand-hover">{t`Sharing`}</span>
+    {type && <Icon name="chevronright" className="mx1 text-medium" />}
     {type}
   </a>
 );
diff --git a/frontend/src/metabase/public/components/widgets/SharingPane.jsx b/frontend/src/metabase/public/components/widgets/SharingPane.jsx
index 420292342caddd045b416fc4aaef316c77184f7a..a588b9a312afa435c86ea3573a01ae8fb18b036c 100644
--- a/frontend/src/metabase/public/components/widgets/SharingPane.jsx
+++ b/frontend/src/metabase/public/components/widgets/SharingPane.jsx
@@ -126,7 +126,7 @@ export default class SharingPane extends Component {
                         "cursor-pointer text-brand-hover text-bold text-uppercase",
                         extension === this.state.extension
                           ? "text-brand"
-                          : "text-grey-2",
+                          : "text-light",
                       )}
                       onClick={() =>
                         this.setState({
diff --git a/frontend/src/metabase/public/containers/PublicQuestion.jsx b/frontend/src/metabase/public/containers/PublicQuestion.jsx
index fe5f9c9e2812006e955215113745b50e2d86fbf6..3d56a2c9c780aca462b7820198eff6e6da406c2f 100644
--- a/frontend/src/metabase/public/containers/PublicQuestion.jsx
+++ b/frontend/src/metabase/public/containers/PublicQuestion.jsx
@@ -55,7 +55,7 @@ const mapDispatchToProps = {
 };
 
 @connect(null, mapDispatchToProps)
-@ExplicitSize
+@ExplicitSize()
 export default class PublicQuestion extends Component {
   props: Props;
   state: State;
@@ -167,7 +167,7 @@ export default class PublicQuestion extends Component {
 
     const actionButtons = result && (
       <QueryDownloadWidget
-        className="m1 text-grey-4-hover"
+        className="m1 text-medium-hover"
         uuid={uuid}
         token={token}
         result={result}
diff --git a/frontend/src/metabase/pulse/actions.js b/frontend/src/metabase/pulse/actions.js
index 351dac69367919726a5cbaf8e14caf432c95b55a..db09a9ea7674836e174be9261e644da3e77ba405 100644
--- a/frontend/src/metabase/pulse/actions.js
+++ b/frontend/src/metabase/pulse/actions.js
@@ -1,17 +1,14 @@
 import { createAction } from "redux-actions";
 import { createThunkAction } from "metabase/lib/redux";
-import { normalize, schema } from "normalizr";
 
-import { PulseApi, CardApi, UserApi } from "metabase/services";
-import { formInputSelector } from "./selectors";
+import { PulseApi } from "metabase/services";
+import Pulses from "metabase/entities/pulses";
 
-import { getDefaultChannel, createChannel } from "metabase/lib/pulse";
+import { getEditingPulse, getPulseFormInput } from "./selectors";
+import { setErrorPage } from "metabase/redux/app";
 
-const card = new schema.Entity("card");
-const pulse = new schema.Entity("pulse");
-const user = new schema.Entity("user");
+import { getDefaultChannel, createChannel } from "metabase/lib/pulse";
 
-export const FETCH_PULSES = "FETCH_PULSES";
 export const SET_EDITING_PULSE = "SET_EDITING_PULSE";
 export const UPDATE_EDITING_PULSE = "UPDATE_EDITING_PULSE";
 export const SAVE_PULSE = "SAVE_PULSE";
@@ -19,93 +16,66 @@ export const SAVE_EDITING_PULSE = "SAVE_EDITING_PULSE";
 export const DELETE_PULSE = "DELETE_PULSE";
 export const TEST_PULSE = "TEST_PULSE";
 
-export const FETCH_CARDS = "FETCH_CARDS";
-export const FETCH_USERS = "FETCH_USERS";
 export const FETCH_PULSE_FORM_INPUT = "FETCH_PULSE_FORM_INPUT";
 export const FETCH_PULSE_CARD_PREVIEW = "FETCH_PULSE_CARD_PREVIEW";
 
-export const fetchPulses = createThunkAction(FETCH_PULSES, function() {
-  return async function(dispatch, getState) {
-    let pulses = await PulseApi.list();
-    return normalize(pulses, [pulse]);
-  };
-});
-
 export const setEditingPulse = createThunkAction(SET_EDITING_PULSE, function(
   id,
+  initialCollectionId = null,
 ) {
   return async function(dispatch, getState) {
     if (id != null) {
       try {
-        return await PulseApi.get({ pulseId: id });
-      } catch (e) {}
+        return Pulses.HACK_getObjectFromAction(
+          await dispatch(Pulses.actions.fetch({ id })),
+        );
+      } catch (e) {
+        console.error(e);
+        dispatch(setErrorPage(e));
+      }
+    } else {
+      // HACK: need a way to wait for form_input to finish loading
+      const channels =
+        getPulseFormInput(getState()).channels ||
+        (await PulseApi.form_input()).channels;
+      const defaultChannelSpec = getDefaultChannel(channels);
+      return {
+        name: null,
+        cards: [],
+        channels: defaultChannelSpec ? [createChannel(defaultChannelSpec)] : [],
+        skip_if_empty: false,
+        collection_id: initialCollectionId,
+      };
     }
-    // HACK: need a way to wait for form_input to finish loading
-    const channels =
-      formInputSelector(getState()).channels ||
-      (await PulseApi.form_input()).channels;
-    const defaultChannelSpec = getDefaultChannel(channels);
-    return {
-      name: null,
-      cards: [],
-      channels: defaultChannelSpec ? [createChannel(defaultChannelSpec)] : [],
-      skip_if_empty: false,
-    };
   };
 });
 
 export const updateEditingPulse = createAction(UPDATE_EDITING_PULSE);
 
-export const savePulse = createThunkAction(SAVE_PULSE, function(pulse) {
-  return async function(dispatch, getState) {
-    return await PulseApi.update(pulse);
-  };
-});
-
 export const saveEditingPulse = createThunkAction(
   SAVE_EDITING_PULSE,
   function() {
     return async function(dispatch, getState) {
-      let { pulse: { editingPulse } } = getState();
+      let editingPulse = getEditingPulse(getState());
       if (editingPulse.id != null) {
-        return await PulseApi.update(editingPulse);
+        return Pulses.HACK_getObjectFromAction(
+          await dispatch(Pulses.actions.update(editingPulse)),
+        );
       } else {
-        return await PulseApi.create(editingPulse);
+        return Pulses.HACK_getObjectFromAction(
+          await dispatch(Pulses.actions.create(editingPulse)),
+        );
       }
     };
   },
 );
 
-export const deletePulse = createThunkAction(DELETE_PULSE, function(id) {
-  return async function(dispatch, getState) {
-    return await PulseApi.delete({ pulseId: id });
-  };
-});
-
 export const testPulse = createThunkAction(TEST_PULSE, function(pulse) {
   return async function(dispatch, getState) {
     return await PulseApi.test(pulse);
   };
 });
 
-// NOTE: duplicated from dashboards/actions.js
-export const fetchCards = createThunkAction(FETCH_CARDS, function(
-  filterMode = "all",
-) {
-  return async function(dispatch, getState) {
-    let cards = await CardApi.list({ f: filterMode });
-    return normalize(cards, [card]);
-  };
-});
-
-// NOTE: duplicated from admin/people/actions.js
-export const fetchUsers = createThunkAction(FETCH_USERS, function() {
-  return async function(dispatch, getState) {
-    let users = await UserApi.list();
-    return normalize(users, [user]);
-  };
-});
-
 export const fetchPulseFormInput = createThunkAction(
   FETCH_PULSE_FORM_INPUT,
   function() {
diff --git a/frontend/src/metabase/pulse/components/CardPicker.jsx b/frontend/src/metabase/pulse/components/CardPicker.jsx
deleted file mode 100644
index f2b90440acf7f457a60fcf70e11b199c1b02aa82..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/pulse/components/CardPicker.jsx
+++ /dev/null
@@ -1,231 +0,0 @@
-/* eslint "react/prop-types": "warn" */
-import React, { Component } from "react";
-import PropTypes from "prop-types";
-import ReactDOM from "react-dom";
-import { t } from "c-3po";
-
-import Icon from "metabase/components/Icon.jsx";
-import Popover from "metabase/components/Popover.jsx";
-import Query from "metabase/lib/query";
-
-import _ from "underscore";
-
-export default class CardPicker extends Component {
-  state = {
-    isOpen: false,
-    inputValue: "",
-    inputWidth: 300,
-    collectionId: undefined,
-  };
-
-  static propTypes = {
-    cardList: PropTypes.array.isRequired,
-    onChange: PropTypes.func.isRequired,
-    attachmentsEnabled: PropTypes.bool,
-  };
-
-  componentWillUnmount() {
-    clearTimeout(this._timer);
-  }
-
-  onInputChange = ({ target }) => {
-    this.setState({ inputValue: target.value });
-  };
-
-  onInputFocus = () => {
-    this.setState({ isOpen: true });
-  };
-
-  onInputBlur = () => {
-    // Without a timeout here isOpen gets set to false when an item is clicked
-    // which causes the click handler to not fire. For some reason this even
-    // happens with a 100ms delay, but not 200ms?
-    clearTimeout(this._timer);
-    this._timer = setTimeout(() => {
-      if (!this.state.isClicking) {
-        this.setState({ isOpen: false });
-      } else {
-        this.setState({ isClicking: false });
-      }
-    }, 250);
-  };
-
-  onChange = id => {
-    this.props.onChange(id);
-    ReactDOM.findDOMNode(this.refs.input).blur();
-  };
-
-  renderItem(card) {
-    const { attachmentsEnabled } = this.props;
-    let error;
-    try {
-      if (!attachmentsEnabled && Query.isBareRows(card.dataset_query.query)) {
-        error = t`Raw data cannot be included in pulses`;
-      }
-    } catch (e) {}
-    if (
-      !attachmentsEnabled &&
-      (card.display === "pin_map" ||
-        card.display === "state" ||
-        card.display === "country")
-    ) {
-      error = t`Maps cannot be included in pulses`;
-    }
-
-    if (error) {
-      return (
-        <li key={card.id} className="px2 py1">
-          <h4 className="text-grey-2">{card.name}</h4>
-          <h4 className="text-gold mt1">{error}</h4>
-        </li>
-      );
-    } else {
-      return (
-        <li
-          key={card.id}
-          className="List-item cursor-pointer"
-          onClickCapture={this.onChange.bind(this, card.id)}
-        >
-          <h4 className="List-item-title px2 py1">{card.name}</h4>
-        </li>
-      );
-    }
-  }
-
-  // keep the modal width in sync with the input width :-/
-  componentDidUpdate() {
-    let { scrollWidth } = ReactDOM.findDOMNode(this.refs.input);
-    if (this.state.inputWidth !== scrollWidth) {
-      this.setState({ inputWidth: scrollWidth });
-    }
-  }
-
-  render() {
-    let { cardList } = this.props;
-
-    let { isOpen, inputValue, inputWidth, collectionId } = this.state;
-
-    let cardByCollectionId = _.groupBy(cardList, "collection_id");
-    let collectionIds = Object.keys(cardByCollectionId);
-
-    const collections = _.chain(cardList)
-      .map(card => card.collection)
-      .uniq(c => c && c.id)
-      .filter(c => c)
-      .sortBy("name")
-      // add "Everything else" as the last option for cards without a
-      // collection
-      .concat([{ id: null, name: t`Everything else` }])
-      .value();
-
-    let visibleCardList;
-    if (inputValue) {
-      let searchString = inputValue.toLowerCase();
-      visibleCardList = cardList.filter(
-        card =>
-          ~(card.name || "").toLowerCase().indexOf(searchString) ||
-          ~(card.description || "").toLowerCase().indexOf(searchString),
-      );
-    } else {
-      if (collectionId !== undefined) {
-        visibleCardList = cardByCollectionId[collectionId];
-      } else if (collectionIds.length === 1) {
-        visibleCardList = cardByCollectionId[collectionIds[0]];
-      }
-    }
-
-    const collection = _.findWhere(collections, { id: collectionId });
-    return (
-      <div className="CardPicker flex-full">
-        <input
-          ref="input"
-          className="input no-focus full text-bold"
-          placeholder={t`Type a question name to filter`}
-          value={this.inputValue}
-          onFocus={this.onInputFocus}
-          onBlur={this.onInputBlur}
-          onChange={this.onInputChange}
-        />
-        <Popover
-          isOpen={isOpen && cardList.length > 0}
-          hasArrow={false}
-          tetherOptions={{
-            attachment: "top left",
-            targetAttachment: "bottom left",
-            targetOffset: "0 0",
-          }}
-        >
-          <div
-            className="rounded bordered scroll-y scroll-show"
-            style={{ width: inputWidth + "px", maxHeight: "400px" }}
-          >
-            {visibleCardList &&
-              collectionIds.length > 1 && (
-                <div
-                  className="flex align-center text-slate cursor-pointer border-bottom p2"
-                  onClick={e => {
-                    this.setState({
-                      collectionId: undefined,
-                      isClicking: true,
-                    });
-                  }}
-                >
-                  <Icon name="chevronleft" size={18} />
-                  <h3 className="ml1">{collection && collection.name}</h3>
-                </div>
-              )}
-            {visibleCardList ? (
-              <ul className="List text-brand">
-                {visibleCardList.map(card => this.renderItem(card))}
-              </ul>
-            ) : collections ? (
-              <CollectionList>
-                {collections.map(collection => (
-                  <CollectionListItem
-                    key={collection.id}
-                    collection={collection}
-                    onClick={e => {
-                      this.setState({
-                        collectionId: collection.id,
-                        isClicking: true,
-                      });
-                    }}
-                  />
-                ))}
-              </CollectionList>
-            ) : null}
-          </div>
-        </Popover>
-      </div>
-    );
-  }
-}
-
-const CollectionListItem = ({ collection, onClick }) => (
-  <li
-    className="List-item cursor-pointer flex align-center py1 px2"
-    onClick={onClick}
-  >
-    <Icon
-      name="collection"
-      style={{ color: collection.color }}
-      className="Icon mr2 text-default"
-      size={18}
-    />
-    <h4 className="List-item-title">{collection.name}</h4>
-    <Icon name="chevronright" className="flex-align-right text-grey-2" />
-  </li>
-);
-
-CollectionListItem.propTypes = {
-  collection: PropTypes.object.isRequired,
-  onClick: PropTypes.func.isRequired,
-};
-
-const CollectionList = ({ children }) => (
-  <ul className="List text-brand">{children}</ul>
-);
-
-CollectionList.propTypes = {
-  children: PropTypes.array.isRequired,
-};
diff --git a/frontend/src/metabase/pulse/components/PulseCardPreview.jsx b/frontend/src/metabase/pulse/components/PulseCardPreview.jsx
index 06c4fd8cd00cb125001b7c60d5697ae17e988e15..2b2c349876262df71faba7e892df519061be6da7 100644
--- a/frontend/src/metabase/pulse/components/PulseCardPreview.jsx
+++ b/frontend/src/metabase/pulse/components/PulseCardPreview.jsx
@@ -9,6 +9,7 @@ import Tooltip from "metabase/components/Tooltip.jsx";
 
 import { t } from "c-3po";
 import cx from "classnames";
+import colors, { alpha } from "metabase/lib/colors";
 
 export default class PulseCardPreview extends Component {
   constructor(props, context) {
@@ -74,12 +75,14 @@ export default class PulseCardPreview extends Component {
         }}
       >
         <div
-          className="absolute p2 text-grey-2"
+          className="absolute p2 text-light"
           style={{
             top: 2,
             right: 2,
-            background:
-              "linear-gradient(to right, rgba(255,255,255,0.2), white, white)",
+            background: `linear-gradient(to right, ${alpha(
+              colors["bg-white"],
+              0.2,
+            )}, white, white)`,
             paddingLeft: 100,
           }}
         >
@@ -177,7 +180,7 @@ const RenderedPulseCardPreviewHeader = ({ children }) => (
                 'Lato, "Helvetica Neue", Helvetica, Arial, sans-serif',
               fontSize: 16,
               fontWeight: 700,
-              color: "rgb(57,67,64)",
+              color: colors["text-dark"],
               textDecoration: "none",
             }}
           >
@@ -195,7 +198,7 @@ RenderedPulseCardPreviewHeader.propTypes = {
 };
 
 const RenderedPulseCardPreviewMessage = ({ children }) => (
-  <div className="text-grey-4">{children}</div>
+  <div className="text-medium">{children}</div>
 );
 
 RenderedPulseCardPreviewMessage.propTypes = {
diff --git a/frontend/src/metabase/pulse/components/PulseEdit.jsx b/frontend/src/metabase/pulse/components/PulseEdit.jsx
index c40cd549e30617b5ce845324370e8037a9ed214d..4c63caf3923a51f7a30e31e1ba89f35aa3be38f1 100644
--- a/frontend/src/metabase/pulse/components/PulseEdit.jsx
+++ b/frontend/src/metabase/pulse/components/PulseEdit.jsx
@@ -2,7 +2,6 @@
 import React, { Component } from "react";
 import PropTypes from "prop-types";
 import { t, jt, ngettext, msgid } from "c-3po";
-import { withRouter } from "react-router";
 
 import PulseEditName from "./PulseEditName.jsx";
 import PulseEditCollection from "./PulseEditCollection";
@@ -12,7 +11,7 @@ import PulseEditSkip from "./PulseEditSkip.jsx";
 import WhatsAPulse from "./WhatsAPulse.jsx";
 
 import ActionButton from "metabase/components/ActionButton.jsx";
-import Link from "metabase/components/Link";
+import Button from "metabase/components/Button";
 import MetabaseAnalytics from "metabase/lib/analytics";
 import ModalWithTrigger from "metabase/components/ModalWithTrigger.jsx";
 import ModalContent from "metabase/components/ModalContent.jsx";
@@ -21,36 +20,27 @@ import DeleteModalWithConfirm from "metabase/components/DeleteModalWithConfirm.j
 import { pulseIsValid, cleanPulse, emailIsEnabled } from "metabase/lib/pulse";
 import * as Urls from "metabase/lib/urls";
 
-import _ from "underscore";
 import cx from "classnames";
 
-@withRouter
 export default class PulseEdit extends Component {
-  constructor(props) {
-    super(props);
-
-    _.bindAll(this, "save", "delete", "setPulse");
-  }
-
   static propTypes = {
     pulse: PropTypes.object.isRequired,
     pulseId: PropTypes.number,
     formInput: PropTypes.object.isRequired,
     setEditingPulse: PropTypes.func.isRequired,
-    fetchCards: PropTypes.func.isRequired,
-    fetchUsers: PropTypes.func.isRequired,
     fetchPulseFormInput: PropTypes.func.isRequired,
     updateEditingPulse: PropTypes.func.isRequired,
     saveEditingPulse: PropTypes.func.isRequired,
-    deletePulse: PropTypes.func.isRequired,
     onChangeLocation: PropTypes.func.isRequired,
-    location: PropTypes.object,
+    goBack: PropTypes.func,
+    initialCollectionId: PropTypes.number,
   };
 
   componentDidMount() {
-    this.props.setEditingPulse(this.props.pulseId);
-    this.props.fetchCards();
-    this.props.fetchUsers();
+    this.props.setEditingPulse(
+      this.props.pulseId,
+      this.props.initialCollectionId,
+    );
     this.props.fetchPulseFormInput();
 
     MetabaseAnalytics.trackEvent(
@@ -59,7 +49,7 @@ export default class PulseEdit extends Component {
     );
   }
 
-  async save() {
+  handleSave = async () => {
     let pulse = cleanPulse(this.props.pulse, this.props.formInput.channels);
     await this.props.updateEditingPulse(pulse);
     await this.props.saveEditingPulse();
@@ -71,19 +61,28 @@ export default class PulseEdit extends Component {
     );
 
     this.props.onChangeLocation(Urls.collection(pulse.collection_id));
-  }
+  };
 
-  async delete() {
-    await this.props.deletePulse(this.props.pulse.id);
+  handleArchive = async () => {
+    await this.props.setPulseArchived(this.props.pulse, true);
 
-    MetabaseAnalytics.trackEvent("PulseDelete", "Complete");
+    MetabaseAnalytics.trackEvent("PulseArchive", "Complete");
 
-    this.props.onChangeLocation("/pulse");
-  }
+    this.props.onChangeLocation(
+      Urls.collection(this.props.pulse.collection_id),
+    );
+  };
+
+  handleUnarchive = async () => {
+    await this.props.setPulseArchived(this.props.pulse, false);
+    this.setPulse({ ...this.props.pulse, archived: false });
+
+    MetabaseAnalytics.trackEvent("PulseUnarchive", "Complete");
+  };
 
-  setPulse(pulse) {
+  setPulse = pulse => {
     this.props.updateEditingPulse(pulse);
-  }
+  };
 
   getConfirmItems() {
     return this.props.pulse.channels.map(
@@ -119,7 +118,7 @@ export default class PulseEdit extends Component {
   }
 
   render() {
-    const { pulse, formInput, location } = this.props;
+    const { pulse, formInput } = this.props;
     const isValid = pulseIsValid(pulse, formInput.channels);
     const attachmentsEnabled = emailIsEnabled(pulse);
     return (
@@ -148,11 +147,7 @@ export default class PulseEdit extends Component {
         </div>
         <div className="PulseEdit-content pt2 pb4">
           <PulseEditName {...this.props} setPulse={this.setPulse} />
-          <PulseEditCollection
-            {...this.props}
-            setPulse={this.setPulse}
-            initialCollectionId={location.query.collectionId}
-          />
+          <PulseEditCollection {...this.props} setPulse={this.setPulse} />
           <PulseEditCards
             {...this.props}
             setPulse={this.setPulse}
@@ -167,50 +162,50 @@ export default class PulseEdit extends Component {
             />
           </div>
           <PulseEditSkip {...this.props} setPulse={this.setPulse} />
-          {pulse &&
-            pulse.id != null && (
-              <div className="DangerZone mb2 p3 rounded bordered relative">
-                <h3
-                  className="text-error absolute top bg-white px1"
-                  style={{ marginTop: "-12px" }}
-                >{t`Danger Zone`}</h3>
-                <div className="ml1">
-                  <h4 className="text-bold mb1">{t`Delete this pulse`}</h4>
-                  <div className="flex">
-                    <p className="h4 pr2">{t`Stop delivery and delete this pulse. There's no undo, so be careful.`}</p>
-                    <ModalWithTrigger
-                      ref={"deleteModal" + pulse.id}
-                      triggerClasses="Button Button--danger flex-align-right flex-no-shrink"
-                      triggerElement={t`Delete this Pulse`}
-                    >
-                      <DeleteModalWithConfirm
-                        objectType="pulse"
-                        title={t`Delete` + ' "' + pulse.name + '"?'}
-                        confirmItems={this.getConfirmItems()}
-                        onClose={() =>
-                          this.refs["deleteModal" + pulse.id].close()
-                        }
-                        onDelete={this.delete}
-                      />
-                    </ModalWithTrigger>
-                  </div>
-                </div>
-              </div>
-            )}
         </div>
         <div className="PulseEdit-footer flex align-center border-top py3">
-          <ActionButton
-            actionFn={this.save}
-            className={cx("Button Button--primary", { disabled: !isValid })}
-            normalText={pulse.id != null ? t`Save changes` : t`Create pulse`}
-            activeText={t`Saving…`}
-            failedText={t`Save failed`}
-            successText={t`Saved`}
-          />
-          <Link
-            to={Urls.collection(location.query.collectionId)}
-            className="Button ml2"
-          >{t`Cancel`}</Link>
+          {pulse.archived ? (
+            <ActionButton
+              key="unarchive"
+              actionFn={this.handleUnarchive}
+              className={cx("Button Button--danger")}
+              normalText={t`Unarchive`}
+              activeText={t`Unarchiving…`}
+              failedText={t`Unarchive failed`}
+              successText={t`Unarchived`}
+            />
+          ) : (
+            <ActionButton
+              key="save"
+              actionFn={this.handleSave}
+              className={cx("Button Button--primary", { disabled: !isValid })}
+              normalText={pulse.id != null ? t`Save changes` : t`Create pulse`}
+              activeText={t`Saving…`}
+              failedText={t`Save failed`}
+              successText={t`Saved`}
+            />
+          )}
+          <Button onClick={() => this.props.goBack()} ml={2}>
+            {t`Cancel`}
+          </Button>
+          {pulse.id != null &&
+            !pulse.archived && (
+              <ModalWithTrigger
+                triggerClasses="Button Button--danger flex-align-right flex-no-shrink"
+                triggerElement={t`Archive`}
+              >
+                {({ onClose }) => (
+                  <DeleteModalWithConfirm
+                    objectType="pulse"
+                    title={t`Archive` + ' "' + pulse.name + '"?'}
+                    buttonText={t`Archive`}
+                    confirmItems={this.getConfirmItems()}
+                    onClose={onClose}
+                    onDelete={this.handleArchive}
+                  />
+                )}
+              </ModalWithTrigger>
+            )}
         </div>
       </div>
     );
diff --git a/frontend/src/metabase/pulse/components/PulseEditCards.jsx b/frontend/src/metabase/pulse/components/PulseEditCards.jsx
index 5c94cb0cca20e178dff08ff780ce1f6362fc8c30..81dd7207bff7043c2daba4d3461a649e731e3391 100644
--- a/frontend/src/metabase/pulse/components/PulseEditCards.jsx
+++ b/frontend/src/metabase/pulse/components/PulseEditCards.jsx
@@ -4,11 +4,14 @@ import PropTypes from "prop-types";
 import { t } from "c-3po";
 import cx from "classnames";
 
-import CardPicker from "./CardPicker.jsx";
 import PulseCardPreview from "./PulseCardPreview.jsx";
 
+import QuestionSelect from "metabase/containers/QuestionSelect";
+
 import MetabaseAnalytics from "metabase/lib/analytics";
 
+import colors from "metabase/lib/colors";
+
 const SOFT_LIMIT = 10;
 const HARD_LIMIT = 25;
 const TABLE_MAX_ROWS = 20;
@@ -33,8 +36,6 @@ export default class PulseEditCards extends Component {
     pulse: PropTypes.object.isRequired,
     pulseId: PropTypes.number,
     cardPreviews: PropTypes.object.isRequired,
-    cards: PropTypes.object.isRequired,
-    cardList: PropTypes.array.isRequired,
     fetchPulseCardPreview: PropTypes.func.isRequired,
     setPulse: PropTypes.func.isRequired,
     attachmentsEnabled: PropTypes.bool,
@@ -147,7 +148,7 @@ export default class PulseEditCards extends Component {
   }
 
   render() {
-    let { pulse, cards, cardList, cardPreviews } = this.props;
+    let { pulse, cardPreviews } = this.props;
 
     let pulseCards = pulse ? pulse.cards.slice() : [];
     if (pulseCards.length < HARD_LIMIT) {
@@ -157,50 +158,50 @@ export default class PulseEditCards extends Component {
     return (
       <div className="py1">
         <h2>{t`Pick your data`}</h2>
-        <p className="mt1 h4 text-bold text-grey-3">
+        <p className="mt1 h4 text-bold text-medium">
           {t`Choose questions you'd like to send in this pulse`}.
         </p>
         <ol className="my3">
-          {cards &&
-            pulseCards.map((card, index) => (
-              <li key={index} className="my1">
-                {index === SOFT_LIMIT && (
-                  <div
-                    className="my4 ml3"
-                    style={{
-                      width: 375,
-                      borderTop: "1px dashed rgb(214,214,214)",
-                    }}
-                  />
-                )}
-                <div className="flex align-top">
-                  <div className="flex align-top" style={{ width: 400 }}>
-                    <span className="h3 text-bold mr1 mt2">{index + 1}.</span>
-                    {card ? (
-                      <PulseCardPreview
-                        card={card}
-                        cardPreview={cardPreviews[card.id]}
-                        onChange={this.setCard.bind(this, index)}
-                        onRemove={this.removeCard.bind(this, index)}
-                        fetchPulseCardPreview={this.props.fetchPulseCardPreview}
-                        attachmentsEnabled={
-                          this.props.attachmentsEnabled &&
-                          !isAutoAttached(cardPreviews[card.id])
-                        }
-                        trackPulseEvent={this.trackPulseEvent}
-                      />
-                    ) : (
-                      <CardPicker
-                        cardList={cardList}
-                        onChange={this.addCard.bind(this, index)}
-                        attachmentsEnabled={this.props.attachmentsEnabled}
-                      />
-                    )}
-                  </div>
-                  {this.renderCardNotices(card, index)}
+          {pulseCards.map((card, index) => (
+            <li key={index} className="my1">
+              {index === SOFT_LIMIT && (
+                <div
+                  className="my4 ml3"
+                  style={{
+                    width: 375,
+                    borderTop: `1px dashed ${colors["border"]}`,
+                  }}
+                />
+              )}
+              <div className="flex align-top">
+                <div className="flex align-top" style={{ width: 400 }}>
+                  <span className="h3 text-bold mr1 mt2">{index + 1}.</span>
+                  {card ? (
+                    <PulseCardPreview
+                      card={card}
+                      cardPreview={cardPreviews[card.id]}
+                      onChange={this.setCard.bind(this, index)}
+                      onRemove={this.removeCard.bind(this, index)}
+                      fetchPulseCardPreview={this.props.fetchPulseCardPreview}
+                      attachmentsEnabled={
+                        this.props.attachmentsEnabled &&
+                        !isAutoAttached(cardPreviews[card.id])
+                      }
+                      trackPulseEvent={this.trackPulseEvent}
+                    />
+                  ) : (
+                    <QuestionSelect
+                      onChange={questionId => this.addCard(index, questionId)}
+                      className="flex-full"
+                      // TODO: reimplement CardPicker's warnings for unsuitable cards
+                      // attachmentsEnabled={this.props.attachmentsEnabled}
+                    />
+                  )}
                 </div>
-              </li>
-            ))}
+                {this.renderCardNotices(card, index)}
+              </div>
+            </li>
+          ))}
         </ol>
       </div>
     );
diff --git a/frontend/src/metabase/pulse/components/PulseEditChannels.jsx b/frontend/src/metabase/pulse/components/PulseEditChannels.jsx
index 26f64805efdac5e48ae94e133c9707da7e030dbc..761e084cff6eecc6bee6e86995879904ff400030 100644
--- a/frontend/src/metabase/pulse/components/PulseEditChannels.jsx
+++ b/frontend/src/metabase/pulse/components/PulseEditChannels.jsx
@@ -31,10 +31,7 @@ const CHANNEL_NOUN_PLURAL = {
 };
 
 export default class PulseEditChannels extends Component {
-  constructor(props) {
-    super(props);
-    this.state = {};
-  }
+  state = {};
 
   static propTypes = {
     pulse: PropTypes.object.isRequired,
@@ -42,7 +39,7 @@ export default class PulseEditChannels extends Component {
     pulseIsValid: PropTypes.bool.isRequired,
     formInput: PropTypes.object.isRequired,
     user: PropTypes.object.isRequired,
-    userList: PropTypes.array.isRequired,
+    users: PropTypes.array.isRequired,
     setPulse: PropTypes.func.isRequired,
     testPulse: PropTypes.func,
     cardPreviews: PropTypes.object,
@@ -199,7 +196,7 @@ export default class PulseEditChannels extends Component {
               autoFocus={!!this.props.pulse.name}
               recipients={channel.recipients}
               recipientTypes={channelSpec.recipients}
-              users={this.props.userList}
+              users={this.props.users}
               onRecipientsChange={recipients =>
                 this.onChannelPropertyChange(index, "recipients", recipients)
               }
@@ -263,7 +260,7 @@ export default class PulseEditChannels extends Component {
         <div className="flex align-center p3 border-row-divider">
           {CHANNEL_ICONS[channelSpec.type] && (
             <Icon
-              className="mr1 text-grey-2"
+              className="mr1 text-light"
               name={CHANNEL_ICONS[channelSpec.type]}
               size={28}
             />
@@ -276,7 +273,7 @@ export default class PulseEditChannels extends Component {
           />
         </div>
         {channels.length > 0 && channelSpec.configured ? (
-          <ul className="bg-grey-0 px3">{channels}</ul>
+          <ul className="bg-light px3">{channels}</ul>
         ) : channels.length > 0 && !channelSpec.configured ? (
           <div className="p4 text-centered">
             <h3 className="mb2">{t`${
@@ -297,7 +294,7 @@ export default class PulseEditChannels extends Component {
       slack: { name: t`Slack`, type: "slack" },
     };
     return (
-      <ul className="bordered rounded">
+      <ul className="bordered rounded bg-white">
         {Object.values(channels).map(channelSpec =>
           this.renderChannelSection(channelSpec),
         )}
diff --git a/frontend/src/metabase/pulse/components/PulseEditCollection.jsx b/frontend/src/metabase/pulse/components/PulseEditCollection.jsx
index 9d6a16b9e3a9c173168030fed3880dcdf7c1309e..34f4ae1cd567ecd2b2bb7f7f04d7954ecaad21d6 100644
--- a/frontend/src/metabase/pulse/components/PulseEditCollection.jsx
+++ b/frontend/src/metabase/pulse/components/PulseEditCollection.jsx
@@ -6,19 +6,17 @@ import CollectionSelect from "metabase/containers/CollectionSelect";
 
 export default class PulseEditCollection extends React.Component {
   render() {
+    const { pulse, setPulse } = this.props;
     return (
       <Box>
         <h2>{t`Which collection should this pulse live in?`}</h2>
 
         <Box my={2} width={400}>
           <CollectionSelect
-            value={
-              this.props.pulse.collection_id ||
-              parseInt(this.props.initialCollectionId)
-            }
+            value={pulse.collection_id}
             onChange={collection_id =>
-              this.props.setPulse({
-                ...this.props.pulse,
+              setPulse({
+                ...pulse,
                 collection_id,
               })
             }
diff --git a/frontend/src/metabase/pulse/components/PulseEditName.jsx b/frontend/src/metabase/pulse/components/PulseEditName.jsx
index 3bf984dfd32e2330a1b4b789aae3d41f321a252d..27d6c9f670cbb6f1fb7a049c8f170ad68c31a687 100644
--- a/frontend/src/metabase/pulse/components/PulseEditName.jsx
+++ b/frontend/src/metabase/pulse/components/PulseEditName.jsx
@@ -33,7 +33,7 @@ export default class PulseEditName extends Component {
     return (
       <div className="py1">
         <h2>{t`Name your pulse`}</h2>
-        <p className="mt1 h4 text-bold text-grey-3">
+        <p className="mt1 h4 text-bold text-medium">
           {t`Give your pulse a name to help others understand what it's about`}.
         </p>
         <div className="my3">
diff --git a/frontend/src/metabase/pulse/components/PulseEditSkip.jsx b/frontend/src/metabase/pulse/components/PulseEditSkip.jsx
index 32e29bf58225ad1fd9d0f75e361b6911a87bf6fe..b178c2e5fa842cd4e7fa6f0c5403a905647ef419 100644
--- a/frontend/src/metabase/pulse/components/PulseEditSkip.jsx
+++ b/frontend/src/metabase/pulse/components/PulseEditSkip.jsx
@@ -20,7 +20,7 @@ export default class PulseEditSkip extends Component {
     return (
       <div className="py1">
         <h2>{t`Skip if no results`}</h2>
-        <p className="mt1 h4 text-bold text-grey-3">
+        <p className="mt1 h4 text-bold text-medium">
           {t`Skip a scheduled Pulse if none of its questions have any results`}.
         </p>
         <div className="my3">
diff --git a/frontend/src/metabase/pulse/components/PulseList.jsx b/frontend/src/metabase/pulse/components/PulseList.jsx
deleted file mode 100644
index 0c291a888d0f96d80bae2e6d4239246f425ef458..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/pulse/components/PulseList.jsx
+++ /dev/null
@@ -1,97 +0,0 @@
-import React, { Component } from "react";
-import { t } from "c-3po";
-
-import PulseListItem from "./PulseListItem.jsx";
-import WhatsAPulse from "./WhatsAPulse.jsx";
-
-import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper.jsx";
-import ChannelSetupModal from "metabase/components/ChannelSetupModal";
-import Modal from "metabase/components/Modal.jsx";
-
-import _ from "underscore";
-
-export default class PulseList extends Component {
-  constructor(props, context) {
-    super(props, context);
-
-    this.state = {
-      showSetupModal: false,
-    };
-
-    _.bindAll(this, "create");
-  }
-
-  static propTypes = {};
-  static defaultProps = {};
-
-  componentDidMount() {
-    this.props.fetchPulses();
-    this.props.fetchPulseFormInput();
-  }
-
-  create() {
-    if (this.props.hasConfiguredAnyChannel) {
-      this.props.onChangeLocation("/pulse/create");
-    } else {
-      this.setState({ showSetupModal: true });
-    }
-  }
-
-  render() {
-    let { pulses, user } = this.props;
-    return (
-      <div className="PulseList px3">
-        <div className="border-bottom mb2 mt3">
-          <div className="wrapper wrapper--trim flex align-center mb2">
-            <h1>{t`Pulses`}</h1>
-            <a
-              onClick={this.create}
-              className="PulseButton Button flex-align-right"
-            >{t`Create a pulse`}</a>
-          </div>
-        </div>
-        <LoadingAndErrorWrapper loading={!pulses}>
-          {() =>
-            pulses.length > 0 ? (
-              <ul className="wrapper wrapper--trim">
-                {pulses
-                  .slice()
-                  .sort((a, b) => b.created_at - a.created_at)
-                  .map(pulse => (
-                    <li key={pulse.id}>
-                      <PulseListItem
-                        scrollTo={pulse.id === this.props.pulseId}
-                        pulse={pulse}
-                        user={user}
-                        formInput={this.props.formInput}
-                        savePulse={this.props.savePulse}
-                      />
-                    </li>
-                  ))}
-              </ul>
-            ) : (
-              <div className="mt4 ml-auto mr-auto">
-                <WhatsAPulse
-                  button={
-                    <a
-                      onClick={this.create}
-                      className="Button Button--primary"
-                    >{t`Create a pulse`}</a>
-                  }
-                />
-              </div>
-            )
-          }
-        </LoadingAndErrorWrapper>
-        <Modal isOpen={this.state.showSetupModal}>
-          <ChannelSetupModal
-            user={user}
-            onClose={() => this.setState({ showSetupModal: false })}
-            onChangeLocation={this.props.onChangeLocation}
-            entityNamePlural={t`pulses`}
-          />
-        </Modal>
-      </div>
-    );
-  }
-}
diff --git a/frontend/src/metabase/pulse/components/PulseListChannel.jsx b/frontend/src/metabase/pulse/components/PulseListChannel.jsx
deleted file mode 100644
index 155a777ec45bffd7adf68a5611ef462d9d333e64..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/pulse/components/PulseListChannel.jsx
+++ /dev/null
@@ -1,128 +0,0 @@
-/* eslint "react/prop-types": "warn" */
-import React, { Component } from "react";
-import PropTypes from "prop-types";
-import { t, ngettext, msgid } from "c-3po";
-
-import Icon from "metabase/components/Icon.jsx";
-
-import _ from "underscore";
-
-export default class PulseListChannel extends Component {
-  constructor(props, context) {
-    super(props, context);
-
-    _.bindAll(this, "subscribe", "unsubscribe");
-  }
-
-  static propTypes = {
-    pulse: PropTypes.object.isRequired,
-    channel: PropTypes.object.isRequired,
-    channelSpec: PropTypes.object,
-    user: PropTypes.object.isRequired,
-    savePulse: PropTypes.func.isRequired,
-  };
-
-  subscribe() {
-    let { pulse, channel, user } = this.props;
-    this.props.savePulse({
-      ...pulse,
-      channels: pulse.channels.map(
-        c =>
-          c !== channel ? c : { ...c, recipients: [...c.recipients, user] },
-      ),
-    });
-  }
-
-  unsubscribe() {
-    let { pulse, channel, user } = this.props;
-    this.props.savePulse({
-      ...pulse,
-      channels: pulse.channels.map(
-        c =>
-          c !== channel
-            ? c
-            : { ...c, recipients: c.recipients.filter(r => r.id !== user.id) },
-      ),
-    });
-  }
-
-  renderChannelSchedule() {
-    let { channel, channelSpec } = this.props;
-
-    let channelIcon = null;
-    let channelVerb =
-      (channelSpec && channelSpec.displayName) || channel.channel_type;
-    let channelSchedule = channel.schedule_type;
-    let channelTarget =
-      channel.recipients &&
-      (n => ngettext(msgid`${n} person`, `${n} people`, n))(
-        channel.recipients.length,
-      );
-
-    if (channel.channel_type === "email") {
-      channelIcon = "mail";
-      channelVerb = t`Emailed`;
-    } else if (channel.channel_type === "slack") {
-      channelIcon = "slack";
-      channelVerb = t`Slack'd`;
-      // Address #5799 where `details` object is missing for some reason
-      channelTarget = channel.details ? channel.details.channel : t`No channel`;
-    }
-
-    return (
-      <div className="h4 text-grey-4 py2 flex align-center">
-        {channelIcon && <Icon className="mr1" name={channelIcon} size={24} />}
-        <span>
-          {channelVerb + " "}
-          <strong>{channelSchedule}</strong>
-          {channelTarget && (
-            <span>
-              {" " + t`to` + " "}
-              <strong>{channelTarget}</strong>
-            </span>
-          )}
-        </span>
-      </div>
-    );
-  }
-
-  render() {
-    let { pulse, channel, channelSpec, user } = this.props;
-
-    let subscribable = channelSpec && channelSpec.allows_recipients;
-    let subscribed = false;
-    if (subscribable) {
-      subscribed = _.any(channel.recipients, r => r.id === user.id);
-    }
-    return (
-      <div className="py2 flex align-center">
-        {this.renderChannelSchedule()}
-        {subscribable && (
-          <div className="flex-align-right">
-            {subscribed ? (
-              <div className="flex align-center rounded bg-green text-white text-bold">
-                <div className="pl2">{t`You get this ${
-                  channel.channel_type
-                }`}</div>
-                <Icon
-                  className="p2 text-grey-1 text-white-hover cursor-pointer"
-                  name="close"
-                  size={12}
-                  onClick={this.unsubscribe}
-                />
-              </div>
-            ) : !pulse.read_only ? (
-              <div
-                className="flex align-center rounded bordered bg-white text-default text-bold cursor-pointer"
-                onClick={this.subscribe}
-              >
-                <Icon className="p2" name="add" size={12} />
-                <div className="pr2">{t`Get this ${channel.channel_type}`}</div>
-              </div>
-            ) : null}
-          </div>
-        )}
-      </div>
-    );
-  }
-}
diff --git a/frontend/src/metabase/pulse/components/PulseListItem.jsx b/frontend/src/metabase/pulse/components/PulseListItem.jsx
deleted file mode 100644
index db66a0ca628d6e55e9a7f5112a446dcfbe39e5f8..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/pulse/components/PulseListItem.jsx
+++ /dev/null
@@ -1,89 +0,0 @@
-/* eslint "react/prop-types": "warn" */
-import React, { Component } from "react";
-import PropTypes from "prop-types";
-import ReactDOM from "react-dom";
-import { Link } from "react-router";
-import { jt, t } from "c-3po";
-
-import cx from "classnames";
-
-import * as Urls from "metabase/lib/urls";
-import PulseListChannel from "./PulseListChannel.jsx";
-
-export default class PulseListItem extends Component {
-  static propTypes = {
-    pulse: PropTypes.object.isRequired,
-    formInput: PropTypes.object.isRequired,
-    user: PropTypes.object.isRequired,
-    scrollTo: PropTypes.bool.isRequired,
-    savePulse: PropTypes.func.isRequired,
-  };
-
-  componentDidMount() {
-    if (this.props.scrollTo) {
-      const element = ReactDOM.findDOMNode(this.refs.pulseListItem);
-      element.scrollIntoView(true);
-    }
-  }
-
-  render() {
-    let { pulse, formInput, user } = this.props;
-
-    const creator = (
-      <span className="text-bold">
-        {pulse.creator && pulse.creator.common_name}
-      </span>
-    );
-    return (
-      <div
-        ref="pulseListItem"
-        className={cx("PulseListItem bordered rounded mb2 pt3", {
-          "PulseListItem--focused": this.props.scrollTo,
-        })}
-      >
-        <div className="px4 mb2">
-          <div className="flex align-center mb1">
-            <h2 className="break-word" style={{ maxWidth: "80%" }}>
-              {pulse.name}
-            </h2>
-            {!pulse.read_only && (
-              <div className="ml-auto">
-                <Link
-                  to={"/pulse/" + pulse.id}
-                  className="PulseEditButton PulseButton Button no-decoration text-bold"
-                >
-                  {t`Edit`}
-                </Link>
-              </div>
-            )}
-          </div>
-          <span>{jt`Pulse by ${creator}`}</span>
-        </div>
-        <ol className="mb2 px4 flex flex-wrap">
-          {pulse.cards.map((card, index) => (
-            <li key={index} className="mr1 mb1">
-              <Link to={Urls.question(card.id)} className="Button">
-                {card.name}
-              </Link>
-            </li>
-          ))}
-        </ol>
-        <ul className="border-top px4 bg-grey-0">
-          {pulse.channels.filter(channel => channel.enabled).map(channel => (
-            <li key={channel.id} className="border-row-divider">
-              <PulseListChannel
-                pulse={pulse}
-                channel={channel}
-                channelSpec={
-                  formInput.channels && formInput.channels[channel.channel_type]
-                }
-                user={user}
-                savePulse={this.props.savePulse}
-              />
-            </li>
-          ))}
-        </ul>
-      </div>
-    );
-  }
-}
diff --git a/frontend/src/metabase/pulse/components/PulseMoveModal.jsx b/frontend/src/metabase/pulse/components/PulseMoveModal.jsx
index c5a5e135ad221992cdee6fa9fbf924bdc689c94e..107264c7b981d7a9ba425184dde3ffbd358ac592 100644
--- a/frontend/src/metabase/pulse/components/PulseMoveModal.jsx
+++ b/frontend/src/metabase/pulse/components/PulseMoveModal.jsx
@@ -10,6 +10,8 @@ import Icon from "metabase/components/Icon";
 
 import CollectionListLoader from "metabase/containers/CollectionListLoader";
 
+import colors from "metabase/lib/colors";
+
 @withRouter
 class PulseMoveModal extends React.Component {
   state = {
@@ -61,7 +63,7 @@ class PulseMoveModal extends React.Component {
                     )}
                   >
                     <Flex align="center">
-                      <Icon name="all" color={"#DCE1E4"} size={32} />
+                      <Icon name="all" color={colors["text-light"]} size={32} />
                       <h4 className="ml1">{collection.name}</h4>
                     </Flex>
                   </Box>
diff --git a/frontend/src/metabase/pulse/components/RecipientPicker.jsx b/frontend/src/metabase/pulse/components/RecipientPicker.jsx
index b34c73b1278aa6523747c73f25f1b40cdce5d52f..e881c477ea06767c2855308a4b820528cedf6120 100644
--- a/frontend/src/metabase/pulse/components/RecipientPicker.jsx
+++ b/frontend/src/metabase/pulse/components/RecipientPicker.jsx
@@ -54,7 +54,11 @@ export default class RecipientPicker extends Component {
     return (
       <TokenField
         value={recipients}
-        options={users.map(user => ({ label: user.common_name, value: user }))}
+        options={
+          users
+            ? users.map(user => ({ label: user.common_name, value: user }))
+            : []
+        }
         onChange={this.handleOnChange}
         placeholder={
           recipients.length === 0
diff --git a/frontend/src/metabase/pulse/components/WhatsAPulse.jsx b/frontend/src/metabase/pulse/components/WhatsAPulse.jsx
index cfa4f781ba0c23cc0ccfa15e61c66045a389f882..c57b3844c9abb479aba3a296e7e3f8ebc7b33835 100644
--- a/frontend/src/metabase/pulse/components/WhatsAPulse.jsx
+++ b/frontend/src/metabase/pulse/components/WhatsAPulse.jsx
@@ -24,7 +24,7 @@ export default class WhatsAPulse extends Component {
           />
         </div>
         <div
-          className="h3 my3 text-centered text-grey-2 text-bold"
+          className="h3 my3 text-centered text-light text-bold"
           style={{ maxWidth: "500px" }}
         >
           {t`Pulses let you send data from Metabase to email or Slack on the schedule of your choice.`}
diff --git a/frontend/src/metabase/pulse/containers/PulseEditApp.jsx b/frontend/src/metabase/pulse/containers/PulseEditApp.jsx
index 50fb70f53da04fb1ab1399da36fe8f42c682c522..a82b2ecd75f4cffa0bbdec6abfacccd2c6427af0 100644
--- a/frontend/src/metabase/pulse/containers/PulseEditApp.jsx
+++ b/frontend/src/metabase/pulse/containers/PulseEditApp.jsx
@@ -1,45 +1,59 @@
 /* eslint "react/prop-types": "warn" */
 import React, { Component } from "react";
 import { connect } from "react-redux";
-import { push } from "react-router-redux";
 
 import title from "metabase/hoc/Title";
 
 import PulseEdit from "../components/PulseEdit.jsx";
+import { entityListLoader } from "metabase/entities/containers/EntityListLoader";
+
+import Collections from "metabase/entities/collections";
+import Pulses from "metabase/entities/pulses";
+
+import { push, goBack } from "react-router-redux";
+
+import {
+  getPulseId,
+  getEditingPulse,
+  getPulseCardPreviews,
+  getPulseFormInput,
+} from "../selectors";
+import { getUser } from "metabase/selectors/user";
 
-import { editPulseSelectors } from "../selectors";
 import {
   setEditingPulse,
   updateEditingPulse,
   saveEditingPulse,
-  deletePulse,
-  fetchCards,
-  fetchUsers,
   fetchPulseFormInput,
   fetchPulseCardPreview,
   testPulse,
 } from "../actions";
 
-const mapStateToProps = (state, props) => {
-  return {
-    ...editPulseSelectors(state, props),
-    user: state.currentUser,
-  };
-};
+const mapStateToProps = (state, props) => ({
+  pulseId: getPulseId(state, props),
+  pulse: getEditingPulse(state, props),
+  cardPreviews: getPulseCardPreviews(state, props),
+  formInput: getPulseFormInput(state, props),
+  user: getUser(state),
+  initialCollectionId: Collections.selectors.getInitialCollectionId(
+    state,
+    props,
+  ),
+});
 
 const mapDispatchToProps = {
   setEditingPulse,
   updateEditingPulse,
   saveEditingPulse,
-  deletePulse,
-  fetchCards,
-  fetchUsers,
   fetchPulseFormInput,
   fetchPulseCardPreview,
+  setPulseArchived: Pulses.actions.setArchived,
   testPulse,
   onChangeLocation: push,
+  goBack,
 };
 
+@entityListLoader({ entityType: "users" })
 @connect(mapStateToProps, mapDispatchToProps)
 @title(({ pulse }) => pulse && pulse.name)
 export default class PulseEditApp extends Component {
diff --git a/frontend/src/metabase/pulse/containers/PulseListApp.jsx b/frontend/src/metabase/pulse/containers/PulseListApp.jsx
deleted file mode 100644
index 38f2171a1de0073337623197bd4f5a934327a4e4..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/pulse/containers/PulseListApp.jsx
+++ /dev/null
@@ -1,31 +0,0 @@
-/* eslint "react/prop-types": "warn" */
-import React, { Component } from "react";
-import { connect } from "react-redux";
-import { push } from "react-router-redux";
-
-import PulseList from "../components/PulseList.jsx";
-import { listPulseSelectors } from "../selectors";
-
-import { fetchPulses, fetchPulseFormInput, savePulse } from "../actions";
-
-const mapStateToProps = (state, props) => {
-  return {
-    ...listPulseSelectors(state, props),
-    user: state.currentUser,
-    // onChangeLocation: onChangeLocation
-  };
-};
-
-const mapDispatchToProps = {
-  fetchPulses,
-  fetchPulseFormInput,
-  savePulse,
-  onChangeLocation: push,
-};
-
-@connect(mapStateToProps, mapDispatchToProps)
-export default class PulseListApp extends Component {
-  render() {
-    return <PulseList {...this.props} />;
-  }
-}
diff --git a/frontend/src/metabase/pulse/reducers.js b/frontend/src/metabase/pulse/reducers.js
index 9aedf0bf0b2cdf0dbcc38cc2dd2c4c07446b04fd..a27d0335fe172eec3c459894495668be38eaf7dd 100644
--- a/frontend/src/metabase/pulse/reducers.js
+++ b/frontend/src/metabase/pulse/reducers.js
@@ -1,53 +1,13 @@
 import { handleActions } from "redux-actions";
 
 import {
-  momentifyTimestamps,
-  momentifyObjectsTimestamps,
-} from "metabase/lib/redux";
-
-import {
-  FETCH_PULSES,
   SET_EDITING_PULSE,
   UPDATE_EDITING_PULSE,
   SAVE_EDITING_PULSE,
-  SAVE_PULSE,
-  FETCH_CARDS,
-  FETCH_USERS,
   FETCH_PULSE_FORM_INPUT,
   FETCH_PULSE_CARD_PREVIEW,
 } from "./actions";
 
-export const pulses = handleActions(
-  {
-    [FETCH_PULSES]: {
-      next: (state, { payload }) => ({
-        ...momentifyObjectsTimestamps(payload.entities.pulse),
-      }),
-    },
-    [SAVE_PULSE]: {
-      next: (state, { payload }) => ({
-        ...state,
-        [payload.id]: momentifyTimestamps(payload),
-      }),
-    },
-    [SAVE_EDITING_PULSE]: {
-      next: (state, { payload }) => ({
-        ...state,
-        [payload.id]: momentifyTimestamps(payload),
-      }),
-    },
-  },
-  {},
-);
-
-export const pulseList = handleActions(
-  {
-    [FETCH_PULSES]: { next: (state, { payload }) => payload.result },
-    // [DELETE_PULSE]: { next: (state, { payload }) => state }
-  },
-  null,
-);
-
 export const editingPulse = handleActions(
   {
     [SET_EDITING_PULSE]: { next: (state, { payload }) => payload },
@@ -57,36 +17,6 @@ export const editingPulse = handleActions(
   { name: null, cards: [], channels: [] },
 );
 
-// NOTE: duplicated from dashboards/reducers.js
-export const cards = handleActions(
-  {
-    [FETCH_CARDS]: {
-      next: (state, { payload }) => ({
-        ...momentifyObjectsTimestamps(payload.entities.card),
-      }),
-    },
-  },
-  {},
-);
-export const cardList = handleActions(
-  {
-    [FETCH_CARDS]: { next: (state, { payload }) => payload.result },
-  },
-  [],
-);
-
-// NOTE: duplicated from admin/people/reducers.js
-export const users = handleActions(
-  {
-    [FETCH_USERS]: {
-      next: (state, { payload }) => ({
-        ...momentifyObjectsTimestamps(payload.entities.user),
-      }),
-    },
-  },
-  [],
-);
-
 export const formInput = handleActions(
   {
     [FETCH_PULSE_FORM_INPUT]: { next: (state, { payload }) => payload },
diff --git a/frontend/src/metabase/pulse/selectors.js b/frontend/src/metabase/pulse/selectors.js
index 6034dcbcf0c4cdaeb71fe2319bd1b6166357d3e7..2c199a26db55a394adc7dc67642b89fad1b1c849 100644
--- a/frontend/src/metabase/pulse/selectors.js
+++ b/frontend/src/metabase/pulse/selectors.js
@@ -1,36 +1,23 @@
 import { createSelector } from "reselect";
 import _ from "underscore";
 
-const pulsesSelector = state => state.pulse.pulses;
-const pulseIdListSelector = state => state.pulse.pulseList;
+export const getEditingPulse = state => state.pulse.editingPulse;
 
-const pulseListSelector = createSelector(
-  [pulseIdListSelector, pulsesSelector],
-  (pulseIdList, pulses) => pulseIdList && pulseIdList.map(id => pulses[id]),
-);
-
-const editingPulseSelector = state => state.pulse.editingPulse;
-
-const cardsSelector = state => state.pulse.cards;
-const cardIdListSelector = state => state.pulse.cardList;
-
-const usersSelector = state => state.pulse.users;
-
-export const formInputSelector = state => state.pulse.formInput;
+export const getPulseFormInput = state => state.pulse.formInput;
 
 export const hasLoadedChannelInfoSelector = createSelector(
-  [formInputSelector],
+  [getPulseFormInput],
   formInput => !!formInput.channels,
 );
 export const hasConfiguredAnyChannelSelector = createSelector(
-  [formInputSelector],
+  [getPulseFormInput],
   formInput =>
     (formInput.channels &&
       _.some(Object.values(formInput.channels), c => c.configured)) ||
     false,
 );
 export const hasConfiguredEmailChannelSelector = createSelector(
-  [formInputSelector],
+  [getPulseFormInput],
   formInput =>
     (formInput.channels &&
       _.some(
@@ -40,54 +27,7 @@ export const hasConfiguredEmailChannelSelector = createSelector(
     false,
 );
 
-const cardPreviewsSelector = state => state.pulse.cardPreviews;
-
-const cardListSelector = createSelector(
-  [cardIdListSelector, cardsSelector],
-  (cardIdList, cards) => cardIdList && cardIdList.map(id => cards[id]),
-);
+export const getPulseCardPreviews = state => state.pulse.cardPreviews;
 
-export const userListSelector = createSelector([usersSelector], users =>
-  Object.values(users),
-);
-
-const getPulseId = (state, props) =>
+export const getPulseId = (state, props) =>
   props.params.pulseId ? parseInt(props.params.pulseId) : null;
-
-// LIST
-export const listPulseSelectors = createSelector(
-  [
-    getPulseId,
-    pulseListSelector,
-    formInputSelector,
-    hasConfiguredAnyChannelSelector,
-  ],
-  (pulseId, pulses, formInput, hasConfiguredAnyChannel) => ({
-    pulseId,
-    pulses,
-    formInput,
-    hasConfiguredAnyChannel,
-  }),
-);
-
-// EDIT
-export const editPulseSelectors = createSelector(
-  [
-    getPulseId,
-    editingPulseSelector,
-    cardsSelector,
-    cardListSelector,
-    cardPreviewsSelector,
-    userListSelector,
-    formInputSelector,
-  ],
-  (pulseId, pulse, cards, cardList, cardPreviews, userList, formInput) => ({
-    pulseId,
-    pulse,
-    cards,
-    cardList,
-    cardPreviews,
-    userList,
-    formInput,
-  }),
-);
diff --git a/frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx b/frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx
index 5fab16f1332c8be5b50bcf80a35e706c47034ee0..f6cce5c5637b7a5579e2117c42023d54a42093d0 100644
--- a/frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx
+++ b/frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx
@@ -4,7 +4,7 @@ import React, { Component } from "react";
 import { t } from "c-3po";
 import DatePicker from "metabase/query_builder/components/filters/pickers/DatePicker";
 import PopoverWithTrigger from "metabase/components/PopoverWithTrigger";
-import { SelectButton } from "metabase/components/Select";
+import SelectButton from "metabase/components/SelectButton";
 import Button from "metabase/components/Button";
 
 import * as Query from "metabase/lib/query/query";
diff --git a/frontend/src/metabase/qb/components/TimeseriesGroupingWidget.jsx b/frontend/src/metabase/qb/components/TimeseriesGroupingWidget.jsx
index 457bd7584d53da6dfa95768d6eb29283245bd486..7ffee1492c4c9148e9cb4df8a1ce8e70acae88c4 100644
--- a/frontend/src/metabase/qb/components/TimeseriesGroupingWidget.jsx
+++ b/frontend/src/metabase/qb/components/TimeseriesGroupingWidget.jsx
@@ -4,7 +4,7 @@ import React, { Component } from "react";
 
 import TimeGroupingPopover from "metabase/query_builder/components/TimeGroupingPopover";
 import PopoverWithTrigger from "metabase/components/PopoverWithTrigger";
-import { SelectButton } from "metabase/components/Select";
+import SelectButton from "metabase/components/SelectButton";
 
 import * as Query from "metabase/lib/query/query";
 import * as Card from "metabase/meta/Card";
diff --git a/frontend/src/metabase/qb/components/__support__/fixtures.js b/frontend/src/metabase/qb/components/__support__/fixtures.js
index 379266079a20c1714ad886ccd9ce7dc81bb8ec53..5bb554f5d98ac6510b4caf42b3d14f07f3471c9f 100644
--- a/frontend/src/metabase/qb/components/__support__/fixtures.js
+++ b/frontend/src/metabase/qb/components/__support__/fixtures.js
@@ -58,7 +58,7 @@ export const card = {
   dataset_query: {
     type: "query",
     query: {
-      source_table: 10,
+      "source-table": 10,
     },
   },
 };
@@ -77,7 +77,7 @@ export const savedCard = {
   dataset_query: {
     type: "query",
     query: {
-      source_table: 10,
+      "source-table": 10,
     },
   },
 };
diff --git a/frontend/src/metabase/qb/components/actions/CommonMetricsAction.jsx b/frontend/src/metabase/qb/components/actions/CommonMetricsAction.jsx
index 5d8eff61abe94f905c6aa637b93d49c784f582c7..0c6447ace48cf672d701b0d1f2464e6d33f8885f 100644
--- a/frontend/src/metabase/qb/components/actions/CommonMetricsAction.jsx
+++ b/frontend/src/metabase/qb/components/actions/CommonMetricsAction.jsx
@@ -19,6 +19,6 @@ export default ({ question }: ClickActionProps): ClickAction[] => {
   return activeMetrics.slice(0, 5).map(metric => ({
     name: "common-metric",
     title: <span>{jt`View ${<strong>{metric.name}</strong>}`}</span>,
-    question: () => question.summarize(["METRIC", metric.id]),
+    question: () => question.summarize(["metric", metric.id]),
   }));
 };
diff --git a/frontend/src/metabase/qb/components/actions/CompareWithTable.jsx b/frontend/src/metabase/qb/components/actions/CompareWithTable.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..fcd72aa36aa0ab3510111eb23fbb185712063cb4
--- /dev/null
+++ b/frontend/src/metabase/qb/components/actions/CompareWithTable.jsx
@@ -0,0 +1,45 @@
+/* @flow */
+
+import type {
+  ClickAction,
+  ClickActionProps,
+} from "metabase/meta/types/Visualization";
+
+import { t } from "c-3po";
+import { utf8_to_b64url } from "metabase/lib/card";
+import StructuredQuery from "metabase-lib/lib/queries/StructuredQuery";
+
+export default ({ question }: ClickActionProps): ClickAction[] => {
+  const query = question.query();
+  if (!(query instanceof StructuredQuery)) {
+    return [];
+  }
+  if (!query.isBareRows()) {
+    return [];
+  }
+  if (query.filters().length == 0) {
+    return [];
+  }
+
+  const tableId = query.tableId();
+  if (tableId) {
+    return [
+      {
+        name: "xray-card",
+        title: t`Compare this with all rows in the table`,
+        icon: "beaker",
+
+        url: () =>
+          question.card().id
+            ? `/auto/dashboard/table/${tableId}/compare/question/${
+                question.card().id
+              }`
+            : `/auto/dashboard/table/${tableId}/compare/adhoc/${utf8_to_b64url(
+                JSON.stringify(question.card().dataset_query),
+              )}`,
+      },
+    ];
+  } else {
+    return [];
+  }
+};
diff --git a/frontend/src/metabase/qb/components/actions/GenerateDashboardAction.jsx b/frontend/src/metabase/qb/components/actions/GenerateDashboardAction.jsx
deleted file mode 100644
index b7ff628478a4a6750f69603981a38b80fea8d1d2..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/qb/components/actions/GenerateDashboardAction.jsx
+++ /dev/null
@@ -1,40 +0,0 @@
-/* @flow */
-
-import type {
-  ClickAction,
-  ClickActionProps,
-} from "metabase/meta/types/Visualization";
-import StructuredQuery from "metabase-lib/lib/queries/StructuredQuery";
-import { utf8_to_b64url } from "metabase/lib/card";
-import { t } from "c-3po";
-
-export default ({ question, settings }: ClickActionProps): ClickAction[] => {
-  console.log(JSON.stringify(question.query().datasetQuery()));
-  let dashboard_url = "adhoc";
-
-  const query = question.query();
-  if (!(query instanceof StructuredQuery)) {
-    return [];
-  }
-
-  // aggregations
-  if (query.aggregations().length) {
-    return [];
-  }
-  if (question.card().id) {
-    dashboard_url = `/auto/dashboard/question/${question.card().id}`;
-  } else {
-    let encodedQueryDict = utf8_to_b64url(
-      JSON.stringify(question.query().datasetQuery()),
-    );
-    dashboard_url = `/auto/dashboard/adhoc/${encodedQueryDict}`;
-  }
-  return [
-    {
-      name: "generate-dashboard",
-      title: t`See an exploration of this question`,
-      icon: "bolt",
-      url: () => dashboard_url,
-    },
-  ];
-};
diff --git a/frontend/src/metabase/qb/components/actions/index.js b/frontend/src/metabase/qb/components/actions/index.js
index e2e7ebc6a181197dd0a2f83bb3cf8341b4212a0f..22bb2505bec2da88fb4e18ded9631fb16147af55 100644
--- a/frontend/src/metabase/qb/components/actions/index.js
+++ b/frontend/src/metabase/qb/components/actions/index.js
@@ -3,11 +3,11 @@
 import UnderlyingDataAction from "./UnderlyingDataAction";
 import UnderlyingRecordsAction from "./UnderlyingRecordsAction";
 import XRayCard from "./XRayCard";
-// import GenerateDashboardAction from "./GenerateDashboardAction";
+import CompareWithTable from "./CompareWithTable";
 
 export const DEFAULT_ACTIONS = [
   UnderlyingDataAction,
   UnderlyingRecordsAction,
   XRayCard,
-  // GenerateDashboardAction,
+  CompareWithTable,
 ];
diff --git a/frontend/src/metabase/qb/components/drill/AutomaticDashboardDrill.jsx b/frontend/src/metabase/qb/components/drill/AutomaticDashboardDrill.jsx
index cc089251c8824b8e36548107c7e755256dfe5ae5..ea9a7079262c0a3c726e4a432302b32d80b026c8 100644
--- a/frontend/src/metabase/qb/components/drill/AutomaticDashboardDrill.jsx
+++ b/frontend/src/metabase/qb/components/drill/AutomaticDashboardDrill.jsx
@@ -1,7 +1,5 @@
 /* @flow */
 
-import { inflect } from "metabase/lib/formatting";
-
 import StructuredQuery from "metabase-lib/lib/queries/StructuredQuery";
 import { t } from "c-3po";
 import type {
@@ -21,18 +19,12 @@ export default ({ question, clicked }: ClickActionProps): ClickAction[] => {
     return [];
   }
 
-  // the metric value should be the number of rows that will be displayed
-  const count = typeof clicked.value === "number" ? clicked.value : 2;
-
   return [
     {
       name: "exploratory-dashboard",
       section: "auto",
       icon: "bolt",
-      title: t`X-ray ${inflect(t`these`, count, t`this`, t`these`)} ${inflect(
-        query.table().display_name,
-        count,
-      )}`,
+      title: t`X-ray`,
       url: () => {
         const filters = query
           .clearFilters() // clear existing filters so we don't duplicate them
diff --git a/frontend/src/metabase/qb/components/drill/CompareToRestDrill.js b/frontend/src/metabase/qb/components/drill/CompareToRestDrill.js
new file mode 100644
index 0000000000000000000000000000000000000000..1a237e3725ec3a49737d1891a2aa60834b62e8f5
--- /dev/null
+++ b/frontend/src/metabase/qb/components/drill/CompareToRestDrill.js
@@ -0,0 +1,39 @@
+/* @flow */
+
+import StructuredQuery from "metabase-lib/lib/queries/StructuredQuery";
+import { t } from "c-3po";
+import type {
+  ClickAction,
+  ClickActionProps,
+} from "metabase/meta/types/Visualization";
+
+export default ({ question, clicked }: ClickActionProps): ClickAction[] => {
+  const query = question.query();
+  if (!(query instanceof StructuredQuery)) {
+    return [];
+  }
+
+  // questions with a breakout
+  const dimensions = (clicked && clicked.dimensions) || [];
+  if (!clicked || dimensions.length === 0) {
+    return [];
+  }
+
+  return [
+    {
+      name: "compare-dashboard",
+      section: "auto",
+      icon: "bolt",
+      title: t`Compare to the rest`,
+      url: () => {
+        const filters = query
+          .clearFilters() // clear existing filters so we don't duplicate them
+          .question()
+          .drillUnderlyingRecords(dimensions)
+          .query()
+          .filters();
+        return question.getComparisonDashboardUrl(filters);
+      },
+    },
+  ];
+};
diff --git a/frontend/src/metabase/qb/components/drill/SortAction.jsx b/frontend/src/metabase/qb/components/drill/SortAction.jsx
index 5fe8be5433204e072363c6fd07060bef9840b6cf..6809c30bedc564c10e098ea793ce5ecefea8bc4f 100644
--- a/frontend/src/metabase/qb/components/drill/SortAction.jsx
+++ b/frontend/src/metabase/qb/components/drill/SortAction.jsx
@@ -29,33 +29,25 @@ export default ({ question, clicked }: ClickActionProps): ClickAction[] => {
     return [];
   }
 
-  const [sortFieldRef, sortDirection] = query.sorts()[0] || [];
+  const [sortDirection, sortFieldRef] = query.sorts()[0] || [];
   const isAlreadySorted =
     sortFieldRef != null && Query.isSameField(sortFieldRef, fieldRef);
 
   const actions = [];
-  if (
-    !isAlreadySorted ||
-    sortDirection === "descending" ||
-    sortDirection === "desc"
-  ) {
+  if (!isAlreadySorted || sortDirection === "desc") {
     actions.push({
       name: "sort-ascending",
       section: "sort",
       title: t`Ascending`,
-      question: () => query.replaceSort([fieldRef, "ascending"]).question(),
+      question: () => query.replaceSort(["asc", fieldRef]).question(),
     });
   }
-  if (
-    !isAlreadySorted ||
-    sortDirection === "ascending" ||
-    sortDirection === "asc"
-  ) {
+  if (!isAlreadySorted || sortDirection === "asc") {
     actions.push({
       name: "sort-descending",
       section: "sort",
       title: t`Descending`,
-      question: () => query.replaceSort([fieldRef, "descending"]).question(),
+      question: () => query.replaceSort(["desc", fieldRef]).question(),
     });
   }
   return actions;
diff --git a/frontend/src/metabase/qb/components/drill/index.js b/frontend/src/metabase/qb/components/drill/index.js
index 6e696f4dc2029956f665956a45970c829516246c..8df6dc2bb20ed281852e2cfc5624dbe8963052e4 100644
--- a/frontend/src/metabase/qb/components/drill/index.js
+++ b/frontend/src/metabase/qb/components/drill/index.js
@@ -5,6 +5,7 @@ import ObjectDetailDrill from "./ObjectDetailDrill";
 import QuickFilterDrill from "./QuickFilterDrill";
 import UnderlyingRecordsDrill from "./UnderlyingRecordsDrill";
 import AutomaticDashboardDrill from "./AutomaticDashboardDrill";
+import CompareToRestDrill from "./CompareToRestDrill";
 import ZoomDrill from "./ZoomDrill";
 
 export const DEFAULT_DRILLS = [
@@ -14,4 +15,5 @@ export const DEFAULT_DRILLS = [
   QuickFilterDrill,
   UnderlyingRecordsDrill,
   AutomaticDashboardDrill,
+  CompareToRestDrill,
 ];
diff --git a/frontend/src/metabase/qb/lib/actions.js b/frontend/src/metabase/qb/lib/actions.js
index 1e2fcdbad07225baf00ecfb05881284474d9c99c..4e3b90c3f94970dc6c06109c67638af64ada7cc1 100644
--- a/frontend/src/metabase/qb/lib/actions.js
+++ b/frontend/src/metabase/qb/lib/actions.js
@@ -51,7 +51,7 @@ export const toUnderlyingRecords = (card: CardObject): ?CardObject => {
     const newCard = startNewCard(
       "query",
       card.dataset_query.database,
-      query.source_table,
+      query["source-table"],
     );
     newCard.dataset_query.query.filter = query.filter;
     return newCard;
@@ -244,7 +244,7 @@ export const distribution = (card, column) => {
   newCard.dataset_query = Utils.copy(card.dataset_query);
   newCard.dataset_query.query.aggregation = [["count"]];
   newCard.dataset_query.query.breakout = [breakout];
-  delete newCard.dataset_query.query.order_by;
+  delete newCard.dataset_query.query["order-by"];
   delete newCard.dataset_query.query.fields;
   newCard.display = "bar";
   return newCard;
diff --git a/frontend/src/metabase/qb/lib/modes.js b/frontend/src/metabase/qb/lib/modes.js
index f90b3c9823ef3d54e25ea5ad955c339af4c114f7..6658ac29ffae709314e0cc637e689f7594d8882a 100644
--- a/frontend/src/metabase/qb/lib/modes.js
+++ b/frontend/src/metabase/qb/lib/modes.js
@@ -52,7 +52,11 @@ export function getMode(
         if (tableMetadata && Array.isArray(filter) && filter[0] === "=") {
           const fieldId = Q_DEPRECATED.getFieldTargetId(filter[1]);
           const field = tableMetadata.fields_lookup[fieldId];
-          if (field && field.table.id === query.source_table && isPK(field)) {
+          if (
+            field &&
+            field.table.id === query["source-table"] &&
+            isPK(field)
+          ) {
             return true;
           }
         }
diff --git a/frontend/src/metabase/query_builder/actions.js b/frontend/src/metabase/query_builder/actions.js
index 78b4490c4f8f2a3fc4dc71afc5b4cf3070ee3c1b..9550bd2228b359dd404732e3dfdba6ab047b6dc5 100644
--- a/frontend/src/metabase/query_builder/actions.js
+++ b/frontend/src/metabase/query_builder/actions.js
@@ -5,7 +5,7 @@ declare var ace: any;
 
 import { createAction } from "redux-actions";
 import _ from "underscore";
-import { assocIn } from "icepick";
+import { updateIn } from "icepick";
 
 import * as Urls from "metabase/lib/urls";
 
@@ -24,11 +24,11 @@ import {
 } from "metabase/lib/card";
 import { formatSQL } from "metabase/lib/formatting";
 import Query, { createQuery } from "metabase/lib/query";
+import { syncQueryFields, getExistingFields } from "metabase/lib/dataset";
 import { isPK } from "metabase/lib/types";
 import Utils from "metabase/lib/utils";
 import { getEngineNativeType, formatJsonQuery } from "metabase/lib/engine";
 import { defer } from "metabase/lib/promise";
-import { addUndo } from "metabase/redux/undo";
 import Question from "metabase-lib/lib/Question";
 import { cardIsEquivalent, cardQueryIsEquivalent } from "metabase/meta/Card";
 
@@ -46,7 +46,6 @@ import {
 } from "./selectors";
 
 import {
-  getDatabases,
   getTables,
   getDatabasesList,
   getMetadata,
@@ -66,6 +65,8 @@ import NativeQuery from "metabase-lib/lib/queries/NativeQuery";
 import { getPersistableDefaultSettings } from "metabase/visualizations/lib/settings";
 import { clearRequestState } from "metabase/redux/requests";
 
+import Questions from "metabase/entities/questions";
+
 type UiControls = {
   isEditing?: boolean,
   isShowingTemplateTagsEditor?: boolean,
@@ -310,19 +311,19 @@ export const initializeQB = (location, params) => {
 
       // initialize parts of the query based on optional parameters supplied
       if (options.table != undefined && card.dataset_query.query) {
-        card.dataset_query.query.source_table = parseInt(options.table);
+        card.dataset_query.query["source-table"] = parseInt(options.table);
       }
 
       if (options.segment != undefined && card.dataset_query.query) {
         card.dataset_query.query.filter = [
-          "AND",
-          ["SEGMENT", parseInt(options.segment)],
+          "and",
+          ["segment", parseInt(options.segment)],
         ];
       }
 
       if (options.metric != undefined && card.dataset_query.query) {
         card.dataset_query.query.aggregation = [
-          "METRIC",
+          "metric",
           parseInt(options.metric),
         ];
       }
@@ -520,7 +521,13 @@ export const loadDatabaseFields = createThunkAction(
   },
 );
 
-function updateVisualizationSettings(card, isEditing, display, vizSettings) {
+function updateVisualizationSettings(
+  card,
+  isEditing,
+  display,
+  vizSettings,
+  result,
+) {
   // don't need to store undefined
   vizSettings = Utils.copy(vizSettings);
   for (const name in vizSettings) {
@@ -549,6 +556,10 @@ function updateVisualizationSettings(card, isEditing, display, vizSettings) {
   updatedCard.display = display;
   updatedCard.visualization_settings = vizSettings;
 
+  if (result && result.data && result.data.cols) {
+    syncQueryFields(updatedCard, result.data.cols);
+  }
+
   return updatedCard;
 }
 
@@ -569,6 +580,7 @@ export const setCardVisualization = createThunkAction(
         uiControls.isEditing,
         display,
         card.visualization_settings,
+        getFirstQueryResult(getState()),
       );
       dispatch(updateUrl(updatedCard, { dirty: true }));
       return updatedCard;
@@ -588,6 +600,7 @@ export const updateCardVisualizationSettings = createThunkAction(
         uiControls.isEditing,
         card.display,
         { ...card.visualization_settings, ...settings },
+        getFirstQueryResult(getState()),
       );
       dispatch(updateUrl(updatedCard, { dirty: true }));
       return updatedCard;
@@ -607,6 +620,7 @@ export const replaceAllCardVisualizationSettings = createThunkAction(
         uiControls.isEditing,
         card.display,
         settings,
+        getFirstQueryResult(getState()),
       );
       dispatch(updateUrl(updatedCard, { dirty: true }));
       return updatedCard;
@@ -630,10 +644,11 @@ export const updateTemplateTag = createThunkAction(
         delete updatedCard.description;
       }
 
-      return assocIn(
+      // using updateIn instead of assocIn due to not preserving order of keys
+      return updateIn(
         updatedCard,
-        ["dataset_query", "native", "template_tags", templateTag.name],
-        templateTag,
+        ["dataset_query", "native", "template-tags"],
+        tags => ({ ...tags, [templateTag.name]: templateTag }),
       );
     };
   },
@@ -775,7 +790,7 @@ export const apiCreateQuestion = question => {
     const createdQuestion = await questionWithVizSettings
       .setQuery(question.query().clean())
       .setResultsMetadata(resultsMetadata)
-      .apiCreate();
+      .reduxCreate(dispatch);
 
     // remove the databases in the store that are used to populate the QB databases list.
     // This is done when saving a Card because the newly saved card will be eligible for use as a source query
@@ -808,7 +823,7 @@ export const apiUpdateQuestion = question => {
     const updatedQuestion = await questionWithVizSettings
       .setQuery(question.query().clean())
       .setResultsMetadata(resultsMetadata)
-      .apiUpdate();
+      .reduxUpdate(dispatch);
 
     // reload the question alerts for the current question
     // (some of the old alerts might be removed during update)
@@ -962,7 +977,6 @@ export const setQueryDatabase = createThunkAction(
   databaseId => {
     return async (dispatch, getState) => {
       const { qb: { card, uiControls } } = getState();
-      const databases = getDatabases(getState());
 
       // picking the same database doesn't change anything
       if (databaseId === card.dataset_query.database) {
@@ -976,18 +990,18 @@ export const setQueryDatabase = createThunkAction(
         let updatedCard = startNewCard(card.dataset_query.type, databaseId);
         if (existingQuery) {
           updatedCard.dataset_query.native.query = existingQuery;
-          updatedCard.dataset_query.native.template_tags =
-            card.dataset_query.native.template_tags;
+          updatedCard.dataset_query.native["template-tags"] =
+            card.dataset_query.native["template-tags"];
         }
 
         // set the initial collection for the query if this is a native query
         // this is only used for Mongo queries which need to be ran against a specific collection
-        if (updatedCard.dataset_query.type === "native") {
-          let database = databases[databaseId],
-            tables = database ? database.tables : [],
-            table = tables.length > 0 ? tables[0] : null;
-          if (table) {
-            updatedCard.dataset_query.native.collection = table.name;
+        const question = new Question(getMetadata(getState()), updatedCard);
+        const query = question.query();
+        if (query instanceof NativeQuery && query.requiresTable()) {
+          const tables = query.tables();
+          if (tables && tables.length > 0) {
+            updatedCard.dataset_query.native.collection = tables[0].name;
           }
         }
 
@@ -1004,8 +1018,8 @@ export const setQueryDatabase = createThunkAction(
         );
         if (existingQuery) {
           updatedCard.dataset_query.native.query = existingQuery;
-          updatedCard.dataset_query.native.template_tags =
-            card.dataset_query.native.template_tags;
+          updatedCard.dataset_query.native["template-tags"] =
+            card.dataset_query.native["template-tags"];
         }
 
         dispatch(loadMetadataForCard(updatedCard));
@@ -1031,7 +1045,7 @@ export const setQuerySourceTable = createThunkAction(
       const tableId = sourceTable.id || sourceTable;
 
       // if the table didn't actually change then nothing is modified
-      if (tableId === card.dataset_query.query.source_table) {
+      if (tableId === card.dataset_query.query["source-table"]) {
         return card;
       }
 
@@ -1238,7 +1252,9 @@ export const getDisplayTypeForCard = (card, queryResults) => {
   // try a little logic to pick a smart display for the data
   // TODO: less hard-coded rules for picking chart type
   const isScalarVisualization =
-    card.display === "scalar" || card.display === "progress";
+    card.display === "scalar" ||
+    card.display === "progress" ||
+    card.display === "gauge";
   if (
     !isScalarVisualization &&
     queryResult.data.rows &&
@@ -1344,10 +1360,10 @@ export const followForeignKey = createThunkAction(FOLLOW_FOREIGN_KEY, fk => {
     // action is on an FK column
     let newCard = startNewCard("query", card.dataset_query.database);
 
-    newCard.dataset_query.query.source_table = fk.origin.table.id;
+    newCard.dataset_query.query["source-table"] = fk.origin.table.id;
     newCard.dataset_query.query.aggregation = ["rows"];
     newCard.dataset_query.query.filter = [
-      "AND",
+      "and",
       ["=", fk.origin.id, originValue],
     ];
 
@@ -1378,10 +1394,10 @@ export const loadObjectDetailFKReferences = createThunkAction(
       async function getFKCount(card, queryResult, fk) {
         let fkQuery = createQuery("query");
         fkQuery.database = card.dataset_query.database;
-        fkQuery.query.source_table = fk.origin.table_id;
+        fkQuery.query["source-table"] = fk.origin.table_id;
         fkQuery.query.aggregation = ["count"];
         fkQuery.query.filter = [
-          "AND",
+          "and",
           ["=", fk.origin.id, getObjectDetailIdValue(queryResult.data)],
         ];
 
@@ -1424,27 +1440,45 @@ export const loadObjectDetailFKReferences = createThunkAction(
   },
 );
 
+const ADD_FIELD = "metabase/qb/ADD_FIELD";
+export const addField = createThunkAction(
+  ADD_FIELD,
+  (field, run = true) => (dispatch, getState) => {
+    const { qb: { card } } = getState();
+    const queryResult = getFirstQueryResult(getState());
+    if (
+      card.dataset_query.type === "query" &&
+      queryResult &&
+      queryResult.data
+    ) {
+      dispatch(
+        setDatasetQuery(
+          {
+            ...card.dataset_query,
+            query: {
+              ...card.dataset_query.query,
+              fields: getExistingFields(card, queryResult.data.cols).concat([
+                field,
+              ]),
+            },
+          },
+          true,
+        ),
+      );
+    }
+  },
+);
+
 // DEPRECATED: use metabase/entities/questions
 export const ARCHIVE_QUESTION = "metabase/qb/ARCHIVE_QUESTION";
 export const archiveQuestion = createThunkAction(
   ARCHIVE_QUESTION,
   (questionId, archived = true) => async (dispatch, getState) => {
-    let card = {
-      ...getState().qb.card, // grab the current card
-      archived,
-    };
-    let response = await CardApi.update(card);
+    let card = getState().qb.card;
 
-    dispatch(
-      addUndo({
-        verb: archived ? "archived" : "unarchived",
-        subject: "question",
-        action: archiveQuestion(card.id, !archived),
-      }),
-    );
+    await dispatch(Questions.actions.setArchived({ id: card.id }, archived));
 
     dispatch(push(Urls.collection(card.collection_id)));
-    return response;
   },
 );
 
diff --git a/frontend/src/metabase/query_builder/components/ActionsWidget.jsx b/frontend/src/metabase/query_builder/components/ActionsWidget.jsx
index df7affdd4a293acf28d4fba23683d413e8560a03..12215a83a268377df7d65b22ab701c9f6fc36e97 100644
--- a/frontend/src/metabase/query_builder/components/ActionsWidget.jsx
+++ b/frontend/src/metabase/query_builder/components/ActionsWidget.jsx
@@ -6,6 +6,7 @@ import Icon from "metabase/components/Icon";
 import OnClickOutsideWrapper from "metabase/components/OnClickOutsideWrapper";
 
 import MetabaseAnalytics from "metabase/lib/analytics";
+import colors from "metabase/lib/colors";
 
 import cx from "classnames";
 import _ from "underscore";
@@ -155,7 +156,7 @@ export default class ActionsWidget extends Component {
             height: CIRCLE_SIZE,
             transition: "opacity 300ms ease-in-out",
             opacity: popoverIsOpen || iconIsVisible ? 1 : 0,
-            boxShadow: "2px 2px 4px rgba(0, 0, 0, 0.2)",
+            boxShadow: `2px 2px 4px ${colors["shadow"]}`,
           }}
           onClick={this.toggle}
         >
@@ -189,7 +190,7 @@ export default class ActionsWidget extends Component {
             >
               {PopoverComponent ? (
                 <div>
-                  <div className="flex align-center text-grey-4 p1 px2">
+                  <div className="flex align-center text-medium p1 px2">
                     <Icon
                       name="chevronleft"
                       className="cursor-pointer"
@@ -224,7 +225,7 @@ export default class ActionsWidget extends Component {
                 actions.map((action, index) => (
                   <div
                     key={index}
-                    className="p2 flex align-center text-grey-4 brand-hover cursor-pointer"
+                    className="p2 flex align-center text-medium brand-hover cursor-pointer"
                     onClick={() => this.handleActionClick(index)}
                   >
                     {action.icon && (
diff --git a/frontend/src/metabase/query_builder/components/AddClauseButton.jsx b/frontend/src/metabase/query_builder/components/AddClauseButton.jsx
index 866a2e4e2fc6b750af70f4a84125a8579ca27380..fac84a957e902f97513baa4f3aeb9930761816d7 100644
--- a/frontend/src/metabase/query_builder/components/AddClauseButton.jsx
+++ b/frontend/src/metabase/query_builder/components/AddClauseButton.jsx
@@ -22,7 +22,7 @@ export default class AddClauseButton extends Component {
     const { text, onClick } = this.props;
 
     const className =
-      "text-grey-2 text-bold flex align-center text-grey-4-hover cursor-pointer no-decoration transition-color";
+      "text-light text-bold flex align-center text-medium-hover cursor-pointer no-decoration transition-color";
     if (onClick) {
       return (
         <a className={className} onClick={onClick}>
diff --git a/frontend/src/metabase/query_builder/components/AggregationPopover.jsx b/frontend/src/metabase/query_builder/components/AggregationPopover.jsx
index 996e17c994391739a5a6ae6f190b98e833d4dabb..82109566e23fa5f5944aaf5dfaa7f9ef820f13d4 100644
--- a/frontend/src/metabase/query_builder/components/AggregationPopover.jsx
+++ b/frontend/src/metabase/query_builder/components/AggregationPopover.jsx
@@ -201,7 +201,7 @@ export default class AggregationPopover extends Component {
         tableMetadata.metrics &&
         tableMetadata.metrics.filter(
           mtrc =>
-            mtrc.archived === false ||
+            !mtrc.archived ||
             (selectedAggregation && selectedAggregation.id === mtrc.id),
         );
       if (metrics && metrics.length > 0) {
@@ -209,7 +209,7 @@ export default class AggregationPopover extends Component {
           name: METRICS_SECTION_NAME,
           items: metrics.map(metric => ({
             name: metric.name,
-            value: ["METRIC", metric.id],
+            value: ["metric", metric.id],
             isSelected: aggregation =>
               AggregationClause.getMetric(aggregation) === metric.id,
             metric: metric,
@@ -234,7 +234,7 @@ export default class AggregationPopover extends Component {
     if (editingAggregation) {
       return (
         <div style={{ width: editingAggregation ? 500 : 300 }}>
-          <div className="text-grey-3 p1 py2 border-bottom flex align-center">
+          <div className="text-medium p1 py2 border-bottom flex align-center">
             <a
               className="cursor-pointer flex align-center"
               onClick={this.onClearAggregation}
@@ -306,7 +306,7 @@ export default class AggregationPopover extends Component {
         <div style={{ minWidth: 300 }}>
           <div
             ref={_ => (this._header = _)}
-            className="text-grey-3 p1 py2 border-bottom flex align-center"
+            className="text-medium p1 py2 border-bottom flex align-center"
           >
             <a
               className="cursor-pointer flex align-center"
@@ -339,7 +339,7 @@ export default class AggregationPopover extends Component {
           renderSectionIcon={s => <Icon name={s.icon} size={18} />}
           renderItemExtra={this.renderItemExtra.bind(this)}
           getItemClasses={item =>
-            item.metric && item.metric.archived ? "text-grey-3" : null
+            item.metric && item.metric.archived ? "text-medium" : null
           }
           onChangeSection={index => {
             if (index === customExpressionIndex) {
diff --git a/frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx b/frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx
index 23ebc1abe6eaa1a2828252abb6911d5a33a6499f..59e0caff979f5c77ba522050df839c3c3f6b9f3e 100644
--- a/frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx
+++ b/frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx
@@ -192,7 +192,7 @@ export class AlertListItem extends Component {
 
     return (
       <li
-        className={cx("flex p3 text-grey-4 border-bottom", {
+        className={cx("flex p3 text-medium border-bottom", {
           "bg-light-blue": highlight,
         })}
       >
@@ -266,7 +266,7 @@ export class AlertListItem extends Component {
 
 export const UnsubscribedListItem = () => (
   <li className="border-bottom flex align-center py4 text-bold">
-    <div className="circle flex align-center justify-center p1 bg-grey-0 ml2">
+    <div className="circle flex align-center justify-center p1 bg-light ml2">
       <Icon name="check" className="text-success" />
     </div>
     <h3
diff --git a/frontend/src/metabase/query_builder/components/AlertModals.jsx b/frontend/src/metabase/query_builder/components/AlertModals.jsx
index ac628c037dfdbb50b1b08e793b134825e67c005d..19648d5b0e58211963df0a73f07df033349281e4 100644
--- a/frontend/src/metabase/query_builder/components/AlertModals.jsx
+++ b/frontend/src/metabase/query_builder/components/AlertModals.jsx
@@ -17,10 +17,12 @@ import ButtonWithStatus from "metabase/components/ButtonWithStatus";
 import PulseEditChannels from "metabase/pulse/components/PulseEditChannels";
 import RetinaImage from "react-retina-image";
 
+import { entityListLoader } from "metabase/entities/containers/EntityListLoader";
+
 // actions
 import { createAlert, deleteAlert, updateAlert } from "metabase/alert/alert";
 import { apiUpdateQuestion } from "metabase/query_builder/actions";
-import { fetchPulseFormInput, fetchUsers } from "metabase/pulse/actions";
+import { fetchPulseFormInput } from "metabase/pulse/actions";
 
 // selectors
 import { getUser, getUserIsAdmin } from "metabase/selectors/user";
@@ -29,11 +31,10 @@ import {
   getVisualizationSettings,
 } from "metabase/query_builder/selectors";
 import {
-  formInputSelector,
+  getPulseFormInput,
   hasConfiguredAnyChannelSelector,
   hasConfiguredEmailChannelSelector,
   hasLoadedChannelInfoSelector,
-  userListSelector,
 } from "metabase/pulse/selectors";
 
 // lib
@@ -557,7 +558,7 @@ export class AlertEditSchedule extends Component {
 
         <div className="bordered rounded mb2">
           {alertType === ALERT_TYPE_ROWS && <RawDataAlertTip />}
-          <div className="p3 bg-grey-0">
+          <div className="p3 bg-light">
             <SchedulePicker
               schedule={schedule}
               scheduleOptions={["hourly", "daily", "weekly"]}
@@ -571,28 +572,28 @@ export class AlertEditSchedule extends Component {
   }
 }
 
+@entityListLoader({ entityType: "users" })
 @connect(
-  state => ({
+  (state, props) => ({
     user: getUser(state),
-    userList: userListSelector(state),
-    formInput: formInputSelector(state),
+    formInput: getPulseFormInput(state),
   }),
-  { fetchPulseFormInput, fetchUsers },
+  {
+    fetchPulseFormInput,
+  },
 )
 export class AlertEditChannels extends Component {
   props: {
     onChannelsChange: any => void,
     user: any,
-    userList: any[],
+    users: any[],
     // this stupidly named property contains different channel options, nothing else
     formInput: any,
-    fetchPulseFormInput: () => void,
-    fetchUsers: () => void,
+    fetchPulseFormInput: () => Promise<void>,
   };
 
   componentDidMount() {
     this.props.fetchPulseFormInput();
-    this.props.fetchUsers();
   }
 
   // Technically pulse definition is equal to alert definition
@@ -610,7 +611,7 @@ export class AlertEditChannels extends Component {
   };
 
   render() {
-    const { alert, user, userList, formInput } = this.props;
+    const { alert, user, users, formInput } = this.props;
     return (
       <div className="mt4 pt2">
         <h3 className="text-dark mb3">{jt`Where do you want to send these alerts?`}</h3>
@@ -621,7 +622,7 @@ export class AlertEditChannels extends Component {
             pulseIsValid={true}
             formInput={formInput}
             user={user}
-            userList={userList}
+            users={users}
             setPulse={this.onSetPulse}
             hideSchedulePicker={true}
             emailRecipientText={t`Email alerts to:`}
@@ -652,7 +653,7 @@ export class RawDataAlertTip extends Component {
 
     return (
       <div className="border-row-divider p3 flex align-center">
-        <div className="circle flex align-center justify-center bg-grey-0 p2 mr2 text-grey-3">
+        <div className="circle flex align-center justify-center bg-light p2 mr2 text-medium">
           <Icon name="lightbulb" size="20" />
         </div>
         {showMultiSeriesGoalAlert ? (
diff --git a/frontend/src/metabase/query_builder/components/Clearable.jsx b/frontend/src/metabase/query_builder/components/Clearable.jsx
index ff467956e97c285cb323413515f7948de009f2b6..12c205e56ad92a14c18dd04b43a0beddbc1252eb 100644
--- a/frontend/src/metabase/query_builder/components/Clearable.jsx
+++ b/frontend/src/metabase/query_builder/components/Clearable.jsx
@@ -8,7 +8,7 @@ const Clearable = ({ onClear, children, className }) => (
     {children}
     {onClear && (
       <a
-        className="text-grey-2 no-decoration pr1 flex align-center"
+        className="text-light no-decoration pr1 flex align-center"
         onClick={onClear}
       >
         <Icon name="close" size={14} />
diff --git a/frontend/src/metabase/query_builder/components/DataSelector.jsx b/frontend/src/metabase/query_builder/components/DataSelector.jsx
index 1c6cff6889b2e3d399b01e7a7c6d3c69896c13e3..66d485e91c963fc45221d5e7c5675902a8d8e271 100644
--- a/frontend/src/metabase/query_builder/components/DataSelector.jsx
+++ b/frontend/src/metabase/query_builder/components/DataSelector.jsx
@@ -54,7 +54,7 @@ export const SchemaAndSegmentTriggerContent = ({
     );
   } else {
     return (
-      <span className="text-grey-4 no-decoration">{t`Pick a segment or table`}</span>
+      <span className="text-medium no-decoration">{t`Pick a segment or table`}</span>
     );
   }
 };
@@ -70,7 +70,7 @@ export const DatabaseTriggerContent = ({ selectedDatabase }) =>
   selectedDatabase ? (
     <span className="text-grey no-decoration">{selectedDatabase.name}</span>
   ) : (
-    <span className="text-grey-4 no-decoration">{t`Select a database`}</span>
+    <span className="text-medium no-decoration">{t`Select a database`}</span>
   );
 
 export const SchemaTableAndFieldDataSelector = props => (
@@ -85,7 +85,7 @@ export const SchemaTableAndFieldDataSelector = props => (
 export const FieldTriggerContent = ({ selectedDatabase, selectedField }) => {
   if (!selectedField || !selectedField.table) {
     return (
-      <span className="flex-full text-grey-4 no-decoration">{t`Select...`}</span>
+      <span className="flex-full text-medium no-decoration">{t`Select...`}</span>
     );
   } else {
     const hasMultipleSchemas =
@@ -93,7 +93,7 @@ export const FieldTriggerContent = ({ selectedDatabase, selectedField }) => {
       _.uniq(selectedDatabase.tables, t => t.schema).length > 1;
     return (
       <div className="flex-full cursor-pointer">
-        <div className="h6 text-bold text-uppercase text-grey-2">
+        <div className="h6 text-bold text-uppercase text-light">
           {hasMultipleSchemas && selectedField.table.schema + " > "}
           {selectedField.table.display_name}
         </div>
@@ -125,7 +125,7 @@ export const TableTriggerContent = ({ selectedTable }) =>
       {selectedTable.display_name || selectedTable.name}
     </span>
   ) : (
-    <span className="text-grey-4 no-decoration">{t`Select a table`}</span>
+    <span className="text-medium no-decoration">{t`Select a table`}</span>
   );
 
 @connect(state => ({ metadata: getMetadata(state) }), { fetchTableMetadata })
@@ -548,6 +548,7 @@ export default class DataSelector extends Component {
         triggerElement={this.getTriggerElement()}
         triggerClasses={triggerClasses}
         horizontalAttachments={["center", "left", "right"]}
+        sizeToFit
       >
         {this.renderActiveStep()}
       </PopoverWithTrigger>
@@ -697,7 +698,7 @@ export const DatabaseSchemaPicker = ({
   const sections = databases.map(database => ({
     name: database.name,
     items: database.schemas.length > 1 ? database.schemas : [],
-    className: database.is_saved_questions ? "bg-slate-extra-light" : null,
+    className: database.is_saved_questions ? "bg-light" : null,
     icon: database.is_saved_questions ? "all" : "database",
   }));
 
@@ -817,7 +818,7 @@ export const TablePicker = ({
           showItemArrows={hasAdjacentStep}
         />
         {isSavedQuestionList && (
-          <div className="bg-slate-extra-light p2 text-centered border-top">
+          <div className="bg-light p2 text-centered border-top">
             {t`Is a question missing?`}
             <a
               href="http://metabase.com/docs/latest/users-guide/04-asking-questions.html#source-data"
diff --git a/frontend/src/metabase/query_builder/components/ExtendedOptions.jsx b/frontend/src/metabase/query_builder/components/ExtendedOptions.jsx
index b307fe65f1b83461c6ecf27a953c8ac20aaa4908..5f7e7090b8837c1d61c3e52d1ed1931b7371dc56 100644
--- a/frontend/src/metabase/query_builder/components/ExtendedOptions.jsx
+++ b/frontend/src/metabase/query_builder/components/ExtendedOptions.jsx
@@ -111,7 +111,7 @@ export default class ExtendedOptions extends Component {
             text={t`Pick a field to sort by`}
             onClick={() => {
               // $FlowFixMe: shouldn't be adding a sort with null field
-              query.addSort([null, "ascending"]).update(setDatasetQuery);
+              query.addSort(["asc", null]).update(setDatasetQuery);
             }}
           />
         );
@@ -121,7 +121,7 @@ export default class ExtendedOptions extends Component {
     if ((sortList && sortList.length > 0) || addSortButton) {
       return (
         <div className="pb3">
-          <div className="pb1 h6 text-uppercase text-grey-3 text-bold">{t`Sort`}</div>
+          <div className="pb1 h6 text-uppercase text-medium text-bold">{t`Sort`}</div>
           {sortList}
           {addSortButton}
         </div>
@@ -190,7 +190,7 @@ export default class ExtendedOptions extends Component {
 
           {features.limit && (
             <div>
-              <div className="mb1 h6 text-uppercase text-grey-3 text-bold">{t`Row limit`}</div>
+              <div className="mb1 h6 text-uppercase text-medium text-bold">{t`Row limit`}</div>
               <LimitWidget limit={query.limit()} onChange={this.setLimit} />
             </div>
           )}
@@ -212,7 +212,7 @@ export default class ExtendedOptions extends Component {
     return (
       <div className="GuiBuilder-section GuiBuilder-sort-limit flex align-center">
         <span
-          className={cx("EllipsisButton no-decoration text-grey-1 px1", {
+          className={cx("EllipsisButton no-decoration text-light px1", {
             "cursor-pointer": onClick,
           })}
           onClick={onClick}
diff --git a/frontend/src/metabase/query_builder/components/FieldList.jsx b/frontend/src/metabase/query_builder/components/FieldList.jsx
index eca0c89a8aa02cca1c5dabb475951e4eb25eda1a..09fad529db47b9bf17bf3d7bb2fa46e46e4fa0a3 100644
--- a/frontend/src/metabase/query_builder/components/FieldList.jsx
+++ b/frontend/src/metabase/query_builder/components/FieldList.jsx
@@ -68,7 +68,7 @@ export default class FieldList extends Component {
     if (segmentOptions) {
       specialOptions = segmentOptions.map(segment => ({
         name: segment.name,
-        value: ["SEGMENT", segment.id],
+        value: ["segment", segment.id],
         segment: segment,
       }));
     }
@@ -116,7 +116,7 @@ export default class FieldList extends Component {
         {item.segment && this.renderSegmentTooltip(item.segment)}
         {item.dimension &&
           item.dimension.tag && (
-            <span className="h5 text-grey-2 px1">{item.dimension.tag}</span>
+            <span className="h5 text-light px1">{item.dimension.tag}</span>
           )}
         {enableSubDimensions &&
         item.dimension &&
diff --git a/frontend/src/metabase/query_builder/components/FieldName.jsx b/frontend/src/metabase/query_builder/components/FieldName.jsx
index 7c178b68f162ef04370fbee087e7e082ef45b82f..447375b202915a30a8f820f1d9b6e33b224e1142 100644
--- a/frontend/src/metabase/query_builder/components/FieldName.jsx
+++ b/frontend/src/metabase/query_builder/components/FieldName.jsx
@@ -76,7 +76,7 @@ export default class FieldName extends Component {
         parts.push(<span key="field">{t`Unknown Field`}</span>);
       }
     } else {
-      parts.push(<span key="field" className={"text-grey-2"}>{t`field`}</span>);
+      parts.push(<span key="field" className={"text-light"}>{t`field`}</span>);
     }
 
     const content = (
diff --git a/frontend/src/metabase/query_builder/components/Filter.jsx b/frontend/src/metabase/query_builder/components/Filter.jsx
index a4e5824ad0725104dd71f2fa8f255c0ab4cb0d4e..1394b652ad02dc0cfea0f79169f6d252110be63b 100644
--- a/frontend/src/metabase/query_builder/components/Filter.jsx
+++ b/frontend/src/metabase/query_builder/components/Filter.jsx
@@ -116,7 +116,7 @@ export const SegmentFilter = ({
 };
 
 const Filter = ({ filter, ...props }: Props) =>
-  filter[0] === "SEGMENT" ? (
+  filter[0] === "segment" ? (
     <SegmentFilter filter={filter} {...props} />
   ) : (
     <OperatorFilter filter={filter} {...props} />
diff --git a/frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx b/frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx
index 70f0e54048d20b1ac9fe80b9192f96c8b5399a69..a821aceebe1799b14b324dc7174d8e94845250bf 100644
--- a/frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx
+++ b/frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx
@@ -91,7 +91,7 @@ export default class GuiQueryEditor extends Component {
 
   renderAdd(text: ?string, onClick: ?() => void, targetRefName?: string) {
     let className =
-      "AddButton text-grey-2 text-bold flex align-center text-grey-4-hover cursor-pointer no-decoration transition-color";
+      "AddButton text-light text-bold flex align-center text-medium-hover cursor-pointer no-decoration transition-color";
     if (onClick) {
       return (
         <a className={className} onClick={onClick}>
@@ -309,7 +309,7 @@ export default class GuiQueryEditor extends Component {
     const datasetQuery = query.datasetQuery();
     const databaseId = datasetQuery && datasetQuery.database;
     const sourceTableId =
-      datasetQuery && datasetQuery.query && datasetQuery.query.source_table;
+      datasetQuery && datasetQuery.query && datasetQuery.query["source-table"];
     const isInitiallyOpen =
       (!datasetQuery.database || !sourceTableId) && !isShowingTutorial;
 
diff --git a/frontend/src/metabase/query_builder/components/NativeQueryEditor.css b/frontend/src/metabase/query_builder/components/NativeQueryEditor.css
index a20b288fc2b8b3bc1c3c8edc8f3cdaae5c3abed6..ed5dd63ffe97d3d71065121a12ae65adfc9ec105 100644
--- a/frontend/src/metabase/query_builder/components/NativeQueryEditor.css
+++ b/frontend/src/metabase/query_builder/components/NativeQueryEditor.css
@@ -22,7 +22,7 @@
   padding-top: 2px;
   font-size: 10px;
   font-weight: 700;
-  color: color(var(--base-grey) shade(30%));
+  color: var(--color-text-medium);
   padding-left: 0;
   padding-right: 0;
   display: block;
@@ -30,8 +30,8 @@
 }
 
 .NativeQueryEditor .ace_editor .ace_gutter {
-  background-color: #f9fbfc;
-  border-right: 1px solid var(--border-color);
+  background-color: var(--color-bg-light);
+  border-right: 1px solid var(--color-border);
   padding-left: 5px;
   padding-right: 5px;
 }
diff --git a/frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx b/frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx
index f509501b973cc38a4fe2297894a2e33f36d79c48..6d7ce8869992903dc25f8588f898c36de3e3e7e5 100644
--- a/frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx
+++ b/frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx
@@ -278,6 +278,13 @@ export default class NativeQueryEditor extends Component {
     }
   };
 
+  setParameterIndex = (parameterId: ParameterId, parameterIndex: number) => {
+    const { query, setDatasetQuery } = this.props;
+    query
+      .setParameterIndex(parameterId, parameterIndex)
+      .update(setDatasetQuery);
+  };
+
   render() {
     const { query, setParameterValue, location } = this.props;
     const database = query.database();
@@ -335,7 +342,7 @@ export default class NativeQueryEditor extends Component {
       }
     } else {
       dataSelectors = (
-        <span className="p2 text-grey-4">{t`This question is written in ${query.nativeQueryLanguage()}.`}</span>
+        <span className="p2 text-medium">{t`This question is written in ${query.nativeQueryLanguage()}.`}</span>
       );
     }
 
@@ -363,7 +370,9 @@ export default class NativeQueryEditor extends Component {
               parameters={parameters}
               query={location.query}
               setParameterValue={setParameterValue}
+              setParameterIndex={this.setParameterIndex}
               syncQueryString
+              isEditing
               isQB
               commitImmediately
             />
diff --git a/frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx b/frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx
index 29caa19ef248d3e23e938291ed6d8ece18ffffd8..e9e40562b0b80155df3f41f3cde481c10aaa9147 100644
--- a/frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx
+++ b/frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx
@@ -1,5 +1,6 @@
 import React from "react";
 import PropTypes from "prop-types";
+import { Box } from "grid-styled";
 
 import { t } from "c-3po";
 import { parse as urlParse } from "url";
@@ -10,8 +11,6 @@ import Icon from "metabase/components/Icon.jsx";
 import DownloadButton from "metabase/components/DownloadButton.jsx";
 import Tooltip from "metabase/components/Tooltip.jsx";
 
-import FieldSet from "metabase/components/FieldSet.jsx";
-
 import * as Urls from "metabase/lib/urls";
 
 import _ from "underscore";
@@ -19,48 +18,68 @@ import cx from "classnames";
 
 const EXPORT_FORMATS = ["csv", "xlsx", "json"];
 
-const QueryDownloadWidget = ({ className, card, result, uuid, token }) => (
+const QueryDownloadWidget = ({
+  className,
+  classNameClose,
+  card,
+  result,
+  uuid,
+  token,
+  dashcardId,
+  icon,
+  params,
+}) => (
   <PopoverWithTrigger
     triggerElement={
       <Tooltip tooltip={t`Download full results`}>
-        <Icon title={t`Download this data`} name="downarrow" size={16} />
+        <Icon title={t`Download this data`} name={icon} size={16} />
       </Tooltip>
     }
     triggerClasses={cx(className, "text-brand-hover")}
+    triggerClassesClose={classNameClose}
   >
-    <div className="p2" style={{ maxWidth: 320 }}>
-      <h4>{t`Download full results`}</h4>
-      {result.data.rows_truncated != null && (
-        <FieldSet className="my2 text-gold border-gold" legend={t`Warning`}>
-          <div className="my1">{t`Your answer has a large number of rows so it could take a while to download.`}</div>
-          <div>{t`The maximum download size is 1 million rows.`}</div>
-        </FieldSet>
-      )}
-      <div className="flex flex-row mt2">
-        {EXPORT_FORMATS.map(
-          type =>
-            uuid ? (
+    <Box
+      p={2}
+      w={result.data && result.data.rows_truncated != null ? 300 : 260}
+    >
+      <Box p={1}>
+        <h4>{t`Download full results`}</h4>
+      </Box>
+      {result.data != null &&
+        result.data.rows_truncated != null && (
+          <Box>
+            <p
+            >{t`Your answer has a large number of rows so it could take a while to download.`}</p>
+            <p>{t`The maximum download size is 1 million rows.`}</p>
+          </Box>
+        )}
+      <Box>
+        {EXPORT_FORMATS.map(type => (
+          <Box w={"100%"}>
+            {dashcardId && token ? (
+              <DashboardEmbedQueryButton
+                key={type}
+                type={type}
+                dashcardId={dashcardId}
+                token={token}
+                card={card}
+                params={params}
+              />
+            ) : uuid ? (
               <PublicQueryButton
                 key={type}
                 type={type}
                 uuid={uuid}
                 result={result}
-                className="mr1 text-uppercase text-default"
               />
             ) : token ? (
-              <EmbedQueryButton
-                key={type}
-                type={type}
-                token={token}
-                className="mr1 text-uppercase text-default"
-              />
+              <EmbedQueryButton key={type} type={type} token={token} />
             ) : card && card.id ? (
               <SavedQueryButton
                 key={type}
                 type={type}
                 card={card}
                 result={result}
-                className="mr1 text-uppercase text-default"
               />
             ) : card && !card.id ? (
               <UnsavedQueryButton
@@ -68,23 +87,17 @@ const QueryDownloadWidget = ({ className, card, result, uuid, token }) => (
                 type={type}
                 card={card}
                 result={result}
-                className="mr1 text-uppercase text-default"
               />
-            ) : null,
-        )}
-      </div>
-    </div>
+            ) : null}
+          </Box>
+        ))}
+      </Box>
+    </Box>
   </PopoverWithTrigger>
 );
 
-const UnsavedQueryButton = ({
-  className,
-  type,
-  result: { json_query },
-  card,
-}) => (
+const UnsavedQueryButton = ({ type, result: { json_query }, card }) => (
   <DownloadButton
-    className={className}
     url={`api/dataset/${type}`}
     params={{ query: JSON.stringify(_.omit(json_query, "constraints")) }}
     extensions={[type]}
@@ -93,14 +106,8 @@ const UnsavedQueryButton = ({
   </DownloadButton>
 );
 
-const SavedQueryButton = ({
-  className,
-  type,
-  result: { json_query },
-  card,
-}) => (
+const SavedQueryButton = ({ type, result: { json_query }, card }) => (
   <DownloadButton
-    className={className}
     url={`api/card/${card.id}/query/${type}`}
     params={{ parameters: JSON.stringify(json_query.parameters) }}
     extensions={[type]}
@@ -109,14 +116,8 @@ const SavedQueryButton = ({
   </DownloadButton>
 );
 
-const PublicQueryButton = ({
-  className,
-  type,
-  uuid,
-  result: { json_query },
-}) => (
+const PublicQueryButton = ({ type, uuid, result: { json_query } }) => (
   <DownloadButton
-    className={className}
     method="GET"
     url={Urls.publicQuestion(uuid, type)}
     params={{ parameters: JSON.stringify(json_query.parameters) }}
@@ -126,7 +127,7 @@ const PublicQueryButton = ({
   </DownloadButton>
 );
 
-const EmbedQueryButton = ({ className, type, token }) => {
+const EmbedQueryButton = ({ type, token }) => {
   // Parse the query string part of the URL (e.g. the `?key=value` part) into an object. We need to pass them this
   // way to the `DownloadButton` because it's a form which means we need to insert a hidden `<input>` for each param
   // we want to pass along. For whatever wacky reason the /api/embed endpoint expect params like ?key=value instead
@@ -136,7 +137,6 @@ const EmbedQueryButton = ({ className, type, token }) => {
 
   return (
     <DownloadButton
-      className={className}
       method="GET"
       url={Urls.embedCard(token, type)}
       params={params}
@@ -147,11 +147,37 @@ const EmbedQueryButton = ({ className, type, token }) => {
   );
 };
 
+const DashboardEmbedQueryButton = ({
+  type,
+  dashcardId,
+  token,
+  card,
+  params,
+}) => (
+  <DownloadButton
+    method="GET"
+    url={`/api/embed/dashboard/${token}/dashcard/${dashcardId}/card/${
+      card.id
+    }/${type}`}
+    extensions={[type]}
+    params={params}
+  >
+    {type}
+  </DownloadButton>
+);
+
 QueryDownloadWidget.propTypes = {
-  className: PropTypes.string,
   card: PropTypes.object,
   result: PropTypes.object,
   uuid: PropTypes.string,
+  icon: PropTypes.string,
+  params: PropTypes.object,
+};
+
+QueryDownloadWidget.defaultProps = {
+  result: {},
+  icon: "downarrow",
+  params: {},
 };
 
 export default QueryDownloadWidget;
diff --git a/frontend/src/metabase/query_builder/components/QueryHeader.jsx b/frontend/src/metabase/query_builder/components/QueryHeader.jsx
index 00d9bda0ce303ec53aedb4af976a6701c93545dc..de52bccdc84c28db08b8f4fda65fcd1bbf38c4bc 100644
--- a/frontend/src/metabase/query_builder/components/QueryHeader.jsx
+++ b/frontend/src/metabase/query_builder/components/QueryHeader.jsx
@@ -1,6 +1,5 @@
 import React, { Component } from "react";
 import PropTypes from "prop-types";
-import { Link } from "react-router";
 import { connect } from "react-redux";
 import { t } from "c-3po";
 import QueryModeButton from "./QueryModeButton.jsx";
@@ -17,15 +16,13 @@ import QuestionSavedModal from "metabase/components/QuestionSavedModal.jsx";
 import Tooltip from "metabase/components/Tooltip.jsx";
 import CollectionMoveModal from "metabase/containers/CollectionMoveModal.jsx";
 import ArchiveQuestionModal from "metabase/query_builder/containers/ArchiveQuestionModal";
+import CollectionBadge from "metabase/questions/components/CollectionBadge";
 
 import SaveQuestionModal from "metabase/containers/SaveQuestionModal.jsx";
 
 import { clearRequestState } from "metabase/redux/requests";
 
-import { CardApi, RevisionApi } from "metabase/services";
-
-import MetabaseAnalytics from "metabase/lib/analytics";
-import * as Urls from "metabase/lib/urls";
+import { RevisionApi } from "metabase/services";
 
 import cx from "classnames";
 import _ from "underscore";
@@ -40,10 +37,16 @@ import {
 import { getUser } from "metabase/home/selectors";
 import { fetchAlertsForQuestion } from "metabase/alert/alert";
 
+import Collections from "metabase/entities/collections";
+
 const mapStateToProps = (state, props) => ({
   questionAlerts: getQuestionAlerts(state),
   visualizationSettings: getVisualizationSettings(state),
   user: getUser(state),
+  initialCollectionId: Collections.selectors.getInitialCollectionId(
+    state,
+    props,
+  ),
 });
 
 const mapDispatchToProps = {
@@ -62,21 +65,6 @@ export default class QueryHeader extends Component {
       modal: null,
       revisions: null,
     };
-
-    _.bindAll(
-      this,
-      "resetStateOnTimeout",
-      "onCreate",
-      "onSave",
-      "onBeginEditing",
-      "onCancel",
-      "onDelete",
-      "onFollowBreadcrumb",
-      "onToggleDataReference",
-      "onFetchRevisions",
-      "onRevertToRevision",
-      "onRevertedRevision",
-    );
   }
 
   static propTypes = {
@@ -98,14 +86,14 @@ export default class QueryHeader extends Component {
     clearTimeout(this.timeout);
   }
 
-  resetStateOnTimeout() {
+  resetStateOnTimeout = () => {
     // clear any previously set timeouts then start a new one
     clearTimeout(this.timeout);
     this.timeout = setTimeout(
       () => this.setState({ recentlySaved: null }),
       5000,
     );
-  }
+  };
 
   onCreate = async (card, showSavedModal = true) => {
     const { question, apiCreateQuestion } = this.props;
@@ -140,52 +128,45 @@ export default class QueryHeader extends Component {
     );
   };
 
-  onBeginEditing() {
+  onBeginEditing = () => {
     this.props.onBeginEditing();
-  }
+  };
 
-  async onCancel() {
+  onCancel = async () => {
     if (this.props.fromUrl) {
       this.onGoBack();
     } else {
       this.props.onCancelEditing();
     }
-  }
-
-  async onDelete() {
-    // TODO: reduxify
-    await CardApi.delete({ cardId: this.props.card.id });
-    this.onGoBack();
-    MetabaseAnalytics.trackEvent("QueryBuilder", "Delete");
-  }
+  };
 
-  onFollowBreadcrumb() {
+  onFollowBreadcrumb = () => {
     this.props.onRestoreOriginalQuery();
-  }
+  };
 
-  onToggleDataReference() {
+  onToggleDataReference = () => {
     this.props.toggleDataReferenceFn();
-  }
+  };
 
-  onGoBack() {
+  onGoBack = () => {
     this.props.onChangeLocation(this.props.fromUrl || "/");
-  }
+  };
 
-  async onFetchRevisions({ entity, id }) {
+  onFetchRevisions = async ({ entity, id }) => {
     // TODO: reduxify
     let revisions = await RevisionApi.list({ entity, id });
     this.setState({ revisions });
-  }
+  };
 
-  onRevertToRevision({ entity, id, revision_id }) {
+  onRevertToRevision = ({ entity, id, revision_id }) => {
     // TODO: reduxify
     return RevisionApi.revert({ entity, id, revision_id });
-  }
+  };
 
-  onRevertedRevision() {
+  onRevertedRevision = () => {
     this.props.reloadCardFn();
     this.refs.cardHistory.toggle();
-  }
+  };
 
   getHeaderButtons() {
     const {
@@ -212,7 +193,7 @@ export default class QueryHeader extends Component {
           form
           key="save"
           ref="saveModal"
-          triggerClasses="h4 text-grey-4 text-brand-hover text-uppercase"
+          triggerClasses="h4 text-medium text-brand-hover text-uppercase"
           triggerElement={t`Save`}
         >
           <SaveQuestionModal
@@ -223,6 +204,7 @@ export default class QueryHeader extends Component {
             saveFn={card => this.onSave(card, false)}
             createFn={this.onCreate}
             onClose={() => this.refs.saveModal && this.refs.saveModal.toggle()}
+            initialCollectionId={this.props.initialCollectionId}
           />
         </ModalWithTrigger>,
       ]);
@@ -263,7 +245,7 @@ export default class QueryHeader extends Component {
           <ActionButton
             key="save"
             actionFn={() => this.onSave(this.props.card, false)}
-            className="cursor-pointer text-brand-hover bg-white text-grey-4 text-uppercase"
+            className="cursor-pointer text-brand-hover bg-white text-medium text-uppercase"
             normalText={t`SAVE CHANGES`}
             activeText={t`Saving…`}
             failedText={t`Save failed`}
@@ -275,7 +257,7 @@ export default class QueryHeader extends Component {
         buttonSections.push([
           <a
             key="cancel"
-            className="cursor-pointer text-brand-hover text-grey-4 text-uppercase"
+            className="cursor-pointer text-brand-hover text-medium text-uppercase"
             onClick={this.onCancel}
           >
             {t`CANCEL`}
@@ -304,7 +286,6 @@ export default class QueryHeader extends Component {
                 }
                 onClose={onClose}
                 onMove={collection => {
-                  this.props.onSetCardAttribute("collection", collection);
                   this.props.onSetCardAttribute(
                     "collection_id",
                     collection && collection.id,
@@ -385,6 +366,7 @@ export default class QueryHeader extends Component {
               }}
               onClose={() => this.refs.addToDashSaveModal.toggle()}
               multiStep
+              initiCollectionId={this.props.initiCollectionId}
             />
           </ModalWithTrigger>
         </Tooltip>,
@@ -529,7 +511,7 @@ export default class QueryHeader extends Component {
 
   render() {
     return (
-      <div className="relative">
+      <div className="relative px2 sm-px0">
         <HeaderBar
           isEditing={this.props.isEditing}
           name={this.props.isNew ? t`New question` : this.props.card.name}
@@ -547,22 +529,11 @@ export default class QueryHeader extends Component {
           buttons={this.getHeaderButtons()}
           setItemAttributeFn={this.props.onSetCardAttribute}
           badge={
-            this.props.card.collection && (
-              <Link
-                to={Urls.collection(this.props.card.collection.id)}
-                className="text-uppercase flex align-center no-decoration"
-                style={{
-                  color: this.props.card.collection.color,
-                  fontSize: 12,
-                }}
-              >
-                <Icon
-                  name="collection"
-                  size={12}
-                  style={{ marginRight: "0.5em" }}
-                />
-                {this.props.card.collection.name}
-              </Link>
+            this.props.card.id && (
+              <CollectionBadge
+                collectionId={this.props.card.collection_id}
+                analyticsContext="QueryBuilder"
+              />
             )
           }
         />
@@ -625,6 +596,7 @@ export default class QueryHeader extends Component {
               this.setState({ modal: null })
             }
             multiStep
+            initiCollectionId={this.props.initiCollectionId}
           />
         </Modal>
       </div>
diff --git a/frontend/src/metabase/query_builder/components/QueryModeButton.jsx b/frontend/src/metabase/query_builder/components/QueryModeButton.jsx
index 315dc59bce972fea476ee35a5828cb7a19668c22..b3f9d333e583f09a431417d3ccad814f2d9552a1 100644
--- a/frontend/src/metabase/query_builder/components/QueryModeButton.jsx
+++ b/frontend/src/metabase/query_builder/components/QueryModeButton.jsx
@@ -69,7 +69,7 @@ export default class QueryModeButton extends Component {
             data-metabase-event={"QueryBuilder;Toggle Mode"}
             className={cx("cursor-pointer", {
               "text-brand-hover": onClick,
-              "text-grey-1": !onClick,
+              "text-light": !onClick,
             })}
             onClick={onClick}
           >
diff --git a/frontend/src/metabase/query_builder/components/QueryVisualization.jsx b/frontend/src/metabase/query_builder/components/QueryVisualization.jsx
index 23770d3d4659073cce96b8ecfd0ec1309edc6d8e..0020002e53aa148da969aa6c1d55bbcf4d6a97b5 100644
--- a/frontend/src/metabase/query_builder/components/QueryVisualization.jsx
+++ b/frontend/src/metabase/query_builder/components/QueryVisualization.jsx
@@ -157,13 +157,13 @@ export default class QueryVisualization extends Component {
     const isPublicLinksEnabled = MetabaseSettings.get("public_sharing");
     const isEmbeddingEnabled = MetabaseSettings.get("embedding");
     return (
-      <div className="relative flex align-center flex-no-shrink mt2 mb1 sm-py3">
+      <div className="relative flex align-center flex-no-shrink mt2 mb1 px2 sm-py3">
         <div className="z4 absolute left hide sm-show">
           {!isObjectDetail && (
             <VisualizationSettings ref="settings" {...this.props} />
           )}
         </div>
-        <div className="z3 absolute left right">
+        <div className="z3 sm-absolute left right">
           <Tooltip tooltip={runButtonTooltip}>
             <RunButton
               isRunnable={isRunnable}
@@ -182,7 +182,7 @@ export default class QueryVisualization extends Component {
             className="flex"
             items={messages}
             renderItem={item => (
-              <div className="flex-no-shrink flex align-center mx2 h5 text-grey-4">
+              <div className="flex-no-shrink flex align-center mx2 h5 text-medium">
                 <Icon className="mr1" name={item.icon} size={12} />
                 {item.message}
               </div>
@@ -289,7 +289,7 @@ export default class QueryVisualization extends Component {
 }
 
 export const VisualizationEmptyState = ({ showTutorialLink }) => (
-  <div className="flex full layout-centered text-grey-1 flex-column">
+  <div className="flex full layout-centered text-light flex-column">
     <h1
     >{t`If you give me some data I can show you something cool. Run a Query!`}</h1>
     {showTutorialLink && (
diff --git a/frontend/src/metabase/query_builder/components/RunButton.jsx b/frontend/src/metabase/query_builder/components/RunButton.jsx
index 08409ac0cee29a021a82211cb5e67b8f7a630642..d8e39584010c0906aebf65966b87619c08f5717e 100644
--- a/frontend/src/metabase/query_builder/components/RunButton.jsx
+++ b/frontend/src/metabase/query_builder/components/RunButton.jsx
@@ -20,8 +20,8 @@ export default class RunButton extends Component {
     if (isRunning) {
       buttonText = (
         <div className="flex align-center">
-          <Icon className="mr1" name="close" />
-          {t`Cancel`}
+          <Icon className="sm-mr1" name="close" />
+          <span className="hide sm-show">{t`Cancel`}</span>
         </div>
       );
     } else if (isRunnable && isDirty) {
@@ -29,8 +29,8 @@ export default class RunButton extends Component {
     } else if (isRunnable && !isDirty) {
       buttonText = (
         <div className="flex align-center">
-          <Icon className="mr1" name="refresh" />
-          {t`Refresh`}
+          <Icon className="sm-mr1" name="refresh" />
+          <span className="hide sm-show">{t`Refresh`}</span>
         </div>
       );
     }
@@ -40,8 +40,8 @@ export default class RunButton extends Component {
       {
         "RunButton--hidden": !buttonText,
         "Button--primary": isDirty,
-        "text-grey-2": !isDirty,
-        "text-grey-4-hover": !isDirty,
+        "text-medium": !isDirty,
+        "text-brand-hover": !isDirty,
       },
     );
     return (
diff --git a/frontend/src/metabase/query_builder/components/SavedQuestionIntroModal.jsx b/frontend/src/metabase/query_builder/components/SavedQuestionIntroModal.jsx
index 32758c6a1a2b3732bad38b5c78cbb85c1d76a9cd..176e7b1dbb7d082022e9c4218b3a576c4e33276e 100644
--- a/frontend/src/metabase/query_builder/components/SavedQuestionIntroModal.jsx
+++ b/frontend/src/metabase/query_builder/components/SavedQuestionIntroModal.jsx
@@ -11,7 +11,7 @@ export default class SavedQuestionIntroModal extends Component {
           <div className="Modal-header Form-header">
             <h2 className="pb2 text-dark">{t`It's okay to play around with saved questions`}</h2>
 
-            <div className="pb1 text-grey-4">{t`You won't make any permanent changes to a saved question unless you click the edit icon in the top-right.`}</div>
+            <div className="pb1 text-medium">{t`You won't make any permanent changes to a saved question unless you click the edit icon in the top-right.`}</div>
           </div>
 
           <div className="Form-actions flex justify-center py1">
diff --git a/frontend/src/metabase/query_builder/components/SelectionModule.jsx b/frontend/src/metabase/query_builder/components/SelectionModule.jsx
index 78e509faac0f4e731b6ee49bdb58d43db7dd548c..a3480a2aed02960e7feb0c4ab61dd4aa1b447f00 100644
--- a/frontend/src/metabase/query_builder/components/SelectionModule.jsx
+++ b/frontend/src/metabase/query_builder/components/SelectionModule.jsx
@@ -244,7 +244,7 @@ export default class SelectionModule extends Component {
     if (this.props.remove) {
       remove = (
         <a
-          className="text-grey-2 no-decoration pr1 flex align-center"
+          className="text-light no-decoration pr1 flex align-center"
           onClick={this.props.remove.bind(null, this.props.index)}
         >
           <Icon name="close" size={14} />
diff --git a/frontend/src/metabase/query_builder/components/SortWidget.jsx b/frontend/src/metabase/query_builder/components/SortWidget.jsx
index 660ccf2b0d868397e675962e2849cf3f5c293790..06fcbbfc0d54dabd440f933096eff0f0d417ca19 100644
--- a/frontend/src/metabase/query_builder/components/SortWidget.jsx
+++ b/frontend/src/metabase/query_builder/components/SortWidget.jsx
@@ -30,8 +30,8 @@ export default class SortWidget extends Component {
 
   componentWillReceiveProps(newProps) {
     this.setState({
-      field: newProps.sort[0], // id of the field
-      direction: newProps.sort[1], // sort direction
+      field: newProps.sort[1], // id of the field
+      direction: newProps.sort[0], // sort direction
     });
   }
 
@@ -44,7 +44,7 @@ export default class SortWidget extends Component {
 
   setField(value) {
     if (this.state.field !== value) {
-      this.props.updateOrderBy([value, this.state.direction]);
+      this.props.updateOrderBy([this.state.direction, value]);
       // Optimistically set field state so componentWillUnmount logic works correctly
       this.setState({ field: value });
     }
@@ -52,7 +52,7 @@ export default class SortWidget extends Component {
 
   setDirection(value) {
     if (this.state.direction !== value) {
-      this.props.updateOrderBy([this.state.field, value]);
+      this.props.updateOrderBy([value, this.state.field]);
       // Optimistically set direction state so componentWillUnmount logic works correctly
       this.setState({ direction: value });
     }
@@ -60,8 +60,8 @@ export default class SortWidget extends Component {
 
   render() {
     let directionOptions = [
-      { key: "ascending", val: "ascending" },
-      { key: "descending", val: "descending" },
+      { key: "ascending", val: "asc" },
+      { key: "descending", val: "desc" },
     ];
 
     return (
diff --git a/frontend/src/metabase/query_builder/components/VisualizationSettings.jsx b/frontend/src/metabase/query_builder/components/VisualizationSettings.jsx
index 4d195d4c28b208d0563c2b64133c86678fcd345e..0ec788905f07b75aa02dc0e8c1017ae9389ca673 100644
--- a/frontend/src/metabase/query_builder/components/VisualizationSettings.jsx
+++ b/frontend/src/metabase/query_builder/components/VisualizationSettings.jsx
@@ -17,7 +17,7 @@ export default class VisualizationSettings extends React.Component {
   }
 
   static propTypes = {
-    card: PropTypes.object.isRequired,
+    question: PropTypes.object.isRequired,
     result: PropTypes.object,
     setDisplayFn: PropTypes.func.isRequired,
     onUpdateVisualizationSettings: PropTypes.func.isRequired,
@@ -31,9 +31,9 @@ export default class VisualizationSettings extends React.Component {
   };
 
   renderChartTypePicker() {
-    let { result, card } = this.props;
+    let { result, question } = this.props;
     let { CardVisualization } = getVisualizationRaw([
-      { card, data: result.data },
+      { card: question.card(), data: result.data },
     ]);
 
     let triggerElement = (
@@ -61,14 +61,14 @@ export default class VisualizationSettings extends React.Component {
           triggerClasses="flex align-center"
           sizeToFit
         >
-          <ul className="pt1 pb1">
+          <ul className="pt1 pb1 scroll-y">
             {Array.from(visualizations).map(([vizType, viz], index) => (
               <li
                 key={index}
                 className={cx(
                   "p2 flex align-center cursor-pointer bg-brand-hover text-white-hover",
                   {
-                    "ChartType--selected": vizType === card.display,
+                    "ChartType--selected": vizType === question.display(),
                     "ChartType--notSensible": !(
                       result &&
                       result.data &&
@@ -111,7 +111,14 @@ export default class VisualizationSettings extends React.Component {
             ref="popover"
           >
             <ChartSettings
-              series={[{ card: this.props.card, data: this.props.result.data }]}
+              question={this.props.question}
+              addField={this.props.addField}
+              series={[
+                {
+                  card: this.props.question.card(),
+                  data: this.props.result.data,
+                },
+              ]}
               onChange={this.props.onReplaceAllVisualizationSettings}
             />
           </ModalWithTrigger>
diff --git a/frontend/src/metabase/query_builder/components/dataref/DetailPane.jsx b/frontend/src/metabase/query_builder/components/dataref/DetailPane.jsx
index 31d69223eddca7e3bec996ed2247a30b247221ae..0244946ca5df64ffb9c38a1f1c12ec3ff2151a24 100644
--- a/frontend/src/metabase/query_builder/components/dataref/DetailPane.jsx
+++ b/frontend/src/metabase/query_builder/components/dataref/DetailPane.jsx
@@ -13,7 +13,7 @@ const DetailPane = ({
 }) => (
   <div>
     <h1>{name}</h1>
-    <p className={cx({ "text-grey-3": !description })}>
+    <p className={cx({ "text-medium": !description })}>
       {description || t`No description set.`}
     </p>
     {useForCurrentQuestion && useForCurrentQuestion.length > 0 ? (
diff --git a/frontend/src/metabase/query_builder/components/dataref/MetricPane.jsx b/frontend/src/metabase/query_builder/components/dataref/MetricPane.jsx
index ac0d1f8bc145bd21e478d73333f6199021505d5d..284a353eca20790810f9db897a7b4352044b174d 100644
--- a/frontend/src/metabase/query_builder/components/dataref/MetricPane.jsx
+++ b/frontend/src/metabase/query_builder/components/dataref/MetricPane.jsx
@@ -62,7 +62,7 @@ export default class MetricPane extends Component {
 
   setQueryMetric() {
     let card = this.newCard();
-    card.dataset_query.query.aggregation = ["METRIC", this.props.metric.id];
+    card.dataset_query.query.aggregation = ["metric", this.props.metric.id];
     this.props.setCardAndRun(card);
   }
 
diff --git a/frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx b/frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx
index c78e6de6d5d190ad14df4be2656dfb54606c443b..75fc8786f553fac78d0c1aa16df9da5edbb88630 100644
--- a/frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx
+++ b/frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx
@@ -64,7 +64,7 @@ export default class SegmentPane extends Component {
         query = query.clearAggregations();
       }
 
-      query = query.addFilter(["SEGMENT", this.props.segment.id]);
+      query = query.addFilter(["segment", this.props.segment.id]);
 
       this.props.updateQuestion(query.question());
       this.props.runQuestionQuery();
@@ -88,14 +88,14 @@ export default class SegmentPane extends Component {
   setQueryFilteredBy() {
     let card = this.newCard();
     card.dataset_query.query.aggregation = ["rows"];
-    card.dataset_query.query.filter = ["SEGMENT", this.props.segment.id];
+    card.dataset_query.query.filter = ["segment", this.props.segment.id];
     this.props.setCardAndRun(card);
   }
 
   setQueryCountFilteredBy() {
     let card = this.newCard();
     card.dataset_query.query.aggregation = ["count"];
-    card.dataset_query.query.filter = ["SEGMENT", this.props.segment.id];
+    card.dataset_query.query.filter = ["segment", this.props.segment.id];
     this.props.setCardAndRun(card);
   }
 
@@ -111,7 +111,7 @@ export default class SegmentPane extends Component {
     if (
       query instanceof StructuredQuery &&
       query.tableId() === segment.table_id &&
-      !_.findWhere(query.filters(), { [0]: "SEGMENT", [1]: segment.id })
+      !_.findWhere(query.filters(), { [0]: "segment", [1]: segment.id })
     ) {
       useForCurrentQuestion.push(
         <UseForButton
diff --git a/frontend/src/metabase/query_builder/components/dataref/TablePane.jsx b/frontend/src/metabase/query_builder/components/dataref/TablePane.jsx
index c2f7081d8dca50c0705863b816f0f52510da1839..5bcce7606723332e77663a757e48baaa87c7635c 100644
--- a/frontend/src/metabase/query_builder/components/dataref/TablePane.jsx
+++ b/frontend/src/metabase/query_builder/components/dataref/TablePane.jsx
@@ -121,7 +121,7 @@ export default class TablePane extends Component {
                 >
                   {fk.origin.table.display_name}
                   {fkCountsByTable[fk.origin.table.id] > 1 ? (
-                    <span className="text-grey-3 text-light h5">
+                    <span className="text-medium text-light h5">
                       {" "}
                       via {fk.origin.display_name}
                     </span>
@@ -145,7 +145,7 @@ export default class TablePane extends Component {
           </ul>
         );
       } else {
-        const descriptionClasses = cx({ "text-grey-3": !table.description });
+        const descriptionClasses = cx({ "text-medium": !table.description });
         description = (
           <p className={descriptionClasses}>
             {table.description || t`No description set.`}
diff --git a/frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.css b/frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.css
index ad1905f617e0740ab0c2905114baa47b7fb3c7f3..757b64effbeea7d329a3bfdca1738f520508783e 100644
--- a/frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.css
+++ b/frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.css
@@ -13,5 +13,5 @@
 
 :local(.placeholder) {
   /* match the placeholder text */
-  color: rgb(192, 192, 192);
+  color: var(--color-text-light);
 }
diff --git a/frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx b/frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx
index fcdcf6026e7600eb1c6397896a73195c5991573a..4f3a8c957327bdeffd41e6ddbb6dabc2d4437533 100644
--- a/frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx
+++ b/frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx
@@ -26,6 +26,14 @@ import { isExpression } from "metabase/lib/expressions";
 
 const MAX_SUGGESTIONS = 30;
 
+const SUGGESTION_SECTION_NAMES = {
+  fields: t`Fields`,
+  aggregations: t`Aggregations`,
+  operators: t`Operators`,
+  metrics: t`Metrics`,
+  other: t`Other`,
+};
+
 export default class ExpressionEditorTextfield extends Component {
   constructor(props, context) {
     super(props, context);
@@ -314,9 +322,10 @@ export default class ExpressionEditorTextfield extends Component {
                   (i === 0 || suggestion.type !== suggestions[i - 1].type) && (
                     <li
                       ref={"header-" + i}
-                      className="mx2 h6 text-uppercase text-bold text-grey-3 py1 pt2"
+                      className="mx2 h6 text-uppercase text-bold text-medium py1 pt2"
                     >
-                      {suggestion.type}
+                      {SUGGESTION_SECTION_NAMES[suggestion.type] ||
+                        suggestion.type}
                     </li>
                   ),
                   <li
@@ -356,7 +365,7 @@ export default class ExpressionEditorTextfield extends Component {
                   <li
                     style={{ paddingTop: 5, paddingBottom: 5 }}
                     onMouseDownCapture={e => this.onShowMoreMouseDown(e)}
-                    className="px2 text-italic text-grey-3 cursor-pointer text-brand-hover"
+                    className="px2 text-italic text-medium cursor-pointer text-brand-hover"
                   >
                     and {suggestions.length - MAX_SUGGESTIONS} more
                   </li>
diff --git a/frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx b/frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx
index 567a0b8634ffc8f32d1ca3da02f0a4b07bc254b5..9a0a9d381ffa02ae0b205137a80db7378f332705 100644
--- a/frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx
+++ b/frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx
@@ -43,7 +43,7 @@ export default class ExpressionWidget extends Component {
     return (
       <div style={{ maxWidth: "600px" }}>
         <div className="p2">
-          <div className="h5 text-uppercase text-grey-3 text-bold">{t`Field formula`}</div>
+          <div className="h5 text-uppercase text-medium text-bold">{t`Field formula`}</div>
           <div>
             <ExpressionEditorTextfield
               expression={expression}
@@ -53,7 +53,7 @@ export default class ExpressionWidget extends Component {
               }
               onError={errorMessage => this.setState({ error: errorMessage })}
             />
-            <p className="h5 text-grey-5">
+            <p className="h5 text-medium">
               {t`Think of this as being kind of like writing a formula in a spreadsheet program: you can use numbers, fields in this table, mathematical symbols like +, and some functions. So you could type something like Subtotal - Cost.`}
               &nbsp;<a
                 className="link"
@@ -63,7 +63,7 @@ export default class ExpressionWidget extends Component {
             </p>
           </div>
 
-          <div className="mt3 h5 text-uppercase text-grey-3 text-bold">{t`Give it a name`}</div>
+          <div className="mt3 h5 text-uppercase text-medium text-bold">{t`Give it a name`}</div>
           <div>
             <input
               className="my1 input block full"
diff --git a/frontend/src/metabase/query_builder/components/expressions/Expressions.jsx b/frontend/src/metabase/query_builder/components/expressions/Expressions.jsx
index 73e0dc9bcdf7e6aa9e62dda69fd56e4bf8086b3e..b71c1a3fbe787d73e5e039a022690612e4c47b73 100644
--- a/frontend/src/metabase/query_builder/components/expressions/Expressions.jsx
+++ b/frontend/src/metabase/query_builder/components/expressions/Expressions.jsx
@@ -26,8 +26,8 @@ export default class Expressions extends Component {
     let sortedNames = _.sortBy(_.keys(expressions), _.identity);
     return (
       <div className="pb3">
-        <div className="pb1 h6 text-uppercase text-grey-3 text-bold">
-          Custom fields
+        <div className="pb1 h6 text-uppercase text-medium text-bold">
+          {t`Custom fields`}
         </div>
 
         {sortedNames &&
@@ -51,7 +51,7 @@ export default class Expressions extends Component {
 
         <a
           data-metabase-event={"QueryBuilder;Show Add Custom Field"}
-          className="text-grey-2 text-bold flex align-center text-grey-4-hover cursor-pointer no-decoration transition-color"
+          className="text-light text-bold flex align-center text-medium-hover cursor-pointer no-decoration transition-color"
           onClick={() => onAddExpression()}
         >
           <IconBorder borderRadius="3px">
diff --git a/frontend/src/metabase/query_builder/components/expressions/TokenizedExpression.css b/frontend/src/metabase/query_builder/components/expressions/TokenizedExpression.css
index c85f651f2bba5b4fb6d36c2e48209c1117253610..974e77d0af365f9b5cfccdeb5043b8fdec0fd05f 100644
--- a/frontend/src/metabase/query_builder/components/expressions/TokenizedExpression.css
+++ b/frontend/src/metabase/query_builder/components/expressions/TokenizedExpression.css
@@ -25,13 +25,13 @@
 
 .Expression-aggregation,
 .Expression-metric {
-  border: 1px solid #9cc177;
-  background-color: #e4f7d1;
+  border: 1px solid var(--color-accent1);
+  background-color: var(--color-bg-white);
 }
 
 .Expression-field {
-  border: 1px solid #509ee3;
-  background-color: #c7e3fb;
+  border: 1px solid var(--color-brand);
+  background-color: var(--color-bg-medium);
 }
 
 .Expression-selected.Expression-aggregation,
@@ -39,11 +39,11 @@
 .Expression-selected .Expression-aggregation,
 .Expression-selected .Expression-metric {
   color: white;
-  background-color: #9cc177;
+  background-color: var(--color-accent1);
 }
 
 .Expression-selected.Expression-field,
 .Expression-selected .Expression-field {
   color: white;
-  background-color: #509ee3;
+  background-color: var(--color-brand);
 }
diff --git a/frontend/src/metabase/query_builder/components/filters/FilterList.jsx b/frontend/src/metabase/query_builder/components/filters/FilterList.jsx
deleted file mode 100644
index c92425f32e4318de6c18998ca59aa5c5fcc5f670..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/query_builder/components/filters/FilterList.jsx
+++ /dev/null
@@ -1,83 +0,0 @@
-/* @flow */
-
-import React, { Component } from "react";
-import { findDOMNode } from "react-dom";
-import { t } from "c-3po";
-import FilterWidget from "./FilterWidget.jsx";
-
-import StructuredQuery from "metabase-lib/lib/queries/StructuredQuery";
-import type { Filter } from "metabase/meta/types/Query";
-import Dimension from "metabase-lib/lib/Dimension";
-
-import type { TableMetadata } from "metabase/meta/types/Metadata";
-
-type Props = {
-  query: StructuredQuery,
-  filters: Array<Filter>,
-  removeFilter?: (index: number) => void,
-  updateFilter?: (index: number, filter: Filter) => void,
-  maxDisplayValues?: number,
-  tableMetadata?: TableMetadata, // legacy parameter
-};
-
-type State = {
-  shouldScroll: boolean,
-};
-
-export default class FilterList extends Component {
-  props: Props;
-  state: State;
-
-  constructor(props: Props) {
-    super(props);
-    this.state = {
-      shouldScroll: false,
-    };
-  }
-
-  componentDidUpdate() {
-    this.state.shouldScroll
-      ? (findDOMNode(this).scrollLeft = findDOMNode(this).scrollWidth)
-      : null;
-  }
-
-  componentWillReceiveProps(nextProps: Props) {
-    // only scroll when a filter is added
-    if (nextProps.filters.length > this.props.filters.length) {
-      this.setState({ shouldScroll: true });
-    } else {
-      this.setState({ shouldScroll: false });
-    }
-  }
-
-  componentDidMount() {
-    this.componentDidUpdate();
-  }
-
-  render() {
-    const { query, filters, tableMetadata } = this.props;
-    return (
-      <div className="Query-filterList scroll-x scroll-show">
-        {filters.map((filter, index) => (
-          <FilterWidget
-            key={index}
-            placeholder={t`Item`}
-            // TODO: update widgets that are still passing tableMetadata instead of query
-            query={
-              query || {
-                table: () => tableMetadata,
-                parseFieldReference: fieldRef =>
-                  Dimension.parseMBQL(fieldRef, tableMetadata),
-              }
-            }
-            filter={filter}
-            index={index}
-            removeFilter={this.props.removeFilter}
-            updateFilter={this.props.updateFilter}
-            maxDisplayValues={this.props.maxDisplayValues}
-          />
-        ))}
-      </div>
-    );
-  }
-}
diff --git a/frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx b/frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx
index 83bdbfd1cd4522cd0a18b2b9be18d23465f2fccd..0e98dc4531553e9a94c7d916793f2c8892bf0fda 100644
--- a/frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx
+++ b/frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx
@@ -305,7 +305,7 @@ export default class FilterPopover extends Component {
     const { filter } = this.state;
     const [operatorName, fieldRef] = filter;
 
-    if (operatorName === "SEGMENT" || fieldRef == undefined) {
+    if (operatorName === "segment" || fieldRef == undefined) {
       return (
         <div className="FilterPopover">
           <FieldList
@@ -331,7 +331,7 @@ export default class FilterPopover extends Component {
             maxWidth: dimension.field().isDate() ? null : 500,
           }}
         >
-          <div className="FilterPopover-header border-bottom text-grey-3 p1 flex align-center">
+          <div className="FilterPopover-header border-bottom text-medium p1 flex align-center">
             <div className="flex py1">
               <a
                 className="cursor-pointer text-purple-hover transition-color flex align-center"
diff --git a/frontend/src/metabase/query_builder/components/filters/FilterWidget.jsx b/frontend/src/metabase/query_builder/components/filters/FilterWidget.jsx
index df66d27f4d2405337f1bb90c5a74b6ac17a73290..9664fefce874eac2aada5a074b791d1df5305d92 100644
--- a/frontend/src/metabase/query_builder/components/filters/FilterWidget.jsx
+++ b/frontend/src/metabase/query_builder/components/filters/FilterWidget.jsx
@@ -139,7 +139,7 @@ export default class FilterWidget extends Component {
         </div>
         {removeFilter && (
           <a
-            className="text-grey-2 no-decoration px1 flex align-center"
+            className="text-light no-decoration px1 flex align-center"
             onClick={() => removeFilter(index)}
           >
             <Icon name="close" size={14} />
diff --git a/frontend/src/metabase/query_builder/components/filters/pickers/HoursMinutesInput.jsx b/frontend/src/metabase/query_builder/components/filters/pickers/HoursMinutesInput.jsx
index 073fe23793a4fe3b58634428938a0ac747686232..1ddfaf6e459f45f4a546f74ecb7102bbb719e9a1 100644
--- a/frontend/src/metabase/query_builder/components/filters/pickers/HoursMinutesInput.jsx
+++ b/frontend/src/metabase/query_builder/components/filters/pickers/HoursMinutesInput.jsx
@@ -52,7 +52,7 @@ const HoursMinutesInput = ({
     </div>
     {onClear && (
       <Icon
-        className="text-grey-2 cursor-pointer text-grey-4-hover ml-auto"
+        className="text-light cursor-pointer text-medium-hover ml-auto"
         name="close"
         onClick={onClear}
       />
diff --git a/frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx b/frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx
index 7e67c9af79422b3ede3362bfa4d939baeba466df..a19cf356bdb3773c64b02c4be6c773dfed94eea8 100644
--- a/frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx
+++ b/frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx
@@ -8,7 +8,7 @@ const EXAMPLES = {
     type: "native",
     native: {
       query: "SELECT count(*)\nFROM products\nWHERE category = {{category}}",
-      template_tags: {
+      "template-tags": {
         category: {
           name: "category",
           display_name: "Category",
@@ -24,7 +24,7 @@ const EXAMPLES = {
     type: "native",
     native: {
       query: "SELECT count(*)\nFROM products\nWHERE {{created_at}}",
-      template_tags: {
+      "template-tags": {
         created_at: {
           name: "created_at",
           display_name: "Created At",
@@ -40,7 +40,7 @@ const EXAMPLES = {
     native: {
       query:
         "SELECT count(*)\nFROM products\n[[WHERE category = {{category}}]]",
-      template_tags: {
+      "template-tags": {
         category: {
           name: "category",
           display_name: "Category",
@@ -56,7 +56,7 @@ const EXAMPLES = {
     native: {
       query:
         "SELECT count(*)\nFROM products\nWHERE 1=1\n  [[AND id = {{id}}]]\n  [[AND category = {{category}}]]",
-      template_tags: {
+      "template-tags": {
         id: { name: "id", display_name: "ID", type: "number", required: false },
         category: {
           name: "category",
diff --git a/frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx b/frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx
index cc4b2455f0990551e8ec6d073764854309f6e33c..7dc3ab8f172c64a8a415675a51197825cf23a927 100644
--- a/frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx
+++ b/frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx
@@ -69,7 +69,7 @@ export default class TagEditorParam extends Component {
         ...this.props.tag,
         type: type,
         dimension: undefined,
-        widget_type: undefined,
+        "widget-type": undefined,
       });
     }
   }
@@ -83,16 +83,19 @@ export default class TagEditorParam extends Component {
         return;
       }
       const options = parameterOptionsForField(field);
-      let widget_type;
-      if (tag.widget_type && _.findWhere(options, { type: tag.widget_type })) {
-        widget_type = tag.widget_type;
+      let widgetType;
+      if (
+        tag["widget-type"] &&
+        _.findWhere(options, { type: tag["widget-type"] })
+      ) {
+        widgetType = tag["widget-type"];
       } else if (options.length > 0) {
-        widget_type = options[0].type;
+        widgetType = options[0].type;
       }
       onUpdate({
         ...tag,
         dimension,
-        widget_type,
+        "widget-type": widgetType,
       });
     }
   }
@@ -124,10 +127,10 @@ export default class TagEditorParam extends Component {
           <h5 className="pb1 text-normal">{t`Filter label`}</h5>
           <InputBlurChange
             type="text"
-            value={tag.display_name}
-            className="AdminSelect p1 text-bold text-grey-4 bordered border-med rounded full"
+            value={tag["display-name"]}
+            className="AdminSelect p1 text-bold text-medium bordered border-med rounded full"
             onBlurChange={e =>
-              this.setParameterAttribute("display_name", e.target.value)
+              this.setParameterAttribute("display-name", e.target.value)
             }
           />
         </div>
@@ -176,11 +179,11 @@ export default class TagEditorParam extends Component {
               <h5 className="pb1 text-normal">{t`Filter widget type`}</h5>
               <Select
                 className="border-med bg-white block"
-                value={tag.widget_type}
+                value={tag["widget-type"]}
                 onChange={e =>
-                  this.setParameterAttribute("widget_type", e.target.value)
+                  this.setParameterAttribute("widget-type", e.target.value)
                 }
-                isInitiallyOpen={!tag.widget_type}
+                isInitiallyOpen={!tag["widget-type"]}
                 placeholder={t`Select…`}
               >
                 {[{ name: "None", type: undefined }]
@@ -205,18 +208,18 @@ export default class TagEditorParam extends Component {
         )}
 
         {((tag.type !== "dimension" && tag.required) ||
-          (tag.type === "dimension" || tag.widget_type)) && (
+          (tag.type === "dimension" || tag["widget-type"])) && (
           <div className="pb1">
             <h5 className="pb1 text-normal">{t`Default filter widget value`}</h5>
             <ParameterValueWidget
               parameter={{
                 type:
-                  tag.widget_type ||
+                  tag["widget-type"] ||
                   (tag.type === "date" ? "date/single" : null),
               }}
               value={tag.default}
               setValue={value => this.setParameterAttribute("default", value)}
-              className="AdminSelect p1 text-bold text-grey-4 bordered border-med rounded bg-white"
+              className="AdminSelect p1 text-bold text-medium bordered border-med rounded bg-white"
               isEditing
               commitImmediately
             />
diff --git a/frontend/src/metabase/query_builder/containers/ArchiveQuestionModal.jsx b/frontend/src/metabase/query_builder/containers/ArchiveQuestionModal.jsx
index 668ee6a45c833c77bfd4da73f54509288717002b..6f5f69418a3a2bec6f90e755f3b037b312f373f1 100644
--- a/frontend/src/metabase/query_builder/containers/ArchiveQuestionModal.jsx
+++ b/frontend/src/metabase/query_builder/containers/ArchiveQuestionModal.jsx
@@ -53,7 +53,8 @@ class ArchiveQuestionModal extends Component {
           >{t`Archive`}</Button>,
         ]}
       >
-        <div className="px4 pb4">{t`This question will be removed from any dashboards or pulses using it.`}</div>
+        <div
+        >{t`This question will be removed from any dashboards or pulses using it.`}</div>
       </ModalWithTrigger>
     );
   }
diff --git a/frontend/src/metabase/query_builder/selectors.js b/frontend/src/metabase/query_builder/selectors.js
index 2b1ead75d67765ac9543df21543dbc865062a751..cbdffeb83e8feb948c61e9c8d5b1adbab26825c8 100644
--- a/frontend/src/metabase/query_builder/selectors.js
+++ b/frontend/src/metabase/query_builder/selectors.js
@@ -56,7 +56,7 @@ export const getDatabaseId = createSelector(
 );
 
 export const getTableId = createSelector([getCard], card =>
-  getIn(card, ["dataset_query", "query", "source_table"]),
+  getIn(card, ["dataset_query", "query", "source-table"]),
 );
 
 export const getTableForeignKeys = state => state.qb.tableForeignKeys;
diff --git a/frontend/src/metabase/questions/Questions.css b/frontend/src/metabase/questions/Questions.css
index 3403e5da1cbee3a35d40a05cd978a90820f4bbad..6671c5102020398ae7aef6fe9520e8f7ea699ea2 100644
--- a/frontend/src/metabase/questions/Questions.css
+++ b/frontend/src/metabase/questions/Questions.css
@@ -1,8 +1,8 @@
 :root {
-  --title-color: #606e7b;
-  --subtitle-color: #aab7c3;
-  --muted-color: #deeaf1;
-  --blue-color: #2d86d4;
+  --title-color: var(--color-text-medium);
+  --subtitle-color: var(--color-text-medium);
+  --muted-color: var(--color-text-light);
+  --blue-color: var(--color-brand);
 }
 
 :local(.header) {
diff --git a/frontend/src/metabase/questions/components/CollectionActions.jsx b/frontend/src/metabase/questions/components/CollectionActions.jsx
deleted file mode 100644
index 0a05aff400a3b55105faf132e4fad15afd455199..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/questions/components/CollectionActions.jsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import React from "react";
-
-const CollectionActions = ({ children }) => (
-  <div
-    className="flex align-center"
-    onClick={e => {
-      e.stopPropagation();
-      e.preventDefault();
-    }}
-  >
-    {React.Children.map(children, (child, index) => (
-      <div key={index} className="cursor-pointer text-brand-hover mx1">
-        {child}
-      </div>
-    ))}
-  </div>
-);
-
-export default CollectionActions;
diff --git a/frontend/src/metabase/questions/components/CollectionBadge.jsx b/frontend/src/metabase/questions/components/CollectionBadge.jsx
index faa9aca34e6ac89293e2299d9c4d663701cd0b1c..c8f84a6623bd3755873b7ef3b609960cc027e747 100644
--- a/frontend/src/metabase/questions/components/CollectionBadge.jsx
+++ b/frontend/src/metabase/questions/components/CollectionBadge.jsx
@@ -1,28 +1,37 @@
 import React from "react";
+import { Flex } from "grid-styled";
 import { Link } from "react-router";
 
+import Icon from "metabase/components/Icon";
+
+import { entityObjectLoader } from "metabase/entities/containers/EntityObjectLoader";
 import * as Urls from "metabase/lib/urls";
 
-import Color from "color";
 import cx from "classnames";
 
-const CollectionBadge = ({ className, collection }) => {
-  const color = Color(collection.color);
-  const darkened = color.darken(0.1);
-  const lightened = color.lighten(0.4);
-  return (
-    <Link
-      to={Urls.collection(collection.id)}
-      className={cx(className, "flex align-center px1 rounded mx1")}
-      style={{
-        fontSize: 14,
-        color: lightened.isDark() ? "#fff" : darkened,
-        backgroundColor: lightened,
-      }}
-    >
-      {collection.name}
-    </Link>
-  );
-};
+@entityObjectLoader({
+  entityType: "collections",
+  entityId: (state, props) => props.collectionId || "root",
+  wrapped: true,
+})
+class CollectionBadge extends React.Component {
+  render() {
+    const { analyticsContext, object } = this.props;
+    return (
+      <Link
+        to={Urls.collection(object.id)}
+        className={cx("inline-block link")}
+        data-metabase-event={`${analyticsContext};Collection Badge Click`}
+      >
+        <Flex align="center">
+          <Icon name={object.getIcon()} mr={1} />
+          <h5 className="text-uppercase" style={{ fontWeight: 900 }}>
+            {object.name}
+          </h5>
+        </Flex>
+      </Link>
+    );
+  }
+}
 
 export default CollectionBadge;
diff --git a/frontend/src/metabase/questions/components/CollectionButtons.jsx b/frontend/src/metabase/questions/components/CollectionButtons.jsx
deleted file mode 100644
index 904671c364208d82bf86037c783bf870a94542b5..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/questions/components/CollectionButtons.jsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import React from "react";
-import { Flex } from "grid-styled";
-import { Link } from "react-router";
-import { t } from "c-3po";
-
-import Icon from "metabase/components/Icon";
-
-const COLLECTION_ICON_SIZE = 18;
-
-const CollectionButtons = ({ collections, isAdmin, push }) => (
-  <ol>
-    {collections
-      .map(collection => (
-        <CollectionLink {...collection} push={push} isAdmin={isAdmin} />
-      ))
-      .concat(isAdmin ? [<NewCollectionButton push={push} />] : [])
-      .map((element, index) => <li key={index}>{element}</li>)}
-  </ol>
-);
-
-const CollectionLink = ({ name, slug }) => {
-  return (
-    <Link to={`/questions/collections/${slug}`}>
-      <Flex align="center">
-        <Icon name="collection" size={COLLECTION_ICON_SIZE} />
-        <h3>{name}</h3>
-      </Flex>
-    </Link>
-  );
-};
-
-const NewCollectionButton = ({ push }) => (
-  <div onClick={() => push(`/collections/create`)}>
-    <div>
-      <div
-        className="flex align-center justify-center ml-auto mr-auto mb2 mt2"
-        style={{
-          border: "2px solid #D8E8F5",
-          borderRadius: COLLECTION_ICON_SIZE,
-          height: COLLECTION_ICON_SIZE,
-          width: COLLECTION_ICON_SIZE,
-        }}
-      >
-        <Icon name="add" width="32" height="32" />
-      </div>
-    </div>
-    <h3>{t`New collection`}</h3>
-  </div>
-);
-
-export default CollectionButtons;
diff --git a/frontend/src/metabase/questions/components/ExpandingSearchField.jsx b/frontend/src/metabase/questions/components/ExpandingSearchField.jsx
deleted file mode 100644
index ef351a4bf3e7b8e59ab19c5eab601b84183616b7..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/questions/components/ExpandingSearchField.jsx
+++ /dev/null
@@ -1,105 +0,0 @@
-/* eslint "react/prop-types": "warn" */
-
-import React, { Component } from "react";
-import PropTypes from "prop-types";
-import ReactDOM from "react-dom";
-import { t } from "c-3po";
-import cx from "classnames";
-import { Motion, spring } from "react-motion";
-
-import Icon from "metabase/components/Icon";
-
-import {
-  KEYCODE_FORWARD_SLASH,
-  KEYCODE_ENTER,
-  KEYCODE_ESCAPE,
-} from "metabase/lib/keyboard";
-
-export default class ExpandingSearchField extends Component {
-  constructor(props, context) {
-    super(props, context);
-    this.state = {
-      active: false,
-    };
-  }
-
-  static propTypes = {
-    onSearch: PropTypes.func.isRequired,
-    className: PropTypes.string,
-    defaultValue: PropTypes.string,
-  };
-
-  componentDidMount() {
-    this.listenToSearchKeyDown();
-  }
-
-  componentWillUnMount() {
-    this.stopListenToSearchKeyDown();
-  }
-
-  handleSearchKeydown = e => {
-    if (!this.state.active && e.keyCode === KEYCODE_FORWARD_SLASH) {
-      this.setActive();
-      e.preventDefault();
-    }
-  };
-
-  onKeyPress = e => {
-    if (e.keyCode === KEYCODE_ENTER) {
-      this.props.onSearch(e.target.value);
-    } else if (e.keyCode === KEYCODE_ESCAPE) {
-      this.setInactive();
-    }
-  };
-
-  setActive = () => {
-    ReactDOM.findDOMNode(this.searchInput).focus();
-  };
-
-  setInactive = () => {
-    ReactDOM.findDOMNode(this.searchInput).blur();
-  };
-
-  listenToSearchKeyDown() {
-    window.addEventListener("keydown", this.handleSearchKeydown);
-  }
-
-  stopListenToSearchKeyDown() {
-    window.removeEventListener("keydown", this.handleSearchKeydown);
-  }
-
-  render() {
-    const { className } = this.props;
-    const { active } = this.state;
-    return (
-      <div
-        className={cx(
-          className,
-          "bordered border-grey-1 flex align-center pr2 transition-border",
-          { "border-brand": active },
-        )}
-        onClick={this.setActive}
-        style={{ borderRadius: 99 }}
-      >
-        <Icon
-          className={cx("ml2 text-grey-3", { "text-brand": active })}
-          name="search"
-        />
-        <Motion style={{ width: active ? spring(400) : spring(200) }}>
-          {interpolatingStyle => (
-            <input
-              ref={search => (this.searchInput = search)}
-              className="input borderless text-bold"
-              placeholder={t`Search for a question`}
-              style={Object.assign({}, interpolatingStyle, { fontSize: "1em" })}
-              onFocus={() => this.setState({ active: true })}
-              onBlur={() => this.setState({ active: false })}
-              onKeyUp={this.onKeyPress}
-              defaultValue={this.props.defaultValue}
-            />
-          )}
-        </Motion>
-      </div>
-    );
-  }
-}
diff --git a/frontend/src/metabase/questions/containers/AddToDashboard.jsx b/frontend/src/metabase/questions/containers/AddToDashboard.jsx
index 6dd15e6cacfafd0449e026447701b3ac30042415..b10018efa16e972b8dc93f4d0a61060106599657 100644
--- a/frontend/src/metabase/questions/containers/AddToDashboard.jsx
+++ b/frontend/src/metabase/questions/containers/AddToDashboard.jsx
@@ -1,151 +1,18 @@
 import React, { Component } from "react";
 import { t } from "c-3po";
-import ModalContent from "metabase/components/ModalContent.jsx";
-import Icon from "metabase/components/Icon.jsx";
-import HeaderWithBack from "metabase/components/HeaderWithBack";
-import QuestionIcon from "metabase/components/QuestionIcon";
-
-import CollectionListLoader from "metabase/containers/CollectionListLoader";
-import QuestionListLoader from "metabase/containers/QuestionListLoader";
-
-import ExpandingSearchField from "../components/ExpandingSearchField.jsx";
 
-const QuestionRow = ({ question, onClick }) => (
-  <div className="py2 border-top border-grey-1">
-    <div className="flex flex-full align-center">
-      <QuestionIcon
-        question={question}
-        className="text-light-blue mr2"
-        size={20}
-      />
-      <div onClick={onClick}>
-        <div className="h3 mb1 text-slate text-brand-hover cursor-pointer">
-          {question.name}
-        </div>
-        {question.description ? (
-          <div className="text-slate">{question.description}</div>
-        ) : (
-          <div className="text-light-blue">{`No description yet`}</div>
-        )}
-      </div>
-    </div>
-  </div>
-);
+import ModalContent from "metabase/components/ModalContent.jsx";
+import QuestionPicker from "metabase/containers/QuestionPicker";
 
 export default class AddToDashboard extends Component {
-  state = {
-    collection: null,
-    query: null,
-  };
-
-  renderQuestionList = () => {
-    return (
-      <QuestionListLoader entityQuery={this.state.query}>
-        {({ questions }) => (
-          <div>
-            {questions.map(question => (
-              <QuestionRow
-                question={question}
-                onClick={() => this.props.onAdd(question)}
-              />
-            ))}
-          </div>
-        )}
-      </QuestionListLoader>
-    );
-  };
-
-  renderCollections = () => {
-    return (
-      <CollectionListLoader>
-        {({ collections }) => (
-          <div>
-            {/* only show the collections list if there are actually collections fixes #4668 */
-            collections.length > 0 ? (
-              <ol>
-                {collections.map((collection, index) => (
-                  <li
-                    className="text-brand-hover flex align-center border-bottom cursor-pointer py1 md-py2"
-                    key={index}
-                    onClick={() =>
-                      this.setState({
-                        collection: collection,
-                        query: { collection: collection.slug },
-                      })
-                    }
-                  >
-                    <Icon
-                      className="mr2"
-                      name="all"
-                      style={{ color: collection.color }}
-                    />
-                    <h3>{collection.name}</h3>
-                    <Icon className="ml-auto" name="chevronright" />
-                  </li>
-                ))}
-                <li
-                  className="text-brand-hover flex align-center border-bottom cursor-pointer py1 md-py2"
-                  onClick={() =>
-                    this.setState({
-                      collection: { name: t`Everything else` },
-                      query: { collection: "" },
-                    })
-                  }
-                >
-                  <Icon className="mr2" name="everything" />
-                  <h3>Everything else</h3>
-                  <Icon className="ml-auto" name="chevronright" />
-                </li>
-              </ol>
-            ) : (
-              this.renderQuestionList()
-            )}
-          </div>
-        )}
-      </CollectionListLoader>
-    );
-  };
-
   render() {
-    const { query, collection } = this.state;
     return (
-      <div className="wrapper wrapper--trim">
-        <ModalContent
-          title={t`Pick a question to add`}
-          className="mb4 scroll-y"
-          onClose={() => this.props.onClose()}
-        >
-          <div className="py1">
-            <div className="flex align-center ml3 mb3">
-              {!query ? (
-                <ExpandingSearchField
-                  defaultValue={query && query.q}
-                  onSearch={value =>
-                    this.setState({
-                      collection: null,
-                      query: { q: value },
-                    })
-                  }
-                />
-              ) : (
-                <HeaderWithBack
-                  name={collection && collection.name}
-                  onBack={() =>
-                    this.setState({ collection: null, query: null })
-                  }
-                />
-              )}
-            </div>
-          </div>
-          <div className="mx4">
-            {query
-              ? // a search term has been entered so show the questions list
-                this.renderQuestionList()
-              : // show the collections list
-                this.renderCollections()}
-          </div>
-        </ModalContent>
-      </div>
+      <ModalContent
+        title={t`Pick a question to add`}
+        onClose={this.props.onClose}
+      >
+        <QuestionPicker onChange={this.props.onAdd} />
+      </ModalContent>
     );
   }
 }
diff --git a/frontend/src/metabase/reference/Reference.css b/frontend/src/metabase/reference/Reference.css
index 16bd412f9db4cd2012afe6690b8f650aa0eeb501..6001693df14924d6f87ac189a7ade8b955298591 100644
--- a/frontend/src/metabase/reference/Reference.css
+++ b/frontend/src/metabase/reference/Reference.css
@@ -1,6 +1,6 @@
 :root {
-  --title-color: #606e7b;
-  --subtitle-color: #aab7c3;
+  --title-color: var(--color-text-medium);
+  --subtitle-color: var(--color-text-medium);
   --icon-width: calc(48px + 1rem);
 }
 
@@ -31,7 +31,7 @@
 }
 
 :local(.schemaSeparator) {
-  composes: text-grey-2 mt2 from "style";
+  composes: text-light mt2 from "style";
   margin-left: var(--icon-width);
   font-size: 18px;
 }
@@ -103,7 +103,7 @@
 :local(.guideEditHeader) {
   composes: full text-body my4 from "style";
   max-width: 550px;
-  color: var(--dark-color);
+  color: var(--color-text-dark);
 }
 
 :local(.guideEditHeaderTitle) {
@@ -138,7 +138,7 @@
 
 :local(.guideEditSubtitle) {
   composes: text-body from "style";
-  color: var(--grey-2);
+  color: var(--color-text-light);
   font-size: 16px;
   max-width: 700px;
 }
diff --git a/frontend/src/metabase/reference/components/Detail.css b/frontend/src/metabase/reference/components/Detail.css
index 027b4273e7eb0505d8d37896e398facd651fbf7e..117833b87a2329647078a35117f8db8d92144c23 100644
--- a/frontend/src/metabase/reference/components/Detail.css
+++ b/frontend/src/metabase/reference/components/Detail.css
@@ -1,8 +1,8 @@
 :root {
-  --title-color: #606e7b;
-  --subtitle-color: #aab7c3;
-  --muted-color: #deeaf1;
-  --blue-color: #2d86d4;
+  --title-color: var(--color-text-medium);
+  --subtitle-color: var(--color-text-medium);
+  --muted-color: var(--color-text-light);
+  --blue-color: var(--color-brand);
   --icon-width: calc(48px + 1rem);
 }
 
diff --git a/frontend/src/metabase/reference/components/EditButton.css b/frontend/src/metabase/reference/components/EditButton.css
index 31f8a7ebaa523f4b111519ca03c27875fe90f160..a945c7420a10b4bbc46eb9253b3112816066a86a 100644
--- a/frontend/src/metabase/reference/components/EditButton.css
+++ b/frontend/src/metabase/reference/components/EditButton.css
@@ -1,12 +1,12 @@
 :local(.editButton) {
   composes: flex align-center text-dark p0 mx1 from "style";
-  color: var(--primary-button-bg-color);
+  color: var(--color-brand);
   font-weight: normal;
   font-size: 16px;
 }
 
 :local(.editButton):hover {
-  color: color(var(--primary-button-border-color) shade(10%));
+  color: var(--color-brand);
   transition: color 0.3s linear;
 }
 
diff --git a/frontend/src/metabase/reference/components/EditHeader.css b/frontend/src/metabase/reference/components/EditHeader.css
index fc3ddd5cfcf8c5aa06c8fc70f2933d9d2c770051..a71c5bc8147839ea4b06a922fed0cf061b513b89 100644
--- a/frontend/src/metabase/reference/components/EditHeader.css
+++ b/frontend/src/metabase/reference/components/EditHeader.css
@@ -1,5 +1,5 @@
 :root {
-  --edit-header-color: #6cafed;
+  --edit-header-color: var(--color-brand);
 }
 
 :local(.editHeader) {
diff --git a/frontend/src/metabase/reference/components/EditableReferenceHeader.jsx b/frontend/src/metabase/reference/components/EditableReferenceHeader.jsx
index 723134c72f7b826d670fc380934fa0b41a1fecea..a6dced89f7e2c843e12a23908ba8b8c13623ccfc 100644
--- a/frontend/src/metabase/reference/components/EditableReferenceHeader.jsx
+++ b/frontend/src/metabase/reference/components/EditableReferenceHeader.jsx
@@ -14,6 +14,8 @@ import InputBlurChange from "metabase/components/InputBlurChange.jsx";
 import Ellipsified from "metabase/components/Ellipsified.jsx";
 import EditButton from "metabase/reference/components/EditButton.jsx";
 
+import colors from "metabase/lib/colors";
+
 const EditableReferenceHeader = ({
   entity = {},
   table,
@@ -36,7 +38,10 @@ const EditableReferenceHeader = ({
     >
       <div className={L.leftIcons}>
         {headerIcon && (
-          <IconBorder borderWidth="0" style={{ backgroundColor: "#E9F4F8" }}>
+          <IconBorder
+            borderWidth="0"
+            style={{ backgroundColor: colors["bg-medium"] }}
+          >
             <Icon
               className="text-brand"
               name={headerIcon}
diff --git a/frontend/src/metabase/reference/components/Field.css b/frontend/src/metabase/reference/components/Field.css
index b233ab73b53385b793748289a2294254f80d7035..f0de738eca40dc0514c342cbc27121d6880fd041 100644
--- a/frontend/src/metabase/reference/components/Field.css
+++ b/frontend/src/metabase/reference/components/Field.css
@@ -1,5 +1,5 @@
 :root {
-  --title-color: #606e7b;
+  --title-color: var(--color-text-medium);
 }
 
 :local(.field) {
diff --git a/frontend/src/metabase/reference/components/FieldToGroupBy.css b/frontend/src/metabase/reference/components/FieldToGroupBy.css
index 18f16afe546a63225b4484eb6d32a2ec2f8ed2c7..babb272e142e204e021cdcfb7eb3c988d62dfe32 100644
--- a/frontend/src/metabase/reference/components/FieldToGroupBy.css
+++ b/frontend/src/metabase/reference/components/FieldToGroupBy.css
@@ -1,5 +1,5 @@
 :local(.fieldToGroupByText) {
   composes: flex-full from "style";
   font-size: 16px;
-  color: #aab7c3;
+  color: var(--color-text-medium);
 }
diff --git a/frontend/src/metabase/reference/components/Formula.css b/frontend/src/metabase/reference/components/Formula.css
index 2ece8eac4c48338425d96439d97e0463ca979cbc..945461add2420e1b638636e723dd019494a133ae 100644
--- a/frontend/src/metabase/reference/components/Formula.css
+++ b/frontend/src/metabase/reference/components/Formula.css
@@ -4,7 +4,7 @@
 
 :local(.formula) {
   composes: bordered rounded my2 from "style";
-  background-color: #fbfcfd;
+  background-color: var(--color-bg-light);
   margin-left: var(--icon-width);
   max-width: 550px;
   cursor: pointer;
diff --git a/frontend/src/metabase/reference/components/GuideDetail.jsx b/frontend/src/metabase/reference/components/GuideDetail.jsx
index dc032bc3ec128e192ec5ac49377f4abe743fe130..cc51d2c471872d0b76c24f365bec71fb5f6e6b98 100644
--- a/frontend/src/metabase/reference/components/GuideDetail.jsx
+++ b/frontend/src/metabase/reference/components/GuideDetail.jsx
@@ -150,13 +150,13 @@ const ItemTitle = ({ title, link, linkColorClass, linkHoverClass }) => (
 );
 
 const ContextHeading = ({ children }) => (
-  <h3 className="my2 text-grey-4">{children}</h3>
+  <h3 className="my2 text-medium">{children}</h3>
 );
 
 const ContextContent = ({ empty, children }) => (
   <p
     className={cx("m0 text-paragraph text-measure text-pre-wrap", {
-      "text-grey-3": empty,
+      "text-medium": empty,
     })}
   >
     {children}
diff --git a/frontend/src/metabase/reference/components/GuideDetailEditor.jsx b/frontend/src/metabase/reference/components/GuideDetailEditor.jsx
index f6daefd5c3f17475659c5b51c213c295f5439615..daf53b92524cb618b9d97b554d59d1475491626a 100644
--- a/frontend/src/metabase/reference/components/GuideDetailEditor.jsx
+++ b/frontend/src/metabase/reference/components/GuideDetailEditor.jsx
@@ -142,7 +142,7 @@ const GuideDetailEditor = ({
             />
           )}
         </div>
-        <div className="ml-auto cursor-pointer text-grey-2">
+        <div className="ml-auto cursor-pointer text-light">
           <Tooltip tooltip={t`Remove item`}>
             <Icon name="close" width={16} height={16} onClick={removeField} />
           </Tooltip>
diff --git a/frontend/src/metabase/reference/components/GuideEditSection.css b/frontend/src/metabase/reference/components/GuideEditSection.css
index 62c0d835be74e5c49b332745799a41d5e7879ae4..8043cdcbe747ca9188323c3e94f11e7454edebd8 100644
--- a/frontend/src/metabase/reference/components/GuideEditSection.css
+++ b/frontend/src/metabase/reference/components/GuideEditSection.css
@@ -4,7 +4,7 @@
 }
 
 :local(.guideEditSectionDisabled) {
-  composes: text-grey-3 from "style";
+  composes: text-medium from "style";
 }
 
 :local(.guideEditSectionCollapsedIcon) {
diff --git a/frontend/src/metabase/reference/components/ReferenceHeader.css b/frontend/src/metabase/reference/components/ReferenceHeader.css
index f6ab2aa00f67b452b11a953fec97950c7a1e1074..c3173e09efd76505a443002a9331fede0267d840 100644
--- a/frontend/src/metabase/reference/components/ReferenceHeader.css
+++ b/frontend/src/metabase/reference/components/ReferenceHeader.css
@@ -1,5 +1,5 @@
 :root {
-  --title-color: #606e7b;
+  --title-color: var(--color-text-medium);
   --icon-width: calc(48px + 1rem);
 }
 
@@ -7,7 +7,7 @@
   composes: flex flex-full border-bottom text-dark text-bold from "style";
   overflow: hidden;
   align-items: center;
-  border-color: #edf5fb;
+  border-color: var(--color-border);
 }
 
 :local(.headerTextInput) {
@@ -29,17 +29,17 @@
 }
 
 :local(.subheaderLink) {
-  color: var(--primary-button-bg-color);
+  color: var(--color-brand);
   text-decoration: none;
 }
 
 :local(.subheaderLink):hover {
-  color: color(var(--primary-button-border-color) shade(10%));
+  color: var(--color-brand);
   transition: color 0.3s linear;
 }
 
 :local(.headerSchema) {
-  composes: text-grey-2 absolute from "style";
+  composes: text-light absolute from "style";
   left: var(--icon-width);
   top: -10px;
   font-size: 12px;
diff --git a/frontend/src/metabase/reference/components/ReferenceHeader.jsx b/frontend/src/metabase/reference/components/ReferenceHeader.jsx
index 77f82e65c6986dbaebfed7cad8fa5df201284c4c..8584fad9baca506e3f9fe60de1639a81d0b46e32 100644
--- a/frontend/src/metabase/reference/components/ReferenceHeader.jsx
+++ b/frontend/src/metabase/reference/components/ReferenceHeader.jsx
@@ -12,6 +12,7 @@ import IconBorder from "metabase/components/IconBorder.jsx";
 import Icon from "metabase/components/Icon.jsx";
 import Ellipsified from "metabase/components/Ellipsified.jsx";
 import { t } from "c-3po";
+import colors from "metabase/lib/colors";
 
 const ReferenceHeader = ({
   name,
@@ -24,7 +25,10 @@ const ReferenceHeader = ({
     <div className={cx("relative", L.header)}>
       <div className={L.leftIcons}>
         {headerIcon && (
-          <IconBorder borderWidth="0" style={{ backgroundColor: "#E9F4F8" }}>
+          <IconBorder
+            borderWidth="0"
+            style={{ backgroundColor: colors["bg-medium"] }}
+          >
             <Icon
               className="text-brand"
               name={headerIcon}
diff --git a/frontend/src/metabase/reference/guide/BaseSidebar.jsx b/frontend/src/metabase/reference/guide/BaseSidebar.jsx
index 52add1ae1da71ad0e1c5cf566f09e8c4b5228a44..116704e06f9f46c183b4934f4f4e6e34e5e6413e 100644
--- a/frontend/src/metabase/reference/guide/BaseSidebar.jsx
+++ b/frontend/src/metabase/reference/guide/BaseSidebar.jsx
@@ -20,12 +20,6 @@ const BaseSidebar = ({ style, className }) => (
       />
     </div>
     <ol>
-      <SidebarItem
-        key="/reference/guide"
-        href="/reference/guide"
-        icon="reference"
-        name={t`Start here`}
-      />
       <SidebarItem
         key="/reference/metrics"
         href="/reference/metrics"
diff --git a/frontend/src/metabase/reference/utils.js b/frontend/src/metabase/reference/utils.js
index 58e8122417cb1f1ea9ee3027444b29e5a70919e7..adcff2dff8774f2cce34d2f025bf38a0a1c94cdb 100644
--- a/frontend/src/metabase/reference/utils.js
+++ b/frontend/src/metabase/reference/utils.js
@@ -90,7 +90,7 @@ export const getQuestion = ({
     return assocIn(
       question,
       ["dataset_query", "query", "aggregation"],
-      ["METRIC", metricId],
+      ["metric", metricId],
     );
   }
 
@@ -98,7 +98,7 @@ export const getQuestion = ({
     return assocIn(
       question,
       ["dataset_query", "query", "filter"],
-      ["AND", ["SEGMENT", segmentId]],
+      ["and", ["segment", segmentId]],
     );
   }
 
diff --git a/frontend/src/metabase/routes.jsx b/frontend/src/metabase/routes.jsx
index d4af90d0e877abcb7e2ffa5afd3d27d79184d47c..bec70959aba10109c797f1128c7736b47ff60c86 100644
--- a/frontend/src/metabase/routes.jsx
+++ b/frontend/src/metabase/routes.jsx
@@ -16,6 +16,7 @@ import App from "metabase/App.jsx";
 import HomepageApp from "metabase/home/containers/HomepageApp";
 
 // auth containers
+import AuthApp from "metabase/auth/AuthApp";
 import ForgotPasswordApp from "metabase/auth/containers/ForgotPasswordApp.jsx";
 import LoginApp from "metabase/auth/containers/LoginApp.jsx";
 import LogoutApp from "metabase/auth/containers/LogoutApp.jsx";
@@ -37,13 +38,11 @@ import QueryBuilder from "metabase/query_builder/containers/QueryBuilder.jsx";
 
 import CollectionEdit from "metabase/collections/containers/CollectionEdit.jsx";
 import CollectionCreate from "metabase/collections/containers/CollectionCreate.jsx";
-import CollectionPermissions from "metabase/admin/permissions/containers/CollectionsPermissionsApp.jsx";
 import ArchiveCollectionModal from "metabase/components/ArchiveCollectionModal";
 import CollectionPermissionsModal from "metabase/admin/permissions/containers/CollectionPermissionsModal";
 import UserCollectionList from "metabase/containers/UserCollectionList";
 
 import PulseEditApp from "metabase/pulse/containers/PulseEditApp.jsx";
-import PulseListApp from "metabase/pulse/containers/PulseListApp.jsx";
 import PulseMoveModal from "metabase/pulse/components/PulseMoveModal";
 import SetupApp from "metabase/setup/containers/SetupApp.jsx";
 import PostSetupApp from "metabase/setup/containers/PostSetupApp.jsx";
@@ -56,8 +55,8 @@ import {
 } from "metabase/new_query/router_wrappers";
 
 import CreateDashboardModal from "metabase/components/CreateDashboardModal";
-import NotFound from "metabase/components/NotFound.jsx";
-import Unauthorized from "metabase/components/Unauthorized.jsx";
+
+import { NotFound, Unauthorized } from "metabase/containers/ErrorPages";
 
 // Reference Guide
 import GettingStartedGuideContainer from "metabase/reference/guide/GettingStartedGuideContainer.jsx";
@@ -177,7 +176,7 @@ export const getRoutes = store => (
       }}
     >
       {/* AUTH */}
-      <Route path="/auth">
+      <Route path="/auth" component={AuthApp}>
         <IndexRedirect to="/auth/login" />
         <Route component={IsNotAuthenticated}>
           <Route path="login" title={t`Login`} component={LoginApp} />
@@ -204,6 +203,7 @@ export const getRoutes = store => (
         </Route>
 
         <Route path="collection/:collectionId" component={CollectionLanding}>
+          <ModalRoute path="edit" modal={CollectionEdit} />
           <ModalRoute path="archive" modal={ArchiveCollectionModal} />
           <ModalRoute path="new_collection" modal={CollectionCreate} />
           <ModalRoute path="new_dashboard" modal={CreateDashboardModal} />
@@ -251,13 +251,11 @@ export const getRoutes = store => (
 
       <Route path="/collections">
         <Route path="create" component={CollectionCreate} />
-        <Route path="permissions" component={CollectionPermissions} />
-        <Route path=":collectionId" component={CollectionEdit} />
       </Route>
 
       {/* REFERENCE */}
       <Route path="/reference" title={`Data Reference`}>
-        <IndexRedirect to="/reference/guide" />
+        <IndexRedirect to="/reference/databases" />
         <Route
           path="guide"
           title={`Getting Started`}
@@ -320,7 +318,8 @@ export const getRoutes = store => (
 
       {/* PULSE */}
       <Route path="/pulse" title={t`Pulses`}>
-        <IndexRoute component={PulseListApp} />
+        {/* NOTE: legacy route, not linked to in app */}
+        <IndexRedirect to="/search" query={{ type: "pulse" }} />
         <Route path="create" component={PulseEditApp} />
         <Route path=":pulseId">
           <IndexRoute component={PulseEditApp} />
@@ -364,6 +363,10 @@ export const getRoutes = store => (
       }
     />
     <Redirect from="/dash/:dashboardId" to="/dashboard/:dashboardId" />
+    <Redirect
+      from="/collections/permissions"
+      to="/admin/permissions/collections"
+    />
 
     {/* MISC */}
     <Route path="/unauthorized" component={Unauthorized} />
diff --git a/frontend/src/metabase/selectors/user.js b/frontend/src/metabase/selectors/user.js
index b515503e99cc141b701547458c893fdfc1be6e05..6fa15c0a161c339d3f1fc07431d28dd20970d162 100644
--- a/frontend/src/metabase/selectors/user.js
+++ b/frontend/src/metabase/selectors/user.js
@@ -1,4 +1,19 @@
+import { createSelector } from "reselect";
+
 export const getUser = state => state.currentUser;
 
-export const getUserIsAdmin = state =>
-  (getUser(state) || {}).is_superuser || false;
+export const getUserIsAdmin = createSelector(
+  [getUser],
+  user => (user && user.is_superuser) || false,
+);
+
+export const getUserPersonalCollectionId = createSelector(
+  [getUser],
+  user => (user && user.personal_collection_id) || null,
+);
+
+export const getUserDefaultCollectionId = createSelector(
+  [getUser, getUserIsAdmin, getUserPersonalCollectionId],
+  (user, isAdmin, personalCollectionId) =>
+    isAdmin ? null : personalCollectionId,
+);
diff --git a/frontend/src/metabase/setup/components/CollapsedStep.jsx b/frontend/src/metabase/setup/components/CollapsedStep.jsx
index 0744cb05d470744f908929d41751b99f98a1c9f3..ea3622b603a0132844e4b56ae478a1a99273e244 100644
--- a/frontend/src/metabase/setup/components/CollapsedStep.jsx
+++ b/frontend/src/metabase/setup/components/CollapsedStep.jsx
@@ -27,6 +27,7 @@ export default class CollapsedStep extends Component {
       rounded: true,
       full: true,
       relative: true,
+      "bg-white": true,
       "SetupStep--completed shadowed": isCompleted,
       "SetupStep--todo": !isCompleted,
     });
diff --git a/frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx b/frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx
index 0a151a3c965b994c0c2c73909d4e29d8fdee0c45..c05e95c266be33d6976fc57474d5af2b6bdd6e24 100644
--- a/frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx
+++ b/frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx
@@ -158,7 +158,7 @@ export default class DatabaseConnectionStep extends Component {
       );
     } else {
       return (
-        <section className="SetupStep rounded full relative SetupStep--active">
+        <section className="SetupStep bg-white rounded full relative SetupStep--active">
           <StepTitle title={stepText} circleText={"2"} />
           <div className="mb4">
             <div style={{ maxWidth: 600 }} className="Form-field Form-offset">
diff --git a/frontend/src/metabase/setup/components/DatabaseSchedulingStep.jsx b/frontend/src/metabase/setup/components/DatabaseSchedulingStep.jsx
index f8ac341ae53717cba2672d74c53004c5ff501892..1512d550a998c480077bca24697d920aa1fd8c77 100644
--- a/frontend/src/metabase/setup/components/DatabaseSchedulingStep.jsx
+++ b/frontend/src/metabase/setup/components/DatabaseSchedulingStep.jsx
@@ -62,7 +62,7 @@ export default class DatabaseSchedulingStep extends Component {
       );
     } else {
       return (
-        <section className="SetupStep rounded full relative SetupStep--active">
+        <section className="SetupStep bg-white rounded full relative SetupStep--active">
           <StepTitle title={stepText} circleText={schedulingIcon} />
           <div className="mb4">
             <div className="text-default">
diff --git a/frontend/src/metabase/setup/components/PreferencesStep.jsx b/frontend/src/metabase/setup/components/PreferencesStep.jsx
index a802869a409939fa8ee5404f75dffb6097006d89..24aab7b3a42f149c82c6593ae28f64154e48eadc 100644
--- a/frontend/src/metabase/setup/components/PreferencesStep.jsx
+++ b/frontend/src/metabase/setup/components/PreferencesStep.jsx
@@ -69,7 +69,7 @@ export default class PreferencesStep extends Component {
       );
     } else {
       return (
-        <section className="SetupStep rounded full relative SetupStep--active">
+        <section className="SetupStep bg-white rounded full relative SetupStep--active">
           <StepTitle title={stepText} circleText={"3"} />
           <form onSubmit={this.formSubmitted.bind(this)} noValidate>
             <div className="Form-field Form-offset">
diff --git a/frontend/src/metabase/setup/components/Setup.jsx b/frontend/src/metabase/setup/components/Setup.jsx
index 83aea36c60d3399cec6e97d207567547efca39c9..cf10d1f08b030c53550cebd3a98775bec48da4ec 100644
--- a/frontend/src/metabase/setup/components/Setup.jsx
+++ b/frontend/src/metabase/setup/components/Setup.jsx
@@ -138,7 +138,7 @@ export default class Setup extends Component {
               />
 
               {setupComplete ? (
-                <section className="SetupStep rounded SetupStep--active flex flex-column layout-centered p4">
+                <section className="SetupStep bg-white rounded SetupStep--active flex flex-column layout-centered p4">
                   <h1
                     style={{ fontSize: "xx-large" }}
                     className="text-light pt2 pb2"
diff --git a/frontend/src/metabase/setup/components/UserStep.jsx b/frontend/src/metabase/setup/components/UserStep.jsx
index 6cdcbf42c8a8a724897afe17e2631a882502ad27..34f4f69f702563f3199b612e4ec1680284b8d85a 100644
--- a/frontend/src/metabase/setup/components/UserStep.jsx
+++ b/frontend/src/metabase/setup/components/UserStep.jsx
@@ -1,6 +1,7 @@
 /* eslint "react/prop-types": "warn" */
 import React, { Component } from "react";
 import PropTypes from "prop-types";
+import { Box, Flex } from "grid-styled";
 import { t } from "c-3po";
 import FormField from "metabase/components/form/FormField.jsx";
 import FormLabel from "metabase/components/form/FormLabel.jsx";
@@ -148,7 +149,7 @@ export default class UserStep extends Component {
     let { activeStep, setActiveStep, stepNumber, userDetails } = this.props;
     let { formError, passwordError, valid } = this.state;
 
-    const passwordComplexityDesc = MetabaseSettings.passwordComplexity();
+    const passwordComplexityDesc = MetabaseSettings.passwordComplexityDescription();
     const stepText =
       activeStep <= stepNumber
         ? t`What should we call you?`
@@ -166,7 +167,7 @@ export default class UserStep extends Component {
       );
     } else {
       return (
-        <section className="SetupStep SetupStep--active rounded full relative">
+        <section className="SetupStep SetupStep--active rounded bg-white full relative">
           <StepTitle title={stepText} circleText={"1"} />
           <form
             name="userForm"
@@ -175,43 +176,45 @@ export default class UserStep extends Component {
             className="mt2"
           >
             <FormField
-              className="Grid mb3"
+              className="mb3"
               fieldName="first_name"
               formError={formError}
             >
-              <div>
-                <FormLabel
-                  title={t`First name`}
-                  fieldName="first_name"
-                  formError={formError}
-                />
-                <input
-                  className="Form-input Form-offset full"
-                  name="first_name"
-                  defaultValue={userDetails ? userDetails.first_name : ""}
-                  placeholder="Johnny"
-                  required
-                  autoFocus={true}
-                  onChange={this.onFirstNameChange}
-                />
-                <span className="Form-charm" />
-              </div>
-              <div>
-                <FormLabel
-                  title={t`Last name`}
-                  fieldName="last_name"
-                  formError={formError}
-                />
-                <input
-                  className="Form-input Form-offset"
-                  name="last_name"
-                  defaultValue={userDetails ? userDetails.last_name : ""}
-                  placeholder="Appleseed"
-                  required
-                  onChange={this.onLastNameChange}
-                />
-                <span className="Form-charm" />
-              </div>
+              <Flex align="center">
+                <Box>
+                  <FormLabel
+                    title={t`First name`}
+                    fieldName="first_name"
+                    formError={formError}
+                  />
+                  <input
+                    className="Form-input Form-offset"
+                    name="first_name"
+                    defaultValue={userDetails ? userDetails.first_name : ""}
+                    placeholder="Johnny"
+                    required
+                    autoFocus={true}
+                    onChange={this.onFirstNameChange}
+                  />
+                  <span className="Form-charm" />
+                </Box>
+                <Box>
+                  <FormLabel
+                    title={t`Last name`}
+                    fieldName="last_name"
+                    formError={formError}
+                  />
+                  <input
+                    className="Form-input Form-offset"
+                    name="last_name"
+                    defaultValue={userDetails ? userDetails.last_name : ""}
+                    placeholder="Appleseed"
+                    required
+                    onChange={this.onLastNameChange}
+                  />
+                  <span className="Form-charm" />
+                </Box>
+              </Flex>
             </FormField>
 
             <FormField fieldName="email" formError={formError}>
diff --git a/frontend/src/metabase/setup/containers/PostSetupApp.jsx b/frontend/src/metabase/setup/containers/PostSetupApp.jsx
index 742d1e694ba5a9e352680ff75ae98c692fa2c2a5..e3d78fb91de468fcfcec9b69a5c2847e91d56445 100644
--- a/frontend/src/metabase/setup/containers/PostSetupApp.jsx
+++ b/frontend/src/metabase/setup/containers/PostSetupApp.jsx
@@ -93,7 +93,7 @@ export default class PostSetupApp extends Component {
           <div className="m4 text-centered">
             <Link
               to="/"
-              className="no-decoration text-bold text-grey-3 text-grey-4-hover"
+              className="no-decoration text-bold text-medium text-medium-hover"
             >
               {t`I'm done exploring for now`}
             </Link>
diff --git a/frontend/src/metabase/store.js b/frontend/src/metabase/store.js
index 50c8777688d60b8f7999d706c1ae7c0f6e4dc483..022146bf976938a7c68703aeb6c68d0ccb3706c9 100644
--- a/frontend/src/metabase/store.js
+++ b/frontend/src/metabase/store.js
@@ -3,7 +3,6 @@
 import { combineReducers, applyMiddleware, createStore, compose } from "redux";
 import { reducer as form } from "redux-form";
 import { routerReducer as routing, routerMiddleware } from "react-router-redux";
-import MetabaseAnalytics from "metabase/lib/analytics";
 
 import promise from "redux-promise";
 import logger from "redux-logger";
@@ -29,73 +28,6 @@ const devToolsExtension = window.devToolsExtension
   ? window.devToolsExtension()
   : f => f;
 
-// Look for redux action names that take the form `metabase/<app_section>/<ACTION_NAME>
-const METABASE_TRACKABLE_ACTION_REGEX = /^metabase\/(.+)\/([^\/]+)$/;
-
-/**
- * Track events by looking at redux dispatch
- * -----
- * This redux middleware is meant to help automate event capture for instances
- * that opt in to anonymous tracking by looking at redux actions and either
- * using the name of the action, or defined analytics metadata to send event
- * data to GA. This makes it un-necessary to instrument individual redux actions
- *
- * Any actions with a name takes the form `metabase/.../...` will be automatially captured
- *
- * Ignoring actions:
- * Any actions we want to ignore can be bypassed by including a meta object with ignore: true
- * {
- *   type: "...",
- *   meta: {
- *     analytics: { ignore: true }
- *   }
- * }
- *
- * Customizing event names:
- * If we don't want to use the action name metadata can be added to the action
- * to customize the name
- *
- * {
- *   type: "...",
- *   meta: {
- *     analytics: {
- *       category: "foo",
- *       action: "bar",
- *       label: "baz",
- *       value: "qux"
- *    }
- *   }
- *}
- */
-export const trackEvent = ({ dispatch, getState }) => next => action => {
-  // look for the meta analytics object if it exists, this gets used to
-  // do customization of the event identifiers sent to GA
-  const analytics = action.meta && action.meta.analytics;
-
-  if (analytics) {
-    if (!analytics.ignore) {
-      MetabaseAnalytics.trackEvent(
-        analytics.category,
-        analytics.action,
-        analytics.label,
-        analytics.value,
-      );
-    }
-  } else if (METABASE_TRACKABLE_ACTION_REGEX.test(action.type)) {
-    // if there is no analytics metadata on the action, look to see if it's
-    // an action name we want to track based on the format of the aciton name
-
-    // eslint doesn't like the _ to ignore the first bit
-    // eslint-disable-next-line
-    const [_, categoryName, actionName] = action.type.match(
-      METABASE_TRACKABLE_ACTION_REGEX,
-    );
-
-    MetabaseAnalytics.trackEvent(categoryName, actionName);
-  }
-  return next(action);
-};
-
 export function getStore(reducers, history, intialState, enhancer = a => a) {
   const reducer = combineReducers({
     ...reducers,
@@ -105,7 +37,6 @@ export function getStore(reducers, history, intialState, enhancer = a => a) {
 
   const middleware = [
     thunkWithDispatchAction,
-    trackEvent,
     promise,
     ...(DEBUG ? [logger] : []),
     ...(history ? [routerMiddleware(history)] : []),
diff --git a/frontend/src/metabase/tutorial/PageFlag.css b/frontend/src/metabase/tutorial/PageFlag.css
index 91cbded10f50e93243516bc4383d6478a941c8bf..879ce597a47fd8c1554cf324bda9b7d36e299740 100644
--- a/frontend/src/metabase/tutorial/PageFlag.css
+++ b/frontend/src/metabase/tutorial/PageFlag.css
@@ -4,13 +4,13 @@
   position: relative;
   min-width: 50px;
   height: 24px;
-  background-color: rgb(53, 141, 248);
+  background-color: var(--color-brand);
   box-sizing: content-box;
   border-top-right-radius: 8px;
   border-bottom-right-radius: 8px;
   border: 3px solid white;
   border-left: 1px solid white;
-  box-shadow: 2px 2px 6px rgba(0, 0, 0, 0.5);
+  box-shadow: 2px 2px 6px var(--color-shadow);
 
   color: white;
   font-weight: bold;
@@ -36,7 +36,7 @@
   left: -12px;
   border-top: 12px solid transparent;
   border-bottom: 12px solid transparent;
-  border-right: 12px solid rgb(53, 141, 248);
+  border-right: 12px solid var(--color-brand);
 }
 
 .PageFlag:before {
diff --git a/frontend/src/metabase/tutorial/Portal.jsx b/frontend/src/metabase/tutorial/Portal.jsx
index 1378b4a396e684c7587db648f177fdfceb5ca6a0..cf3255a2660cf16b564f0ade888f6d8df1b7069f 100644
--- a/frontend/src/metabase/tutorial/Portal.jsx
+++ b/frontend/src/metabase/tutorial/Portal.jsx
@@ -1,6 +1,7 @@
 import React, { Component } from "react";
 
 import BodyComponent from "metabase/components/BodyComponent.jsx";
+import colors from "metabase/lib/colors";
 
 @BodyComponent
 export default class Portal extends Component {
@@ -69,8 +70,8 @@ export default class Portal extends Component {
     return {
       position: "absolute",
       boxSizing: "content-box",
-      border: "10000px solid rgba(0,0,0,0.70)",
-      boxShadow: "inset 0px 0px 8px rgba(0,0,0,0.25)",
+      border: `10000px solid ${colors["text-dark"]}`,
+      boxShadow: `inset 0px 0px 8px ${colors["shadow"]}`,
       transform: "translate(-10000px, -10000px)",
       borderRadius: "10010px",
       pointerEvents: "none",
diff --git a/frontend/src/metabase/tutorial/TutorialModal.jsx b/frontend/src/metabase/tutorial/TutorialModal.jsx
index f0f08f863354595b11858ab0aa5431bcf55793a7..3c0c8496c8affce25d1cc5c3f2f81f0f21a7d4a5 100644
--- a/frontend/src/metabase/tutorial/TutorialModal.jsx
+++ b/frontend/src/metabase/tutorial/TutorialModal.jsx
@@ -13,7 +13,7 @@ export default class TutorialModal extends Component {
       <div className="TutorialModalContent p2">
         <div className="flex">
           <a
-            className="text-grey-4 p1 cursor-pointer flex-align-right"
+            className="text-medium p1 cursor-pointer flex-align-right"
             onClick={this.props.onClose}
           >
             <Icon name="close" size={16} />
@@ -23,14 +23,14 @@ export default class TutorialModal extends Component {
         <div className="flex">
           {showBackButton && (
             <a
-              className="text-grey-4 cursor-pointer"
+              className="text-medium cursor-pointer"
               onClick={this.props.onBack}
             >
               back
             </a>
           )}
           {showStepCount && (
-            <span className="text-grey-4 flex-align-right">
+            <span className="text-medium flex-align-right">
               {modalStepIndex + 1} {t`of`} {modalStepCount}
             </span>
           )}
diff --git a/frontend/src/metabase/user/components/SetUserPassword.jsx b/frontend/src/metabase/user/components/SetUserPassword.jsx
index 1c793287614f3b59388f934867d5e5dfc09fa29f..d75cbe2d78473d66b9d50010acea25dab7390d93 100644
--- a/frontend/src/metabase/user/components/SetUserPassword.jsx
+++ b/frontend/src/metabase/user/components/SetUserPassword.jsx
@@ -88,7 +88,9 @@ export default class SetUserPassword extends Component {
   render() {
     const { updatePasswordResult } = this.props;
     let { formError, valid } = this.state;
-    const passwordComplexity = MetabaseSettings.passwordComplexity(true);
+    const passwordComplexity = MetabaseSettings.passwordComplexityDescription(
+      true,
+    );
 
     formError =
       updatePasswordResult && !formError ? updatePasswordResult : formError;
diff --git a/frontend/src/metabase/user/components/UpdateUserDetails.jsx b/frontend/src/metabase/user/components/UpdateUserDetails.jsx
index d398fa9393fc9fe822c60c4c95651f8e8a469d67..0f1e3e0f5872a69cca3b440a04075ec7f02053b6 100644
--- a/frontend/src/metabase/user/components/UpdateUserDetails.jsx
+++ b/frontend/src/metabase/user/components/UpdateUserDetails.jsx
@@ -144,7 +144,7 @@ export default class UpdateUserDetails extends Component {
               ref="email"
               className={cx("Form-offset full", {
                 "Form-input": !managed,
-                "text-grey-2 h1 borderless mt1": managed,
+                "text-light h1 borderless mt1": managed,
               })}
               name="email"
               defaultValue={user ? user.email : null}
diff --git a/frontend/src/metabase/user/components/UserSettings.jsx b/frontend/src/metabase/user/components/UserSettings.jsx
index 443b02fb749e6c7f2220733a8dfa785dad717e49..68d49b524e6e8692efd0bef937889bd16dc05d51 100644
--- a/frontend/src/metabase/user/components/UserSettings.jsx
+++ b/frontend/src/metabase/user/components/UserSettings.jsx
@@ -51,7 +51,7 @@ export default class UserSettings extends Component {
       <div>
         <div className="py4 border-bottom">
           <div className="wrapper wrapper--trim">
-            <h2 className="text-grey-4">{t`Account settings`}</h2>
+            <h2 className="text-medium">{t`Account settings`}</h2>
           </div>
         </div>
         <div className="mt2 md-mt4 wrapper wrapper--trim">
diff --git a/frontend/src/metabase/visualizations/components/CardRenderer.jsx b/frontend/src/metabase/visualizations/components/CardRenderer.jsx
index 93c108316f20f475ccacaa4dfe4ef102f63122f7..0a7843552edb1e25dcf1082ea1e4b2faefd9a323 100644
--- a/frontend/src/metabase/visualizations/components/CardRenderer.jsx
+++ b/frontend/src/metabase/visualizations/components/CardRenderer.jsx
@@ -16,7 +16,7 @@ type Props = VisualizationProps & {
   renderer: (element: Element, props: VisualizationProps) => DeregisterFunction,
 };
 
-@ExplicitSize
+@ExplicitSize()
 export default class CardRenderer extends Component {
   props: Props;
 
@@ -32,9 +32,7 @@ export default class CardRenderer extends Component {
   shouldComponentUpdate(nextProps: Props) {
     // a chart only needs re-rendering when the result itself changes OR the chart type is different
     let sameSize =
-      // $FlowFixMe: width/height provided by ExplicitSize
       this.props.width === nextProps.width &&
-      // $FlowFixMe: width/height provided by ExplicitSize
       this.props.height === nextProps.height;
     let sameSeries = isSameSeries(this.props.series, nextProps.series);
     return !(sameSize && sameSeries);
@@ -61,7 +59,6 @@ export default class CardRenderer extends Component {
   }
 
   renderChart() {
-    // $FlowFixMe: width/height provided by ExplicitSize
     if (this.props.width == null || this.props.height == null) {
       return;
     }
diff --git a/frontend/src/metabase/visualizations/components/ChartClickActions.jsx b/frontend/src/metabase/visualizations/components/ChartClickActions.jsx
index 70c6475b5e792810b8889fb2df09af389dcb0e15..7aab5791b0c88d6975b8c1d42d23ef5afe3bc351 100644
--- a/frontend/src/metabase/visualizations/components/ChartClickActions.jsx
+++ b/frontend/src/metabase/visualizations/components/ChartClickActions.jsx
@@ -18,7 +18,7 @@ import _ from "underscore";
 
 const SECTIONS = {
   zoom: {
-    icon: "zoom",
+    icon: "zoom-in",
   },
   records: {
     icon: "table2",
@@ -164,7 +164,7 @@ export default class ChartClickActions extends Component {
         {popover ? (
           popover
         ) : (
-          <div className="text-bold text-grey-3">
+          <div className="text-bold text-medium">
             {sections.map(([key, actions]) => (
               <div
                 key={key}
diff --git a/frontend/src/metabase/visualizations/components/ChartSettings.jsx b/frontend/src/metabase/visualizations/components/ChartSettings.jsx
index 1e49a9766f8605d1e3de7fffcdbc5816f99a4966..5d7d19bcf4088037ce6cc6195820ef4c8accbbd0 100644
--- a/frontend/src/metabase/visualizations/components/ChartSettings.jsx
+++ b/frontend/src/metabase/visualizations/components/ChartSettings.jsx
@@ -6,6 +6,7 @@ import { t } from "c-3po";
 import Warnings from "metabase/query_builder/components/Warnings.jsx";
 
 import Button from "metabase/components/Button";
+import Radio from "metabase/components/Radio";
 
 import Visualization from "metabase/visualizations/components/Visualization.jsx";
 import { getSettingsWidgets } from "metabase/visualizations/lib/settings";
@@ -23,12 +24,23 @@ const Widget = ({
   value,
   onChange,
   props,
+  // NOTE: special props to support adding additional fields
+  question,
+  addField,
 }) => {
   const W = widget;
   return (
     <div className={cx("mb2", { hide: hidden, disable: disabled })}>
       {title && <h4 className="mb1">{title}</h4>}
-      {W && <W value={value} onChange={onChange} {...props} />}
+      {W && (
+        <W
+          value={value}
+          onChange={onChange}
+          question={question}
+          addField={addField}
+          {...props}
+        />
+      )}
     </div>
   );
 };
@@ -44,17 +56,14 @@ class ChartSettings extends Component {
     };
   }
 
-  getChartTypeName() {
-    let { CardVisualization } = getVisualizationTransformed(this.props.series);
-    switch (CardVisualization.identifier) {
-      case "table":
-        return "table";
-      case "scalar":
-        return "number";
-      case "funnel":
-        return "funnel";
-      default:
-        return "chart";
+  componentWillReceiveProps(nextProps) {
+    if (this.props.series !== nextProps.series) {
+      this.setState({
+        series: this._getSeries(
+          nextProps.series,
+          nextProps.series[0].card.visualization_settings,
+        ),
+      });
     }
   }
 
@@ -102,7 +111,7 @@ class ChartSettings extends Component {
   };
 
   render() {
-    const { isDashboard } = this.props;
+    const { isDashboard, question, addField } = this.props;
     const { series } = this.state;
 
     const tabs = {};
@@ -130,21 +139,14 @@ class ChartSettings extends Component {
       <div className="flex flex-column spread">
         {tabNames.length > 1 && (
           <div className="border-bottom flex flex-no-shrink pl4">
-            {tabNames.map(tabName => (
-              <div
-                className={cx(
-                  "h3 py2 mr2 border-bottom cursor-pointer text-brand-hover border-brand-hover",
-                  {
-                    "text-brand border-brand": currentTab === tabName,
-                    "border-transparent": currentTab !== tabName,
-                  },
-                )}
-                style={{ borderWidth: 3 }}
-                onClick={() => this.handleSelectTab(tabName)}
-              >
-                {tabName}
-              </div>
-            ))}
+            <Radio
+              value={currentTab}
+              onChange={this.handleSelectTab}
+              options={tabNames}
+              optionNameFn={v => v}
+              optionValueFn={v => v}
+              underlined
+            />
           </div>
         )}
         <div className="full-height relative">
@@ -152,7 +154,12 @@ class ChartSettings extends Component {
             <div className="Grid-cell Cell--1of3 scroll-y scroll-show border-right p4">
               {widgets &&
                 widgets.map(widget => (
-                  <Widget key={`${widget.id}`} {...widget} />
+                  <Widget
+                    key={`${widget.id}`}
+                    question={question}
+                    addField={addField}
+                    {...widget}
+                  />
                 ))}
             </div>
             <div className="Grid-cell flex flex-column pt2">
@@ -167,9 +174,10 @@ class ChartSettings extends Component {
                 <Visualization
                   className="spread"
                   rawSeries={series}
-                  isEditing
                   showTitle
+                  isEditing
                   isDashboard
+                  isSettings
                   showWarnings
                   onUpdateVisualizationSettings={this.handleChangeSettings}
                   onUpdateWarnings={warnings => this.setState({ warnings })}
diff --git a/frontend/src/metabase/visualizations/components/ChartWithLegend.css b/frontend/src/metabase/visualizations/components/ChartWithLegend.css
index e8a4fe9312bd98d5d5a1b1d79c9c6b9bade75ff6..4ca163572093c290f2e4b319eb4431536bd646ce 100644
--- a/frontend/src/metabase/visualizations/components/ChartWithLegend.css
+++ b/frontend/src/metabase/visualizations/components/ChartWithLegend.css
@@ -65,12 +65,12 @@
 /* DEBUG */
 /*
 :local .ChartWithLegend .Legend {
-  background-color: rgba(0,0,255,0.1);
+  background-color: color(var(--color-bg-black) alpha(-90%));
 }
 :local .ChartWithLegend .Chart {
-  background-color: rgba(0,255,0,0.1);
+  background-color: color(var(--color-success) alpha(-90%));
 }
 :local .ChartWithLegend.flexChart .Chart {
-  background-color: rgba(255,0,0,0.1);
+  background-color: color(var(--color-error) alpha(-90%));
 }
 */
diff --git a/frontend/src/metabase/visualizations/components/ChartWithLegend.jsx b/frontend/src/metabase/visualizations/components/ChartWithLegend.jsx
index 1391ccb93e5a2957dc0519c2b5379c1cf94bc666..3a2082d6daf7630be30f2a3caae6815cfb7ccd3c 100644
--- a/frontend/src/metabase/visualizations/components/ChartWithLegend.jsx
+++ b/frontend/src/metabase/visualizations/components/ChartWithLegend.jsx
@@ -11,7 +11,7 @@ import cx from "classnames";
 const GRID_ASPECT_RATIO = 4 / 3;
 const PADDING = 14;
 
-@ExplicitSize
+@ExplicitSize()
 export default class ChartWithLegend extends Component {
   static defaultProps = {
     aspectRatio: 1,
diff --git a/frontend/src/metabase/visualizations/components/ChoroplethMap.jsx b/frontend/src/metabase/visualizations/components/ChoroplethMap.jsx
index 666af838d769ce44ca6feb7996af8c793dd9f454..380539f34b8a9fa42351a1fb8a9190083d8d78b2 100644
--- a/frontend/src/metabase/visualizations/components/ChoroplethMap.jsx
+++ b/frontend/src/metabase/visualizations/components/ChoroplethMap.jsx
@@ -1,3 +1,5 @@
+/* eslint-disable no-color-literals */
+
 import React, { Component } from "react";
 import { t } from "c-3po";
 import LoadingSpinner from "metabase/components/LoadingSpinner.jsx";
@@ -30,6 +32,7 @@ import _ from "underscore";
 // ];
 // const HEAT_MAP_ZERO_COLOR = '#CCC';
 
+// TODO COLOR
 const HEAT_MAP_COLORS = [
   // "#E2F2FF",
   "#C4E4FF",
diff --git a/frontend/src/metabase/visualizations/components/FunnelNormal.css b/frontend/src/metabase/visualizations/components/FunnelNormal.css
index c1497d0458e4eba82475a1fc6ce0faf74714a363..afde8aeb77a387387bce4e369da1034908c633c5 100644
--- a/frontend/src/metabase/visualizations/components/FunnelNormal.css
+++ b/frontend/src/metabase/visualizations/components/FunnelNormal.css
@@ -1,12 +1,12 @@
 :local .Funnel {
-  color: #a2a2a2;
+  color: var(--color-text-medium);
   height: 100%;
 }
 
 :local .FunnelStep {
   width: 100%;
   min-width: 20px;
-  border-right: 1px solid #e2e2e2;
+  border-right: 1px solid var(--color-border);
 }
 
 :local .FunnelStep.Initial {
diff --git a/frontend/src/metabase/visualizations/components/LeafletGridHeatMap.jsx b/frontend/src/metabase/visualizations/components/LeafletGridHeatMap.jsx
index 96b66add16440248b9ad63e3a0b4248083c57c4c..e841cf41e01981cad6ec2b9a73b51c317159a013 100644
--- a/frontend/src/metabase/visualizations/components/LeafletGridHeatMap.jsx
+++ b/frontend/src/metabase/visualizations/components/LeafletGridHeatMap.jsx
@@ -4,6 +4,7 @@ import { t } from "c-3po";
 import d3 from "d3";
 
 import { rangeForValue } from "metabase/lib/dataset";
+import colors from "metabase/lib/colors";
 
 export default class LeafletGridHeatMap extends LeafletMap {
   componentDidMount() {
@@ -29,7 +30,7 @@ export default class LeafletGridHeatMap extends LeafletMap {
         .linear()
         .domain([min, max])
         .interpolate(d3.interpolateHcl)
-        .range([d3.rgb("#00FF00"), d3.rgb("#FF0000")]);
+        .range([d3.rgb(colors["success"]), d3.rgb(colors["error"])]);
 
       let gridSquares = gridLayer.getLayers();
       let totalSquares = Math.max(points.length, gridSquares.length);
diff --git a/frontend/src/metabase/visualizations/components/LegendHeader.jsx b/frontend/src/metabase/visualizations/components/LegendHeader.jsx
index 51f9253b50f1ad6511f1cb41df0e79085d6362d0..3f5263c7358f52bc47fd0f69c1aeceda71f13508 100644
--- a/frontend/src/metabase/visualizations/components/LegendHeader.jsx
+++ b/frontend/src/metabase/visualizations/components/LegendHeader.jsx
@@ -28,6 +28,7 @@ export default class LegendHeader extends Component {
     onChangeCardAndRun: PropTypes.func,
     actionButtons: PropTypes.node,
     description: PropTypes.string,
+    classNameWidgets: PropTypes.string,
   };
 
   static defaultProps = {
@@ -59,6 +60,7 @@ export default class LegendHeader extends Component {
       description,
       onVisualizationClick,
       visualizationIsClickable,
+      classNameWidgets,
     } = this.props;
     const showDots = series.length > 1;
     const isNarrow = this.state.width < 150;
@@ -105,12 +107,13 @@ export default class LegendHeader extends Component {
                       })
                   : null
             }
+            infoClassName={classNameWidgets}
           />,
           onRemoveSeries &&
             index > 0 && (
               <Icon
                 name="close"
-                className="text-grey-2 flex-no-shrink mr1 cursor-pointer"
+                className="text-light flex-no-shrink mr1 cursor-pointer"
                 width={12}
                 height={12}
                 onClick={() => onRemoveSeries(s.card)}
@@ -118,7 +121,12 @@ export default class LegendHeader extends Component {
             ),
         ])}
         {actionButtons && (
-          <span className="flex-no-shrink flex-align-right relative">
+          <span
+            className={cx(
+              classNameWidgets,
+              "flex-no-shrink flex-align-right relative",
+            )}
+          >
             {actionButtons}
           </span>
         )}
diff --git a/frontend/src/metabase/visualizations/components/LegendItem.jsx b/frontend/src/metabase/visualizations/components/LegendItem.jsx
index 4edacdabbbb1d8d032452380fc6d27a883563910..0ac18f12b9e56a2201d49975f42e6c942bf6dc81 100644
--- a/frontend/src/metabase/visualizations/components/LegendItem.jsx
+++ b/frontend/src/metabase/visualizations/components/LegendItem.jsx
@@ -39,6 +39,7 @@ export default class LegendItem extends Component {
       className,
       description,
       onClick,
+      infoClassName,
     } = this.props;
     return (
       <LegendLink
@@ -79,7 +80,7 @@ export default class LegendItem extends Component {
             {description && (
               <div className="hover-child">
                 <Tooltip tooltip={description} maxWidth={"22em"}>
-                  <Icon name="info" />
+                  <Icon className={infoClassName} name="info" />
                 </Tooltip>
               </div>
             )}
diff --git a/frontend/src/metabase/visualizations/components/LineAreaBarChart.css b/frontend/src/metabase/visualizations/components/LineAreaBarChart.css
index 689a80609c43a0e2857c829d494878f764361a50..a5e0d6f0c404f0e9c630347410b188070ae20f34 100644
--- a/frontend/src/metabase/visualizations/components/LineAreaBarChart.css
+++ b/frontend/src/metabase/visualizations/components/LineAreaBarChart.css
@@ -7,7 +7,7 @@
 }
 
 .LineAreaBarChart .dc-chart .grid-line.horizontal {
-  stroke: rgba(151, 151, 151, 0.2);
+  stroke: color(var(--color-text-medium) alpha(-80%));
   stroke-dasharray: 5, 5;
 }
 
@@ -23,15 +23,15 @@
 
 .LineAreaBarChart .dc-chart .axis .domain,
 .LineAreaBarChart .dc-chart .axis .tick line {
-  stroke: #dce1e4;
+  stroke: var(--color-text-light);
 }
 
 .LineAreaBarChart .dc-chart .axis .tick text {
-  fill: #93a1ab;
+  fill: var(--color-text-medium);
 }
 
 .LineAreaBarChart .dc-chart g.row text.outside {
-  fill: #c5c6c8;
+  fill: var(--color-text-light);
 }
 .LineAreaBarChart .dc-chart g.row text.inside {
   fill: white;
@@ -47,7 +47,7 @@
 
 .LineAreaBarChart .dc-chart .x-axis-label,
 .LineAreaBarChart .dc-chart .y-axis-label {
-  fill: #727479;
+  fill: var(--color-text-medium);
   font-size: 14px;
   font-weight: 900;
 }
@@ -121,7 +121,7 @@
   opacity: 0;
 }
 .LineAreaBarChart .dc-chart .line.deselected {
-  color: #ccc;
+  color: var(--color-text-light);
 }
 
 .LineAreaBarChart .dc-chart .area,
@@ -168,6 +168,6 @@
 
 /* brush handles */
 .LineAreaBarChart .dc-chart .brush .resize path {
-  fill: #f9fbfc;
-  stroke: #9ba5b1;
+  fill: var(--color-bg-light);
+  stroke: var(--color-text-medium);
 }
diff --git a/frontend/src/metabase/visualizations/components/PinMap.jsx b/frontend/src/metabase/visualizations/components/PinMap.jsx
index b700af59d562a470eaf11b683ae351df39e695a3..360e7932c606775f59eb6e4d8cd9efbdaeee435c 100644
--- a/frontend/src/metabase/visualizations/components/PinMap.jsx
+++ b/frontend/src/metabase/visualizations/components/PinMap.jsx
@@ -81,9 +81,7 @@ export default class PinMap extends Component {
     if (
       newProps.series[0].data !== this.props.series[0].data ||
       !_.isEqual(
-        // $FlowFixMe
         _.pick(newProps.settings, ...SETTINGS_KEYS),
-        // $FlowFixMe
         _.pick(this.props.settings, ...SETTINGS_KEYS),
       )
     ) {
diff --git a/frontend/src/metabase/visualizations/components/Table.css b/frontend/src/metabase/visualizations/components/Table.css
index 715c772707bd66f24c1e18aebf3f1ff90f20a05c..e65d8cf74154a808551c6e241e3cdb09afa3813e 100644
--- a/frontend/src/metabase/visualizations/components/Table.css
+++ b/frontend/src/metabase/visualizations/components/Table.css
@@ -12,13 +12,13 @@
 }
 
 :local(.Table) tr {
-  border-bottom: 1px solid var(--table-border-color);
+  border-bottom: 1px solid color(var(--color-border) alpha(-70%));
 }
 
 :local(.Table) th,
 :local(.Table) td {
   padding: 1em;
-  border-bottom: 1px solid var(--table-border-color);
+  border-bottom: 1px solid color(var(--color-border) alpha(-70%));
 }
 
 :local(.TableSimple) th:first-child,
diff --git a/frontend/src/metabase/visualizations/components/TableInteractive.css b/frontend/src/metabase/visualizations/components/TableInteractive.css
index 07511fb6817797f28eeeb7ccea47038418618877..029ead80e5c34b83d5a2110b0a6580da524ee6a7 100644
--- a/frontend/src/metabase/visualizations/components/TableInteractive.css
+++ b/frontend/src/metabase/visualizations/components/TableInteractive.css
@@ -30,12 +30,12 @@
 
 /* if the column is the one that is being sorted*/
 .TableInteractive-headerCellData--sorted {
-  color: var(--brand-color);
+  color: var(--color-brand);
 }
 
 .TableInteractive-header {
   box-sizing: border-box;
-  border-bottom: 1px solid #e0e0e0;
+  border-bottom: 1px solid var(--color-border);
 }
 
 .TableInteractive .TableInteractive-cellWrapper {
@@ -47,14 +47,14 @@
   border-top: 1px solid transparent;
   border-left: 1px solid transparent;
   border-right: 1px solid transparent;
-  border-bottom: 1px solid var(--table-border-color);
+  border-bottom: 1px solid color(var(--color-border) alpha(-70%));
 }
 
 .TableInteractive .TableInteractive-cellWrapper--active,
 .TableInteractive:not(.TableInteractive--noHover)
   .TableInteractive-cellWrapper:hover {
-  border-color: var(--brand-color);
-  color: var(--brand-color);
+  border-color: var(--color-brand);
+  color: var(--color-brand);
 }
 
 .TableInteractive .TableInteractive-cellWrapper--active {
@@ -63,13 +63,13 @@
 
 .TableInteractive .TableInteractive-header,
 .TableInteractive .TableInteractive-header .TableInteractive-cellWrapper {
-  background-color: #fff;
+  background-color: var(--color-bg-white);
   background-image: none;
 }
 
 .TableInteractive .TableInteractive-header,
 .TableInteractive .TableInteractive-header .TableInteractive-cellWrapper {
-  background-color: #fff;
+  background-color: var(--color-bg-white);
 }
 
 /* cell overflow ellipsis */
@@ -83,14 +83,14 @@
 /* pivot */
 .TableInteractive.TableInteractive--pivot
   .TableInteractive-cellWrapper--firstColumn {
-  border-right: 1px solid rgb(205, 205, 205);
+  border-right: 1px solid var(--color-border);
 }
 
 .PagingButtons {
-  border: 1px solid #ddd;
+  border: 1px solid var(--color-border);
 }
 
 .TableInteractive .TableInteractive-cellWrapper.tether-enabled {
-  background-color: var(--brand-color);
+  background-color: var(--color-brand);
   color: white !important;
 }
diff --git a/frontend/src/metabase/visualizations/components/TableInteractive.jsx b/frontend/src/metabase/visualizations/components/TableInteractive.jsx
index 1e99af19e35f3d1be648336c400ea63eb78c2241..b4aafe6270a01942cca226b853bbb508e8cde41e 100644
--- a/frontend/src/metabase/visualizations/components/TableInteractive.jsx
+++ b/frontend/src/metabase/visualizations/components/TableInteractive.jsx
@@ -88,7 +88,7 @@ type GridComponent = Component<void, void, void> & {
   recomputeGridSize: () => void,
 };
 
-@ExplicitSize
+@ExplicitSize()
 export default class TableInteractive extends Component {
   state: State;
   props: Props;
@@ -393,7 +393,6 @@ export default class TableInteractive extends Component {
     const { dragColIndex, columnPositions } = this.state;
     const { cols } = this.props.data;
     const indexes = cols.map((col, index) => index);
-    // $FlowFixMe: inner indexes.splice should always return an index
     indexes.splice(dragColNewIndex, 0, indexes.splice(dragColIndex, 1)[0]);
     let left = 0;
     const lefts = indexes.map(index => {
@@ -416,7 +415,6 @@ export default class TableInteractive extends Component {
 
   tableHeaderRenderer = ({ key, style, columnIndex }: CellRendererProps) => {
     const { sort, isPivoted } = this.props;
-    // $FlowFixMe: not sure why flow has a problem with this
     const { cols } = this.props.data;
     const column = cols[columnIndex];
 
@@ -444,7 +442,7 @@ export default class TableInteractive extends Component {
     // the column id is in `["field-id", fieldId]` format
     const isSorted =
       sort && sort[0] && sort[0][0] && sort[0][0][1] === column.id;
-    const isAscending = sort && sort[0] && sort[0][1] === "ascending";
+    const isAscending = sort && sort[0] && sort[0][0] === "asc";
     return (
       <Draggable
         /* needs to be index+name+counter so Draggable resets after each drag */
diff --git a/frontend/src/metabase/visualizations/components/TableSimple.jsx b/frontend/src/metabase/visualizations/components/TableSimple.jsx
index c36f0fec2d348b0ae6585d5d49e66856378edadb..1863401520363104c2a5c7fd6c70ea794f3ec9b4 100644
--- a/frontend/src/metabase/visualizations/components/TableSimple.jsx
+++ b/frontend/src/metabase/visualizations/components/TableSimple.jsx
@@ -33,7 +33,7 @@ type State = {
   sortDescending: boolean,
 };
 
-@ExplicitSize
+@ExplicitSize()
 export default class TableSimple extends Component {
   props: Props;
   state: State;
diff --git a/frontend/src/metabase/visualizations/components/Visualization.jsx b/frontend/src/metabase/visualizations/components/Visualization.jsx
index 863c27d0a3fce3e3d717986df072dc50d884469a..f603cdcd19b8236e24b4a445464dbefbb464649f 100644
--- a/frontend/src/metabase/visualizations/components/Visualization.jsx
+++ b/frontend/src/metabase/visualizations/components/Visualization.jsx
@@ -32,10 +32,8 @@ import { assoc, setIn } from "icepick";
 import _ from "underscore";
 import cx from "classnames";
 
-export const ERROR_MESSAGE_GENERIC =
-  "There was a problem displaying this chart.";
-export const ERROR_MESSAGE_PERMISSION =
-  "Sorry, you don't have permission to see this card.";
+export const ERROR_MESSAGE_GENERIC = t`There was a problem displaying this chart.`;
+export const ERROR_MESSAGE_PERMISSION = t`Sorry, you don't have permission to see this card.`;
 
 import Question from "metabase-lib/lib/Question";
 import type {
@@ -59,6 +57,7 @@ type Props = {
   showTitle: boolean,
   isDashboard: boolean,
   isEditing: boolean,
+  isSettings: boolean,
 
   actionButtons: Element<any>,
 
@@ -92,6 +91,8 @@ type Props = {
   gridSize?: { width: number, height: number },
   // if gridSize isn't specified, compute using this gridSize (4x width, 3x height)
   gridUnit?: number,
+
+  classNameWidgets?: string,
 };
 
 type State = {
@@ -109,7 +110,8 @@ type State = {
   yAxisSplit: ?(number[][]),
 };
 
-@ExplicitSize
+// NOTE: pass `CardVisualization` so that we don't include header when providing size to child element
+@ExplicitSize("CardVisualization")
 export default class Visualization extends Component {
   state: State;
   props: Props;
@@ -135,6 +137,7 @@ export default class Visualization extends Component {
     showTitle: false,
     isDashboard: false,
     isEditing: false,
+    isSettings: false,
     onUpdateVisualizationSettings: (...args) =>
       console.warn("onUpdateVisualizationSettings", args),
   };
@@ -402,7 +405,7 @@ export default class Visualization extends Component {
       </span>
     );
 
-    let { gridSize, gridUnit } = this.props;
+    let { gridSize, gridUnit, classNameWidgets } = this.props;
     if (!gridSize && gridUnit) {
       gridSize = {
         width: Math.round(width / (gridUnit * 4)),
@@ -421,6 +424,7 @@ export default class Visualization extends Component {
         replacementContent ? (
           <div className="p1 flex-no-shrink">
             <LegendHeader
+              classNameWidgets={classNameWidgets}
               series={
                 settings["card.title"]
                   ? // if we have a card title set, use it
@@ -496,7 +500,8 @@ export default class Visualization extends Component {
           // $FlowFixMe
           <CardVisualization
             {...this.props}
-            className="flex-full"
+            // NOTE: CardVisualization class used to target ExplicitSize HOC
+            className="CardVisualization flex-full"
             series={series}
             settings={settings}
             // $FlowFixMe
diff --git a/frontend/src/metabase/visualizations/components/settings/ChartSettingFieldPicker.jsx b/frontend/src/metabase/visualizations/components/settings/ChartSettingFieldPicker.jsx
index f503df8cd92fe450f2854d85c5c55ffe7cc4aabe..d1601c329e584dc882f1212b0db72bff1672f922 100644
--- a/frontend/src/metabase/visualizations/components/settings/ChartSettingFieldPicker.jsx
+++ b/frontend/src/metabase/visualizations/components/settings/ChartSettingFieldPicker.jsx
@@ -17,7 +17,7 @@ const ChartSettingFieldPicker = ({ value, options, onChange, onRemove }) => (
     />
     <Icon
       name="close"
-      className={cx("ml1 text-grey-4 text-brand-hover cursor-pointer", {
+      className={cx("ml1 text-medium text-brand-hover cursor-pointer", {
         "disabled hidden": !onRemove,
       })}
       width={12}
diff --git a/frontend/src/metabase/visualizations/components/settings/ChartSettingGaugeSegments.jsx b/frontend/src/metabase/visualizations/components/settings/ChartSettingGaugeSegments.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..3def4c35297297897731bb83ba38587e20bb6494
--- /dev/null
+++ b/frontend/src/metabase/visualizations/components/settings/ChartSettingGaugeSegments.jsx
@@ -0,0 +1,128 @@
+import React from "react";
+
+import { t } from "c-3po";
+import _ from "underscore";
+
+import colors, { normal } from "metabase/lib/colors";
+
+import ColorPicker from "metabase/components/ColorPicker";
+import Button from "metabase/components/Button";
+import Icon from "metabase/components/Icon";
+import NumericInput from "metabase/components/NumericInput";
+
+const ChartSettingGaugeSegments = ({ value: segments, onChange }) => {
+  const onChangeProperty = (index, property, value) =>
+    onChange([
+      ...segments.slice(0, index),
+      { ...segments[index], [property]: value },
+      ...segments.slice(index + 1),
+    ]);
+  return (
+    <div>
+      <table>
+        <thead>
+          <tr>
+            <th>Color</th>
+            <th>Min</th>
+            <th>Max</th>
+          </tr>
+        </thead>
+        <tbody>
+          {segments.map((segment, index) => [
+            <tr>
+              <td>
+                <ColorPicker
+                  value={segment.color}
+                  onChange={color => onChangeProperty(index, "color", color)}
+                  triggerSize={28}
+                  padding={2}
+                  colors={getColorPalette()}
+                />
+              </td>
+              <td>
+                <NumericInput
+                  type="number"
+                  className="input full"
+                  value={segment.min}
+                  onChange={value => onChangeProperty(index, "min", value)}
+                  placeholder={t`Min`}
+                />
+              </td>
+              <td>
+                <NumericInput
+                  type="number"
+                  className="input full"
+                  value={segment.max}
+                  onChange={value => onChangeProperty(index, "max", value)}
+                  placeholder={t`Max`}
+                />
+              </td>
+              <td>
+                {segments.length > 1 && (
+                  <Icon
+                    name="close"
+                    className="cursor-pointer text-grey-2 text-grey-4-hover ml2"
+                    onClick={() =>
+                      onChange(segments.filter((v, i) => i !== index))
+                    }
+                  />
+                )}
+              </td>
+            </tr>,
+            <tr>
+              <td colSpan={3} className="pb2">
+                <input
+                  type="text"
+                  className="input full"
+                  value={segment.label}
+                  onChange={e =>
+                    onChangeProperty(index, "label", e.target.value)
+                  }
+                  placeholder={t`Label for this range (optional)`}
+                />
+              </td>
+            </tr>,
+          ])}
+        </tbody>
+      </table>
+      <Button
+        borderless
+        icon="add"
+        onClick={() => onChange(segments.concat(newSegment(segments)))}
+      >
+        {t`Add a range`}
+      </Button>
+    </div>
+  );
+};
+
+function getColorPalette() {
+  return [
+    colors["error"],
+    colors["warning"],
+    colors["success"],
+    ...Object.values(normal).slice(0, 9),
+    colors["bg-medium"],
+  ];
+}
+
+function newSegment(segments) {
+  const palette = getColorPalette();
+  const lastSegment = segments[segments.length - 1];
+  const lastColorIndex = lastSegment
+    ? _.findIndex(palette, color => color === lastSegment.color)
+    : -1;
+  const nextColor =
+    lastColorIndex >= 0
+      ? palette[lastColorIndex + 1 % palette.length]
+      : palette[0];
+
+  return {
+    min: lastSegment ? lastSegment.max : 0,
+    max: lastSegment ? lastSegment.max * 2 : 1,
+    color: nextColor,
+    label: "",
+  };
+}
+
+export default ChartSettingGaugeSegments;
diff --git a/frontend/src/metabase/visualizations/components/settings/ChartSettingOrderedColumns.jsx b/frontend/src/metabase/visualizations/components/settings/ChartSettingOrderedColumns.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..014cbb6c6281d76e0d4280a1104e40a3d20bb080
--- /dev/null
+++ b/frontend/src/metabase/visualizations/components/settings/ChartSettingOrderedColumns.jsx
@@ -0,0 +1,193 @@
+import React, { Component } from "react";
+import { t } from "c-3po";
+
+import Icon from "metabase/components/Icon.jsx";
+
+import { SortableContainer, SortableElement } from "react-sortable-hoc";
+
+import StructuredQuery from "metabase-lib/lib/queries/StructuredQuery";
+import {
+  fieldRefForColumn,
+  findColumnForColumnSetting,
+} from "metabase/lib/dataset";
+import { getFriendlyName } from "metabase/visualizations/lib/utils";
+
+import _ from "underscore";
+
+const SortableColumn = SortableElement(
+  ({ columnSetting, getColumnName, onRemove }) => (
+    <ColumnItem
+      title={getColumnName(columnSetting)}
+      onRemove={() => onRemove(columnSetting)}
+    />
+  ),
+);
+
+const SortableColumnList = SortableContainer(
+  ({ columnSettings, getColumnName, onRemove }) => {
+    return (
+      <div>
+        {columnSettings.map((columnSetting, index) => (
+          <SortableColumn
+            key={`item-${index}`}
+            index={columnSetting.index}
+            columnSetting={columnSetting}
+            getColumnName={getColumnName}
+            onRemove={onRemove}
+          />
+        ))}
+      </div>
+    );
+  },
+);
+
+export default class ChartSettingOrderedColumns extends Component {
+  handleEnable = columnSetting => {
+    const columnSettings = [...this.props.value];
+    const index = columnSetting.index;
+    columnSettings[index] = { ...columnSettings[index], enabled: true };
+    this.props.onChange(columnSettings);
+  };
+
+  handleDisable = columnSetting => {
+    const columnSettings = [...this.props.value];
+    const index = columnSetting.index;
+    columnSettings[index] = { ...columnSettings[index], enabled: false };
+    this.props.onChange(columnSettings);
+  };
+
+  handleSortEnd = ({ oldIndex, newIndex }) => {
+    const fields = [...this.props.value];
+    fields.splice(newIndex, 0, fields.splice(oldIndex, 1)[0]);
+    this.props.onChange(fields);
+  };
+
+  handleAddNewField = fieldRef => {
+    const { value, onChange, addField } = this.props;
+    onChange([
+      // remove duplicates
+      ...value.filter(
+        columnSetting => !_.isEqual(columnSetting.fieldRef, fieldRef),
+      ),
+      { fieldRef, enabled: true },
+    ]);
+    addField(fieldRef);
+  };
+
+  getColumnName = columnSetting =>
+    getFriendlyName(
+      findColumnForColumnSetting(this.props.columns, columnSetting) || {
+        display_name: "[Unknown]",
+      },
+    );
+
+  render() {
+    const { value, question, columns } = this.props;
+
+    let additionalFieldOptions = { count: 0 };
+    if (columns && question && question.query() instanceof StructuredQuery) {
+      const fieldRefs = columns.map(column => fieldRefForColumn(column));
+      additionalFieldOptions = question.query().fieldsOptions(dimension => {
+        const mbql = dimension.mbql();
+        return !_.find(fieldRefs, fieldRef => _.isEqual(fieldRef, mbql));
+      });
+    }
+
+    const [enabledColumns, disabledColumns] = _.partition(
+      value
+        .filter(columnSetting =>
+          findColumnForColumnSetting(columns, columnSetting),
+        )
+        .map((columnSetting, index) => ({ ...columnSetting, index })),
+      columnSetting => columnSetting.enabled,
+    );
+
+    return (
+      <div className="list">
+        <div>{t`Click and drag to change their order`}</div>
+        {enabledColumns.length > 0 ? (
+          <SortableColumnList
+            columnSettings={enabledColumns}
+            getColumnName={this.getColumnName}
+            onRemove={this.handleDisable}
+            onSortEnd={this.handleSortEnd}
+            distance={5}
+            helperClass="z5"
+          />
+        ) : (
+          <div className="my2 p2 flex layout-centered bg-grey-0 text-light text-bold rounded">
+            {t`Add fields from the list below`}
+          </div>
+        )}
+        {disabledColumns.length > 0 || additionalFieldOptions.count > 0 ? (
+          <h4 className="mb2 mt4 pt4 border-top">{`More fields`}</h4>
+        ) : null}
+        {disabledColumns.map((columnSetting, index) => (
+          <ColumnItem
+            key={index}
+            title={this.getColumnName(columnSetting)}
+            onAdd={() => this.handleEnable(columnSetting)}
+          />
+        ))}
+        {additionalFieldOptions.count > 0 && (
+          <div>
+            {additionalFieldOptions.dimensions.map((dimension, index) => (
+              <ColumnItem
+                key={index}
+                title={dimension.displayName()}
+                onAdd={() => this.handleAddNewField(dimension.mbql())}
+              />
+            ))}
+            {additionalFieldOptions.fks.map(fk => (
+              <div>
+                <div className="my2 text-medium text-bold text-uppercase text-small">
+                  {fk.field.target.table.display_name}
+                </div>
+                {fk.dimensions.map((dimension, index) => (
+                  <ColumnItem
+                    key={index}
+                    title={dimension.displayName()}
+                    onAdd={() => this.handleAddNewField(dimension.mbql())}
+                  />
+                ))}
+              </div>
+            ))}
+          </div>
+        )}
+      </div>
+    );
+  }
+}
+
+const ColumnItem = ({ title, onAdd, onRemove }) => (
+  <div
+    className="my1 bordered rounded shadowed cursor-pointer overflow-hidden bg-white"
+    onClick={onAdd}
+  >
+    <div className="p1 border-bottom relative">
+      <div className="px1 flex align-center relative">
+        <span className="h4 flex-full text-dark">{title}</span>
+        {onAdd && (
+          <Icon
+            name="add"
+            className="cursor-pointer text-light text-medium-hover"
+            onClick={e => {
+              e.stopPropagation();
+              onAdd();
+            }}
+          />
+        )}
+        {onRemove && (
+          <Icon
+            name="close"
+            className="cursor-pointer text-light text-medium-hover"
+            onClick={e => {
+              e.stopPropagation();
+              onRemove();
+            }}
+          />
+        )}
+      </div>
+    </div>
+  </div>
+);
diff --git a/frontend/src/metabase/visualizations/components/settings/ChartSettingOrderedFields.jsx b/frontend/src/metabase/visualizations/components/settings/ChartSettingOrderedFields.jsx
deleted file mode 100644
index 94e9290306fbaa1c2e2337eecc55f2aa66c86a46..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/visualizations/components/settings/ChartSettingOrderedFields.jsx
+++ /dev/null
@@ -1,110 +0,0 @@
-import React, { Component } from "react";
-
-import CheckBox from "metabase/components/CheckBox.jsx";
-import Icon from "metabase/components/Icon.jsx";
-
-import { SortableContainer, SortableElement } from "react-sortable-hoc";
-
-import cx from "classnames";
-import _ from "underscore";
-
-const SortableField = SortableElement(
-  ({ field, columnNames, onSetEnabled }) => (
-    <div
-      className={cx("flex align-center p1", {
-        "text-grey-2": !field.enabled,
-      })}
-    >
-      <CheckBox
-        checked={field.enabled}
-        onChange={e => onSetEnabled(e.target.checked)}
-      />
-      <span className="ml1 h4">{columnNames[field.name]}</span>
-      <Icon
-        className="flex-align-right text-grey-2 mr1 cursor-pointer"
-        name="grabber"
-        width={14}
-        height={14}
-      />
-    </div>
-  ),
-);
-
-const SortableFieldList = SortableContainer(
-  ({ fields, columnNames, onSetEnabled }) => {
-    return (
-      <div>
-        {fields.map((field, index) => (
-          <SortableField
-            key={`item-${index}`}
-            index={index}
-            field={field}
-            columnNames={columnNames}
-            onSetEnabled={enabled => onSetEnabled(index, enabled)}
-          />
-        ))}
-      </div>
-    );
-  },
-);
-
-export default class ChartSettingOrderedFields extends Component {
-  handleSetEnabled = (index, checked) => {
-    const fields = [...this.props.value];
-    fields[index] = { ...fields[index], enabled: checked };
-    this.props.onChange(fields);
-  };
-
-  handleToggleAll = anyEnabled => {
-    const fields = this.props.value.map(field => ({
-      ...field,
-      enabled: !anyEnabled,
-    }));
-    this.props.onChange([...fields]);
-  };
-
-  handleSortEnd = ({ oldIndex, newIndex }) => {
-    const fields = [...this.props.value];
-    fields.splice(newIndex, 0, fields.splice(oldIndex, 1)[0]);
-    this.props.onChange(fields);
-  };
-
-  isAnySelected = () => {
-    const { value } = this.props;
-    return _.any(value, field => field.enabled);
-  };
-
-  render() {
-    const { value, columnNames } = this.props;
-    const anyEnabled = this.isAnySelected();
-    return (
-      <div className="list">
-        <div className="toggle-all">
-          <div
-            className={cx("flex align-center p1", {
-              "text-grey-2": !anyEnabled,
-            })}
-          >
-            <CheckBox
-              checked={anyEnabled}
-              className={cx("text-brand", { "text-grey-2": !anyEnabled })}
-              onChange={e => this.handleToggleAll(anyEnabled)}
-              invertChecked
-            />
-            <span className="ml1 h4">
-              {anyEnabled ? "Unselect all" : "Select all"}
-            </span>
-          </div>
-        </div>
-        <SortableFieldList
-          fields={value}
-          columnNames={columnNames}
-          onSetEnabled={this.handleSetEnabled}
-          onSortEnd={this.handleSortEnd}
-          distance={5}
-          helperClass="z5"
-        />
-      </div>
-    );
-  }
-}
diff --git a/frontend/src/metabase/visualizations/components/settings/ChartSettingRadio.jsx b/frontend/src/metabase/visualizations/components/settings/ChartSettingRadio.jsx
index 57e6a351449705bf5953d354985abaede2b3dfd1..4b0d8c79cb44c5ae649e2ec64dde278ef5326b97 100644
--- a/frontend/src/metabase/visualizations/components/settings/ChartSettingRadio.jsx
+++ b/frontend/src/metabase/visualizations/components/settings/ChartSettingRadio.jsx
@@ -8,7 +8,7 @@ const ChartSettingRadio = ({ value, onChange, options = [], className }) => (
     value={value}
     onChange={onChange}
     options={options}
-    isVertical
+    vertical
   />
 );
 
diff --git a/frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx b/frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx
index 0d669b714558247e613ce6e7574d7419f4d2fbbc..b4107be8bbd15ea23d3bbe16ad1c7e0598af6416 100644
--- a/frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx
+++ b/frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx
@@ -12,6 +12,7 @@ import PopoverWithTrigger from "metabase/components/PopoverWithTrigger";
 
 import { SortableContainer, SortableElement } from "react-sortable-hoc";
 
+import MetabaseAnalytics from "metabase/lib/analytics";
 import { formatNumber, capitalize } from "metabase/lib/formatting";
 import { isNumeric } from "metabase/lib/schema_metadata";
 
@@ -28,16 +29,16 @@ const OPERATOR_NAMES = {
   "!=": t`not equal to`,
 };
 
-import { desaturated as colors, getColorScale } from "metabase/lib/colors";
+import colors, { desaturated, getColorScale } from "metabase/lib/colors";
 
-const COLORS = Object.values(colors);
+const COLORS = Object.values(desaturated);
 const COLOR_RANGES = [].concat(
   ...COLORS.map(color => [["white", color], [color, "white"]]),
   [
-    [colors.red, "white", colors.green],
-    [colors.green, "white", colors.red],
-    [colors.red, colors.yellow, colors.green],
-    [colors.green, colors.yellow, colors.red],
+    [colors.error, "white", colors.success],
+    [colors.success, "white", colors.error],
+    [colors.error, colors.warning, colors.success],
+    [colors.success, colors.warning, colors.error],
   ],
 );
 
@@ -116,13 +117,23 @@ export default class ChartSettingsTableFormatting extends React.Component {
             ]);
             this.setState({ editingRule: 0, editingRuleIsNew: true });
           }}
-          onRemove={index =>
-            onChange([...value.slice(0, index), ...value.slice(index + 1)])
-          }
+          onRemove={index => {
+            onChange([...value.slice(0, index), ...value.slice(index + 1)]);
+            MetabaseAnalytics.trackEvent(
+              "Chart Settings",
+              "Table Formatting",
+              "Remove Rule",
+            );
+          }}
           onMove={(from, to) => {
             const newValue = [...value];
             newValue.splice(to, 0, newValue.splice(from, 1)[0]);
             onChange(newValue);
+            MetabaseAnalytics.trackEvent(
+              "Chart Settings",
+              "Table Formatting",
+              "Move Rule",
+            );
           }}
         />
       );
@@ -161,7 +172,12 @@ const RuleListing = ({ rules, cols, onEdit, onAdd, onRemove, onMove }) => (
     they meet certain conditions.`}
     </div>
     <div className="mt2">
-      <Button borderless icon="add" onClick={onAdd}>
+      <Button
+        borderless
+        icon="add"
+        onClick={onAdd}
+        data-metabase-event={`Chart Settings;Table Formatting;Add Rule`}
+      >
         {t`Add a rule`}
       </Button>
     </div>
@@ -188,7 +204,7 @@ const RulePreview = ({ rule, cols, onClick, onRemove }) => (
     className="my2 bordered rounded shadowed cursor-pointer overflow-hidden bg-white"
     onClick={onClick}
   >
-    <div className="p1 border-bottom relative bg-grey-0">
+    <div className="p1 border-bottom relative bg-light">
       <div className="px1 flex align-center relative">
         <span className="h4 flex-full text-dark">
           {rule.columns.length > 0 ? (
@@ -206,7 +222,7 @@ const RulePreview = ({ rule, cols, onClick, onRemove }) => (
         </span>
         <Icon
           name="close"
-          className="cursor-pointer text-grey-2 text-grey-4-hover"
+          className="cursor-pointer text-light text-medium-hover"
           onClick={e => {
             e.stopPropagation();
             onRemove();
@@ -217,10 +233,9 @@ const RulePreview = ({ rule, cols, onClick, onRemove }) => (
     <div className="p2 flex align-center">
       <RuleBackground
         rule={rule}
-        className={cx(
-          "mr2 flex-no-shrink rounded overflow-hidden border-grey-1",
-          { bordered: rule.type === "range" },
-        )}
+        className={cx("mr2 flex-no-shrink rounded overflow-hidden", {
+          bordered: rule.type === "range",
+        })}
         style={{ width: 40, height: 40 }}
       />
       <RuleDescription rule={rule} />
@@ -290,7 +305,7 @@ const RuleEditor = ({ rule, cols, isNew, onChange, onDone, onRemove }) => (
         { name: t`Color range`, value: "range" },
       ]}
       onChange={type => onChange({ ...DEFAULTS_BY_TYPE[type], ...rule, type })}
-      isVertical
+      vertical
     />
     {rule.type === "single" ? (
       <div>
@@ -340,7 +355,7 @@ const RuleEditor = ({ rule, cols, isNew, onChange, onDone, onRemove }) => (
                 },
               ]
           ).concat([{ name: t`Custom value`, value: "custom" }])}
-          isVertical
+          vertical
         />
         {rule.min_type === "custom" && (
           <NumericInput
@@ -362,7 +377,7 @@ const RuleEditor = ({ rule, cols, isNew, onChange, onDone, onRemove }) => (
                 },
               ]
           ).concat([{ name: t`Custom value`, value: "custom" }])}
-          isVertical
+          vertical
         />
         {rule.max_type === "custom" && (
           <NumericInput
@@ -374,11 +389,21 @@ const RuleEditor = ({ rule, cols, isNew, onChange, onDone, onRemove }) => (
     ) : null}
     <div className="mt4">
       {rule.columns.length === 0 ? (
-        <Button primary onClick={onRemove}>
+        <Button
+          primary
+          onClick={onRemove}
+          data-metabase-event={`Chart Settings;Table Formatting;`}
+        >
           {isNew ? t`Cancel` : t`Delete`}
         </Button>
       ) : (
-        <Button primary onClick={onDone}>
+        <Button
+          primary
+          onClick={onDone}
+          data-metabase-event={`Chart Setttings;Table Formatting;${
+            isNew ? "Add Rule" : "Update Rule"
+          };Rule Type ${rule.type} Color`}
+        >
           {isNew ? t`Add rule` : t`Update rule`}
         </Button>
       )}
@@ -404,6 +429,12 @@ const ColorRangePicker = ({ colors, onChange, className, style }) => (
               colors={range}
               onClick={() => {
                 onChange(range);
+                MetabaseAnalytics.trackEvent(
+                  "Chart Settings",
+                  "Table Formatting",
+                  "Select Range  Colors",
+                  range,
+                );
                 onClose();
               }}
               className={cx("bordered rounded overflow-hidden cursor-pointer")}
@@ -421,6 +452,6 @@ const NumericInput = ({ value, onChange }) => (
     className="AdminSelect input mt1 full"
     type="number"
     value={value}
-    onChange={e => onChange(parseFloat(e.target.value))}
+    onChange={e => onChange(e.target.value)}
   />
 );
diff --git a/frontend/src/metabase/visualizations/index.js b/frontend/src/metabase/visualizations/index.js
index ca6701f4d368db4e88c924602184cedaa69bdf24..cac43f18fc7ce40ebd0ffeca8b9c533a5b196e5b 100644
--- a/frontend/src/metabase/visualizations/index.js
+++ b/frontend/src/metabase/visualizations/index.js
@@ -12,6 +12,7 @@ import AreaChart from "./visualizations/AreaChart.jsx";
 import MapViz from "./visualizations/Map.jsx";
 import ScatterPlot from "./visualizations/ScatterPlot.jsx";
 import Funnel from "./visualizations/Funnel.jsx";
+import Gauge from "./visualizations/Gauge.jsx";
 import ObjectDetail from "./visualizations/ObjectDetail.jsx";
 import { t } from "c-3po";
 import _ from "underscore";
@@ -124,6 +125,7 @@ const extractRemappedColumns = data => {
 
 registerVisualization(Scalar);
 registerVisualization(Progress);
+registerVisualization(Gauge);
 registerVisualization(Table);
 registerVisualization(Text);
 registerVisualization(LineChart);
diff --git a/frontend/src/metabase/visualizations/lib/LineAreaBarPostRender.js b/frontend/src/metabase/visualizations/lib/LineAreaBarPostRender.js
index 73a1a1d0c908d4e54a8a23be4c5c5baefdfef201..d8045dea56119cde75e02309443984b4391e2060 100644
--- a/frontend/src/metabase/visualizations/lib/LineAreaBarPostRender.js
+++ b/frontend/src/metabase/visualizations/lib/LineAreaBarPostRender.js
@@ -2,6 +2,7 @@
 
 import d3 from "d3";
 
+import colors from "metabase/lib/colors";
 import { clipPathReference } from "metabase/lib/dom";
 import { adjustYAxisTicksIfNeeded } from "./apply_axis";
 
@@ -9,6 +10,7 @@ const X_LABEL_MIN_SPACING = 2; // minimum space we want to leave between labels
 const X_LABEL_ROTATE_90_THRESHOLD = 24; // tick width breakpoint for switching from 45° to 90°
 const X_LABEL_HIDE_THRESHOLD = 12; // tick width breakpoint for hiding labels entirely
 const X_LABEL_MAX_LABEL_HEIGHT_RATIO = 0.7; // percent rotated labels are allowed to take
+const X_LABEL_DISABLED_SPACING = 6; // spacing to use if the x-axis is disabled completely
 
 // +-------------------------------------------------------------------------------------------------------------------+
 // |                                                ON RENDER FUNCTIONS                                                |
@@ -170,7 +172,7 @@ function onRenderCleanupGoal(chart, onGoalHover, isSplitAxis) {
     this.parentNode.appendChild(this);
   });
   chart.selectAll(".goal .line").attr({
-    stroke: "rgba(157,160,164, 0.7)",
+    stroke: colors["text-medium"],
     "stroke-dasharray": "5,5",
   });
 
@@ -204,7 +206,7 @@ function onRenderCleanupGoal(chart, onGoalHover, isSplitAxis) {
         y: y - 5,
         "text-anchor": labelOnRight ? "end" : "start",
         "font-weight": "bold",
-        fill: "rgb(157,160,164)",
+        fill: colors["text-medium"],
       })
       .on("mouseenter", function() {
         onGoalHover(this);
@@ -373,6 +375,9 @@ function rotateSize(size, rotation) {
 }
 
 function computeXAxisMargin(chart) {
+  if (chart.settings["graph.x_axis.axis_enabled"] === false) {
+    return X_LABEL_DISABLED_SPACING;
+  }
   const rotation = getXAxisRotation(chart);
   const maxSize = computeXAxisLabelMaxSize(chart);
   const rotatedMaxSize = rotateSize(maxSize, rotation);
diff --git a/frontend/src/metabase/visualizations/lib/LineAreaBarRenderer.js b/frontend/src/metabase/visualizations/lib/LineAreaBarRenderer.js
index 24f5f2aa1d908ba69c6f6741f616daffe0798b5b..d88eb025aeabdc11c9327c7ea8ca795b1d6e9cb2 100644
--- a/frontend/src/metabase/visualizations/lib/LineAreaBarRenderer.js
+++ b/frontend/src/metabase/visualizations/lib/LineAreaBarRenderer.js
@@ -52,7 +52,6 @@ import {
 
 import lineAndBarOnRender from "./LineAreaBarPostRender";
 
-import { formatNumber } from "metabase/lib/formatting";
 import { isStructured } from "metabase/meta/Card";
 
 import {
@@ -138,6 +137,7 @@ function getXAxisProps(props, datas) {
     xValues,
     xDomain: d3.extent(xValues),
     xInterval: getXInterval(props, xValues),
+    isHistogramBar: isHistogramBar(props),
   };
 }
 
@@ -675,11 +675,6 @@ export default function lineAreaBar(
 
   applyXAxisSettings(parent, props.series, xAxisProps);
 
-  // override tick format for bars. ticks are aligned with beginning of bar, so just show the start value
-  if (isHistogramBar(props)) {
-    parent.xAxis().tickFormat(d => formatNumber(d));
-  }
-
   applyYAxisSettings(parent, yAxisProps);
 
   setupTooltips(props, datas, parent, brushChangeFunctions);
diff --git a/frontend/src/metabase/visualizations/lib/apply_axis.js b/frontend/src/metabase/visualizations/lib/apply_axis.js
index 5129e359249b34f0b8d8b3cfe871c52a6380a144..977daea065a425a91355cd12b5cc3fc350936c27 100644
--- a/frontend/src/metabase/visualizations/lib/apply_axis.js
+++ b/frontend/src/metabase/visualizations/lib/apply_axis.js
@@ -10,6 +10,7 @@ import { formatValue } from "metabase/lib/formatting";
 import { parseTimestamp } from "metabase/lib/time";
 
 import { computeTimeseriesTicksInterval } from "./timeseries";
+import { isMultipleOf, getModuloScaleFactor } from "./numeric";
 import { getFriendlyName } from "./utils";
 import { isHistogram } from "./renderer_utils";
 
@@ -207,13 +208,21 @@ export function applyChartQuantitativeXAxis(
     );
     adjustXAxisTicksIfNeeded(chart.xAxis(), chart.width(), xValues);
 
-    chart.xAxis().tickFormat(d =>
-      formatValue(d, {
-        column: dimensionColumn,
-        type: "axis",
-        compact: chart.settings["graph.x_axis.axis_enabled"] === "compact",
-      }),
-    );
+    // if xInterval is less than 1 we need to scale the values before doing
+    // modulo comparison. isMultipleOf will compute it for us but we can do it
+    // once here as an optimization
+    const modulorScale = getModuloScaleFactor(xInterval);
+
+    chart.xAxis().tickFormat(d => {
+      // don't show ticks that aren't multiples of xInterval
+      if (isMultipleOf(d, xInterval, modulorScale)) {
+        return formatValue(d, {
+          column: dimensionColumn,
+          type: "axis",
+          compact: chart.settings["graph.x_axis.axis_enabled"] === "compact",
+        });
+      }
+    });
   } else {
     chart.xAxis().ticks(0);
     chart.xAxis().tickFormat("");
@@ -242,7 +251,11 @@ export function applyChartQuantitativeXAxis(
   chart.x(scale.domain(xDomain)).xUnits(dc.units.fp.precision(xInterval));
 }
 
-export function applyChartOrdinalXAxis(chart, series, { xValues }) {
+export function applyChartOrdinalXAxis(
+  chart,
+  series,
+  { xValues, isHistogramBar },
+) {
   // find the first nonempty single series
   // $FlowFixMe
   const firstSeries: SingleSeries = _.find(
@@ -271,6 +284,7 @@ export function applyChartOrdinalXAxis(chart, series, { xValues }) {
         column: dimensionColumn,
         type: "axis",
         compact: chart.settings["graph.x_axis.labels_enabled"] === "compact",
+        noRange: isHistogramBar,
       }),
     );
   } else {
diff --git a/frontend/src/metabase/visualizations/lib/numeric.js b/frontend/src/metabase/visualizations/lib/numeric.js
index 733629806bc588158f39e14718ce0df2001068e1..7b415bb4c0ff3d7a94f4535e58bd6bdd679f2b8c 100644
--- a/frontend/src/metabase/visualizations/lib/numeric.js
+++ b/frontend/src/metabase/visualizations/lib/numeric.js
@@ -58,3 +58,9 @@ export function logTickFormat(axis) {
   let formatTick = d => 10 + formatPower(Math.round(Math.log(d) / Math.LN10));
   axis.tickFormat(formatTick);
 }
+
+export const getModuloScaleFactor = base =>
+  Math.max(1, Math.pow(10, Math.ceil(Math.log10(1 / base))));
+
+export const isMultipleOf = (value, base, scale = getModuloScaleFactor(base)) =>
+  (value * scale) % (base * scale) === 0;
diff --git a/frontend/src/metabase/visualizations/lib/settings/graph.js b/frontend/src/metabase/visualizations/lib/settings/graph.js
index 799f1a2f78a740423e6b1ca51004f2c950d04221..93e865edf94453eaafaa5ace550ccace6fac580d 100644
--- a/frontend/src/metabase/visualizations/lib/settings/graph.js
+++ b/frontend/src/metabase/visualizations/lib/settings/graph.js
@@ -20,6 +20,15 @@ import { dimensionIsTimeseries } from "metabase/visualizations/lib/timeseries";
 
 import _ from "underscore";
 
+// NOTE: currently we don't consider any date extracts to be histgrams
+const HISTOGRAM_DATE_EXTRACTS = new Set([
+  // "minute-of-hour",
+  // "hour-of-day",
+  // "day-of-month",
+  // "day-of-year",
+  // "week-of-year",
+]);
+
 function getSeriesDefaultTitles(series, vizSettings) {
   return series.map(s => s.card.name);
 }
@@ -43,7 +52,7 @@ export const GRAPH_DATA_SETTINGS = {
     useRawSeries: true,
   },
   "graph.dimensions": {
-    section: "Data",
+    section: t`Data`,
     title: t`X-axis`,
     widget: "fields",
     isValid: ([{ card, data }], vizSettings) =>
@@ -80,7 +89,7 @@ export const GRAPH_DATA_SETTINGS = {
     useRawSeries: true,
   },
   "graph.metrics": {
-    section: "Data",
+    section: t`Data`,
     title: t`Y-axis`,
     widget: "fields",
     isValid: ([{ card, data }], vizSettings) =>
@@ -119,7 +128,7 @@ export const GRAPH_DATA_SETTINGS = {
 
 export const GRAPH_BUBBLE_SETTINGS = {
   "scatter.bubble": {
-    section: "Data",
+    section: t`Data`,
     title: t`Bubble size`,
     widget: "field",
     isValid: ([{ card, data }], vizSettings) =>
@@ -144,7 +153,7 @@ export const GRAPH_BUBBLE_SETTINGS = {
 
 export const LINE_SETTINGS = {
   "line.interpolate": {
-    section: "Display",
+    section: t`Display`,
     title: t`Style`,
     widget: "select",
     props: {
@@ -157,7 +166,7 @@ export const LINE_SETTINGS = {
     getDefault: () => "linear",
   },
   "line.marker_enabled": {
-    section: "Display",
+    section: t`Display`,
     title: t`Show point markers on lines`,
     widget: "toggle",
   },
@@ -165,7 +174,7 @@ export const LINE_SETTINGS = {
 
 export const STACKABLE_SETTINGS = {
   "stackable.stack_type": {
-    section: "Display",
+    section: t`Display`,
     title: t`Stacking`,
     widget: "radio",
     getProps: (series, vizSettings) => ({
@@ -188,13 +197,13 @@ export const STACKABLE_SETTINGS = {
 
 export const GRAPH_GOAL_SETTINGS = {
   "graph.show_goal": {
-    section: "Display",
+    section: t`Display`,
     title: t`Show goal`,
     widget: "toggle",
     default: false,
   },
   "graph.goal_value": {
-    section: "Display",
+    section: t`Display`,
     title: t`Goal value`,
     widget: "number",
     default: 0,
@@ -205,7 +214,7 @@ export const GRAPH_GOAL_SETTINGS = {
 
 export const LINE_SETTINGS_2 = {
   "line.missing": {
-    section: "Display",
+    section: t`Display`,
     title: t`Replace missing values with`,
     widget: "select",
     default: "interpolate",
@@ -221,7 +230,7 @@ export const LINE_SETTINGS_2 = {
 
 export const GRAPH_COLORS_SETTINGS = {
   "graph.colors": {
-    section: "Display",
+    section: t`Display`,
     getTitle: ([{ card: { display } }]) =>
       capitalize(display === "scatter" ? "bubble" : display) + " colors",
     widget: "colors",
@@ -264,11 +273,14 @@ export const GRAPH_AXIS_SETTINGS = {
   },
   "graph.x_axis._is_histogram": {
     getDefault: ([{ data: { cols } }], vizSettings) =>
-      // matches binned numeric columns, and date extracts like day-of-week, etc
-      cols[0].binning_info != null || /^\w+-of-\w+$/.test(cols[0].unit),
+      // matches binned numeric columns
+      cols[0].binning_info != null ||
+      // matches certain date extracts like day-of-week, etc
+      // NOTE: currently disabled
+      HISTOGRAM_DATE_EXTRACTS.has(cols[0].unit),
   },
   "graph.x_axis.scale": {
-    section: "Axes",
+    section: t`Axes`,
     title: t`X-axis scale`,
     widget: "select",
     default: "ordinal",
@@ -301,7 +313,7 @@ export const GRAPH_AXIS_SETTINGS = {
     },
   },
   "graph.y_axis.scale": {
-    section: "Axes",
+    section: t`Axes`,
     title: t`Y-axis scale`,
     widget: "select",
     default: "linear",
@@ -314,7 +326,7 @@ export const GRAPH_AXIS_SETTINGS = {
     }),
   },
   "graph.x_axis.axis_enabled": {
-    section: "Axes",
+    section: t`Axes`,
     title: t`Show x-axis line and marks`,
     widget: "select",
     props: {
@@ -329,7 +341,7 @@ export const GRAPH_AXIS_SETTINGS = {
     default: true,
   },
   "graph.y_axis.axis_enabled": {
-    section: "Axes",
+    section: t`Axes`,
     title: t`Show y-axis line and marks`,
     widget: "select",
     props: {
@@ -341,13 +353,13 @@ export const GRAPH_AXIS_SETTINGS = {
     default: true,
   },
   "graph.y_axis.auto_range": {
-    section: "Axes",
+    section: t`Axes`,
     title: t`Auto y-axis range`,
     widget: "toggle",
     default: true,
   },
   "graph.y_axis.min": {
-    section: "Axes",
+    section: t`Axes`,
     title: t`Min`,
     widget: "number",
     default: 0,
@@ -355,7 +367,7 @@ export const GRAPH_AXIS_SETTINGS = {
       vizSettings["graph.y_axis.auto_range"] !== false,
   },
   "graph.y_axis.max": {
-    section: "Axes",
+    section: t`Axes`,
     title: t`Max`,
     widget: "number",
     default: 100,
@@ -364,20 +376,20 @@ export const GRAPH_AXIS_SETTINGS = {
   },
   /*
   "graph.y_axis_right.auto_range": {
-      section: "Axes",
+      section: t`Axes`,
       title: t`Auto right-hand y-axis range`,
       widget: "toggle",
       default: true
   },
   "graph.y_axis_right.min": {
-      section: "Axes",
+      section: t`Axes`,
       title: t`Min`,
       widget: "number",
       default: 0,
       getHidden: (series, vizSettings) => vizSettings["graph.y_axis_right.auto_range"] !== false
   },
   "graph.y_axis_right.max": {
-      section: "Axes",
+      section: t`Axes`,
       title: t`Max`,
       widget: "number",
       default: 100,
@@ -385,20 +397,20 @@ export const GRAPH_AXIS_SETTINGS = {
   },
 */
   "graph.y_axis.auto_split": {
-    section: "Axes",
+    section: t`Axes`,
     title: t`Use a split y-axis when necessary`,
     widget: "toggle",
     default: true,
     getHidden: series => series.length < 2,
   },
   "graph.x_axis.labels_enabled": {
-    section: "Labels",
+    section: t`Labels`,
     title: t`Show label on x-axis`,
     widget: "toggle",
     default: true,
   },
   "graph.x_axis.title_text": {
-    section: "Labels",
+    section: t`Labels`,
     title: t`X-axis label`,
     widget: "input",
     getHidden: (series, vizSettings) =>
@@ -407,13 +419,13 @@ export const GRAPH_AXIS_SETTINGS = {
       series.length === 1 ? getFriendlyName(series[0].data.cols[0]) : null,
   },
   "graph.y_axis.labels_enabled": {
-    section: "Labels",
+    section: t`Labels`,
     title: t`Show label on y-axis`,
     widget: "toggle",
     default: true,
   },
   "graph.y_axis.title_text": {
-    section: "Labels",
+    section: t`Labels`,
     title: t`Y-axis label`,
     widget: "input",
     getHidden: (series, vizSettings) =>
@@ -422,7 +434,7 @@ export const GRAPH_AXIS_SETTINGS = {
       series.length === 1 ? getFriendlyName(series[0].data.cols[1]) : null,
   },
   "graph.series_labels": {
-    section: "Labels",
+    section: t`Labels`,
     title: "Series labels",
     widget: "inputGroup",
     readDependencies: ["graph.dimensions", "graph.metrics"],
diff --git a/frontend/src/metabase/visualizations/lib/table_format.js b/frontend/src/metabase/visualizations/lib/table_format.js
new file mode 100644
index 0000000000000000000000000000000000000000..75a34290904b52dc3d936ee9e58c42120b10f83d
--- /dev/null
+++ b/frontend/src/metabase/visualizations/lib/table_format.js
@@ -0,0 +1,239 @@
+/* @flow */
+
+// NOTE: this file is used on the frontend and backend and there are some
+// limitations. See frontend/src/metabase-shared/color_selector for details
+
+import { alpha, getColorScale } from "metabase/lib/colors";
+
+const CELL_ALPHA = 0.65;
+const ROW_ALPHA = 0.2;
+const GRADIENT_ALPHA = 0.75;
+
+import type { Column } from "metabase/meta/types/Dataset";
+// for simplicity wheb typing assume all values are numbers, since you can only pick numeric columns
+type Value = number;
+type Row = Value[];
+
+type ColumnName = string;
+type Color = string;
+
+type SingleFormat = {
+  type: "single",
+  columns: ColumnName[],
+  color: Color,
+  operator: "<" | ">" | "<=" | ">=" | "=" | "!=",
+  value: number,
+  highlight_row: boolean,
+};
+
+type RangeFormat = {
+  type: "range",
+  columns: ColumnName[],
+  colors: Color[],
+  min_type: null | "all" | "custom",
+  min_value: number,
+  max_type: null | "all" | "custom",
+  max_value: number,
+};
+
+type Format = SingleFormat | RangeFormat;
+
+type Settings = {
+  "table.column_formatting": Format[],
+  "table.pivot"?: boolean,
+};
+
+type Formatter = (value: number) => ?Color;
+type RowFormatter = (row: number[], colIndexes: ColumnIndexes) => ?Color;
+
+type BackgroundGetter = (
+  value: number,
+  rowIndex: number,
+  colName: ColumnName,
+) => ?Color;
+
+type ColumnIndexes = {
+  [key: ColumnName]: number,
+};
+type ColumnExtents = {
+  [key: ColumnName]: [number, number],
+};
+
+export function makeCellBackgroundGetter(
+  rows: Row[],
+  cols: Column[],
+  settings: Settings,
+): BackgroundGetter {
+  const formats = settings["table.column_formatting"];
+  const pivot = settings["table.pivot"];
+  let formatters = {};
+  let rowFormatters = [];
+  const colIndexes = getColumnIndexesByName(cols);
+  try {
+    const columnExtents = computeColumnExtents(formats, rows, colIndexes);
+    formatters = compileFormatters(formats, columnExtents);
+    rowFormatters = compileRowFormatters(formats, columnExtents);
+  } catch (e) {
+    console.error(e);
+  }
+  if (Object.keys(formatters).length === 0 && rowFormatters.length === 0) {
+    return () => null;
+  } else {
+    return function(value: Value, rowIndex: number, colName: ColumnName) {
+      if (formatters[colName]) {
+        // const value = rows[rowIndex][colIndexes[colName]];
+        for (let i = 0; i < formatters[colName].length; i++) {
+          const formatter = formatters[colName][i];
+          const color = formatter(value);
+          if (color != null) {
+            return color;
+          }
+        }
+      }
+      // don't highlight row for pivoted tables
+      if (!pivot) {
+        for (let i = 0; i < rowFormatters.length; i++) {
+          const rowFormatter = rowFormatters[i];
+          const color = rowFormatter(rows[rowIndex], colIndexes);
+          if (color != null) {
+            return color;
+          }
+        }
+      }
+    };
+  }
+}
+
+function getColumnIndexesByName(cols) {
+  const colIndexes = {};
+  for (let i = 0; i < cols.length; i++) {
+    colIndexes[cols[i].name] = i;
+  }
+  return colIndexes;
+}
+
+function compileFormatter(
+  format,
+  columnName,
+  columnExtents,
+  isRowFormatter = false,
+): ?Formatter {
+  if (format.type === "single") {
+    let { operator, value, color } = format;
+    if (isRowFormatter) {
+      color = alpha(color, ROW_ALPHA);
+    } else {
+      color = alpha(color, CELL_ALPHA);
+    }
+    switch (operator) {
+      case "<":
+        return v => (v < value ? color : null);
+      case "<=":
+        return v => (v <= value ? color : null);
+      case ">=":
+        return v => (v >= value ? color : null);
+      case ">":
+        return v => (v > value ? color : null);
+      case "=":
+        return v => (v === value ? color : null);
+      case "!=":
+        return v => (v !== value ? color : null);
+    }
+  } else if (format.type === "range") {
+    const columnMin = name =>
+      // $FlowFixMe
+      columnExtents && columnExtents[name] && columnExtents[name][0];
+    const columnMax = name =>
+      // $FlowFixMe
+      columnExtents && columnExtents[name] && columnExtents[name][1];
+
+    const min =
+      format.min_type === "custom"
+        ? format.min_value
+        : format.min_type === "all"
+          ? // $FlowFixMe
+            Math.min(...format.columns.map(columnMin))
+          : columnMin(columnName);
+    const max =
+      format.max_type === "custom"
+        ? format.max_value
+        : format.max_type === "all"
+          ? // $FlowFixMe
+            Math.max(...format.columns.map(columnMax))
+          : columnMax(columnName);
+
+    if (typeof max !== "number" || typeof min !== "number") {
+      console.warn("Invalid range min/max", min, max);
+      return () => null;
+    }
+
+    return getColorScale(
+      [min, max],
+      format.colors.map(c => alpha(c, GRADIENT_ALPHA)),
+    ).clamp(true);
+  } else {
+    console.warn("Unknown format type", format.type);
+    return () => null;
+  }
+}
+
+// NOTE: implement `extent` like this rather than using d3.extent since rows may
+// be a Java `List` rather than a JavaScript Array when used in Pulse formatting
+function extent(rows: Row[], colIndex: number) {
+  let min = Infinity;
+  let max = -Infinity;
+  const length = rows.length;
+  for (let i = 0; i < length; i++) {
+    const value = rows[i][colIndex];
+    if (value < min) {
+      min = value;
+    }
+    if (value > max) {
+      max = value;
+    }
+  }
+  return [min, max];
+}
+
+function computeColumnExtents(formats, rows, colIndexes) {
+  const columnExtents = {};
+  formats.forEach(format => {
+    format.columns.forEach(columnName => {
+      if (!columnExtents[columnName]) {
+        const colIndex = colIndexes[columnName];
+        columnExtents[columnName] = extent(rows, colIndex);
+      }
+    });
+  });
+  return columnExtents;
+}
+
+function compileFormatters(formats: Format[], columnExtents: ColumnExtents) {
+  const formatters = {};
+  formats.forEach(format => {
+    format.columns.forEach(columnName => {
+      formatters[columnName] = formatters[columnName] || [];
+      formatters[columnName].push(
+        compileFormatter(format, columnName, columnExtents, false),
+      );
+    });
+  });
+  return formatters;
+}
+
+function compileRowFormatters(formats: Format[]): RowFormatter[] {
+  const rowFormatters = [];
+  formats
+    .filter(format => format.type === "single" && format.highlight_row)
+    .forEach(format => {
+      const formatter = compileFormatter(format, null, null, true);
+      if (formatter) {
+        format.columns.forEach(columnName => {
+          rowFormatters.push((row, colIndexes) =>
+            formatter(row[colIndexes[columnName]]),
+          );
+        });
+      }
+    });
+  return rowFormatters;
+}
diff --git a/frontend/src/metabase/visualizations/lib/utils.js b/frontend/src/metabase/visualizations/lib/utils.js
index 31cc7e71796697cce2e079a884e69c82aca35fee..bac3243557d2e537daaa35edfead60a6eccb7eba 100644
--- a/frontend/src/metabase/visualizations/lib/utils.js
+++ b/frontend/src/metabase/visualizations/lib/utils.js
@@ -6,7 +6,7 @@ import d3 from "d3";
 import { t } from "c-3po";
 import crossfilter from "crossfilter";
 
-import * as colors from "metabase/lib/colors";
+import { harmony } from "metabase/lib/colors";
 
 const SPLIT_AXIS_UNSPLIT_COST = -100;
 const SPLIT_AXIS_COST_FACTOR = 2;
@@ -172,8 +172,8 @@ export function getCardColors(card) {
     chartColorList = settings.line.colors;
   }
   return _.uniq(
-    [chartColor || Object.values(colors.harmony)[0]].concat(
-      chartColorList || Object.values(colors.harmony),
+    [chartColor || Object.values(harmony)[0]].concat(
+      chartColorList || Object.values(harmony),
     ),
   );
 }
diff --git a/frontend/src/metabase/visualizations/visualizations/Funnel.jsx b/frontend/src/metabase/visualizations/visualizations/Funnel.jsx
index bfa0090c6b9cce075402d13e77dd1ee48373f397..3c81e6b54e267254dbc2b7fe0c932187ae7a9c5c 100644
--- a/frontend/src/metabase/visualizations/visualizations/Funnel.jsx
+++ b/frontend/src/metabase/visualizations/visualizations/Funnel.jsx
@@ -63,14 +63,14 @@ export default class Funnel extends Component {
 
   static settings = {
     "funnel.dimension": {
-      section: "Data",
+      section: t`Data`,
       title: t`Step`,
       ...dimensionSetting("funnel.dimension"),
       dashboard: false,
       useRawSeries: true,
     },
     "funnel.metric": {
-      section: "Data",
+      section: t`Data`,
       title: t`Measure`,
       ...metricSetting("funnel.metric"),
       dashboard: false,
@@ -78,7 +78,7 @@ export default class Funnel extends Component {
     },
     "funnel.type": {
       title: t`Funnel type`,
-      section: "Display",
+      section: t`Display`,
       widget: "select",
       props: {
         options: [
@@ -157,7 +157,6 @@ export default class Funnel extends Component {
           )}
           <LegendHeader
             className="flex-no-shrink"
-            // $FlowFixMe
             series={series._raw || series}
             actionButtons={!hasTitle && actionButtons}
             onChangeCardAndRun={onChangeCardAndRun}
diff --git a/frontend/src/metabase/visualizations/visualizations/Gauge.jsx b/frontend/src/metabase/visualizations/visualizations/Gauge.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..f313e4f166e5638f3f3414b662ca05de330ed6c0
--- /dev/null
+++ b/frontend/src/metabase/visualizations/visualizations/Gauge.jsx
@@ -0,0 +1,405 @@
+/* @flow */
+
+import React, { Component } from "react";
+import ReactDOM from "react-dom";
+import { t } from "c-3po";
+import d3 from "d3";
+import cx from "classnames";
+
+import colors from "metabase/lib/colors";
+import { formatValue } from "metabase/lib/formatting";
+import { isNumeric } from "metabase/lib/schema_metadata";
+
+import ChartSettingGaugeSegments from "metabase/visualizations/components/settings/ChartSettingGaugeSegments";
+
+import type { VisualizationProps } from "metabase/meta/types/Visualization";
+
+const MAX_WIDTH = 500;
+const PADDING_BOTTOM = 10;
+
+const OUTER_RADIUS = 45; // within 100px SVG element
+const INNER_RADIUS_RATIO = 3.7 / 5;
+const INNER_RADIUS = OUTER_RADIUS * INNER_RADIUS_RATIO;
+
+// arrow shape, currently an equilateral triangle
+const ARROW_HEIGHT = (OUTER_RADIUS - INNER_RADIUS) * 2.5 / 4; // 2/3 of segment thickness
+const ARROW_BASE = ARROW_HEIGHT / Math.tan(64 / 180 * Math.PI);
+const ARROW_STROKE_THICKNESS = 1.25;
+
+// colors
+const BACKGROUND_ARC_COLOR = colors["bg-medium"];
+const SEGMENT_LABEL_COLOR = colors["text-dark"];
+const CENTER_LABEL_COLOR = colors["text-dark"];
+const ARROW_FILL_COLOR = colors["text-medium"];
+const ARROW_STROKE_COLOR = "white";
+
+// in ems, but within the scaled 100px SVG element
+const FONT_SIZE_SEGMENT_LABEL = 0.25;
+const FONT_SIZE_CENTER_LABEL_MIN = 0.5;
+const FONT_SIZE_CENTER_LABEL_MAX = 0.7;
+
+// hide labels if SVG width is smaller than this
+const MIN_WIDTH_LABEL_THRESHOLD = 250;
+
+const LABEL_OFFSET_PERCENT = 1.025;
+
+// total degrees of the arc (180 = semicircle, etc)
+const ARC_DEGREES = 180 + 45 * 2; // semicircle plus a bit
+
+const radians = degrees => degrees * Math.PI / 180;
+const degrees = radians => radians * 180 / Math.PI;
+
+const segmentIsValid = s => !isNaN(s.min) && !isNaN(s.max);
+
+export default class Gauge extends Component {
+  props: VisualizationProps;
+
+  static uiName = t`Gauge`;
+  static identifier = "gauge";
+  static iconName = "gauge";
+
+  static minSize = { width: 4, height: 4 };
+
+  static isSensible(cols, rows) {
+    return rows.length === 1 && cols.length === 1;
+  }
+
+  static checkRenderable([{ data: { cols, rows } }]) {
+    if (!isNumeric(cols[0])) {
+      throw new Error(t`Gauge visualization requires a number.`);
+    }
+  }
+
+  state = {
+    mounted: false,
+  };
+
+  _label: ?HTMLElement;
+
+  static settings = {
+    "gauge.range": {
+      // currently not exposed in settings, just computed from gauge.segments
+      getDefault(series, vizSettings) {
+        const segments = vizSettings["gauge.segments"].filter(segmentIsValid);
+        const values = [
+          ...segments.map(s => s.max),
+          ...segments.map(s => s.min),
+        ];
+        return values.length > 0
+          ? [Math.min(...values), Math.max(...values)]
+          : [0, 1];
+      },
+      readDependencies: ["gauge.segments"],
+    },
+    "gauge.segments": {
+      section: "Display",
+      title: t`Gauge ranges`,
+      getDefault(series) {
+        let value = 100;
+        try {
+          value = series[0].data.rows[0][0];
+        } catch (e) {}
+        return [
+          { min: 0, max: value / 2, color: colors["error"], label: "" },
+          { min: value / 2, max: value, color: colors["warning"], label: "" },
+          { min: value, max: value * 2, color: colors["success"], label: "" },
+        ];
+      },
+      widget: ChartSettingGaugeSegments,
+      persistDefault: true,
+    },
+  };
+
+  componentDidMount() {
+    this.setState({ mounted: true });
+    this._updateLabelSize();
+  }
+  componentDidUpdate() {
+    this._updateLabelSize();
+  }
+
+  _updateLabelSize() {
+    // TODO: extract this into a component that resizes SVG <text> element to fit bounds
+    const label = this._label && ReactDOM.findDOMNode(this._label);
+    if (label) {
+      const { width: currentWidth } = label.getBBox();
+      // maxWidth currently 95% of inner diameter, could be more intelligent based on text aspect ratio
+      const maxWidth = INNER_RADIUS * 2 * 0.95;
+      const currentFontSize = parseFloat(
+        label.style.fontSize.replace("em", ""),
+      );
+      // scale the font based on currentWidth/maxWidth, within min and max
+      // TODO: if text is too big wrap or ellipsis?
+      const desiredFontSize = Math.max(
+        FONT_SIZE_CENTER_LABEL_MIN,
+        Math.min(
+          FONT_SIZE_CENTER_LABEL_MAX,
+          currentFontSize * (maxWidth / currentWidth),
+        ),
+      );
+      // don't resize if within 5% to avoid potential thrashing
+      if (Math.abs(1 - currentFontSize / desiredFontSize) > 0.05) {
+        label.style.fontSize = desiredFontSize + "em";
+      }
+    }
+  }
+
+  render() {
+    const {
+      series: [{ data: { rows, cols } }],
+      settings,
+      className,
+      isSettings,
+    } = this.props;
+
+    const width = this.props.width;
+    const height = this.props.height - PADDING_BOTTOM;
+
+    const viewBoxHeight =
+      (ARC_DEGREES > 180 ? 50 : 0) + Math.sin(radians(ARC_DEGREES / 2)) * 50;
+    const viewBoxWidth = 100;
+
+    const svgAspectRatio = viewBoxHeight / viewBoxWidth;
+    const containerAspectRadio = height / width;
+
+    let svgWidth, svgHeight;
+    if (containerAspectRadio < svgAspectRatio) {
+      svgWidth = Math.min(MAX_WIDTH, height / svgAspectRatio);
+    } else {
+      svgWidth = Math.min(MAX_WIDTH, width);
+    }
+    svgHeight = svgWidth * svgAspectRatio;
+
+    const showLabels = svgWidth > MIN_WIDTH_LABEL_THRESHOLD;
+
+    const range = settings["gauge.range"];
+    const segments = settings["gauge.segments"].filter(segmentIsValid);
+
+    // value to angle in radians, clamped
+    const angle = d3.scale
+      .linear()
+      .domain(range) // NOTE: confusing, but the "range" is the domain for the arc scale
+      .range([
+        ARC_DEGREES / 180 * -Math.PI / 2,
+        ARC_DEGREES / 180 * Math.PI / 2,
+      ])
+      .clamp(true);
+
+    const value = rows[0][0];
+    const column = cols[0];
+
+    const valuePosition = (value, distance) => {
+      return [
+        Math.cos(angle(value) - Math.PI / 2) * distance,
+        Math.sin(angle(value) - Math.PI / 2) * distance,
+      ];
+    };
+
+    // get unique min/max plus range endpoints
+    const numberLabels = Array.from(
+      new Set(
+        range.concat(...segments.map(segment => [segment.min, segment.max])),
+      ),
+    );
+
+    const textLabels = segments
+      .filter(segment => segment.label)
+      .map(segment => ({
+        label: segment.label,
+        value: segment.min + (segment.max - segment.min) / 2,
+      }));
+
+    // expand the width to fill available space so that labels don't overflow as often
+    const expandWidthFactor = width / svgWidth;
+
+    return (
+      <div className={cx(className, "relative")}>
+        <div
+          className="absolute overflow-hidden"
+          style={{
+            width: svgWidth * expandWidthFactor,
+            height: svgHeight,
+            top: (height - svgHeight) / 2,
+            left:
+              (width - svgWidth) / 2 -
+              // shift to the left the
+              (svgWidth * expandWidthFactor - svgWidth) / 2,
+          }}
+        >
+          <svg
+            viewBox={`0 0 ${viewBoxWidth * expandWidthFactor} ${viewBoxHeight}`}
+          >
+            <g
+              transform={`translate(${viewBoxWidth *
+                expandWidthFactor /
+                2},50)`}
+            >
+              {/* BACKGROUND ARC */}
+              <GaugeArc
+                start={angle(range[0])}
+                end={angle(range[1])}
+                fill={BACKGROUND_ARC_COLOR}
+              />
+              {/* SEGMENT ARCS */}
+              {segments.map((segment, index) => (
+                <GaugeArc
+                  key={index}
+                  start={angle(segment.min)}
+                  end={angle(segment.max)}
+                  fill={segment.color}
+                  segment={segment}
+                  onHoverChange={this.props.onHoverChange}
+                />
+              ))}
+              {/* NEEDLE */}
+              <GaugeNeedle
+                angle={angle(this.state.mounted ? value : 0)}
+                isAnimated={!isSettings}
+              />
+              {/* NUMBER LABELS */}
+              {showLabels &&
+                numberLabels.map((value, index) => (
+                  <GaugeSegmentLabel
+                    position={valuePosition(
+                      value,
+                      OUTER_RADIUS * LABEL_OFFSET_PERCENT,
+                    )}
+                  >
+                    {formatValue(value, { column })}
+                  </GaugeSegmentLabel>
+                ))}
+              {/* TEXT LABELS */}
+              {showLabels &&
+                textLabels.map(({ label, value }, index) => (
+                  <HideIfOverlowingSVG>
+                    <GaugeSegmentLabel
+                      position={valuePosition(
+                        value,
+                        OUTER_RADIUS * LABEL_OFFSET_PERCENT,
+                      )}
+                      style={{
+                        fill: SEGMENT_LABEL_COLOR,
+                      }}
+                    >
+                      {label}
+                    </GaugeSegmentLabel>
+                  </HideIfOverlowingSVG>
+                ))}
+              {/* CENTER LABEL */}
+              {/* NOTE: can't be a component because ref doesn't work? */}
+              <text
+                ref={label => (this._label = label)}
+                x={0}
+                y={0}
+                style={{
+                  fill: CENTER_LABEL_COLOR,
+                  fontSize: "1em",
+                  fontWeight: "bold",
+                  textAnchor: "middle",
+                  transform: "translate(0,0.2em)",
+                }}
+              >
+                {formatValue(value, { column })}
+              </text>
+            </g>
+          </svg>
+        </div>
+      </div>
+    );
+  }
+}
+
+const GaugeArc = ({ start, end, fill, segment, onHoverChange }) => {
+  const arc = d3.svg
+    .arc()
+    .outerRadius(OUTER_RADIUS)
+    .innerRadius(OUTER_RADIUS * INNER_RADIUS_RATIO);
+  return (
+    <path
+      d={arc({
+        startAngle: start,
+        endAngle: end,
+      })}
+      fill={fill}
+      onMouseMove={
+        onHoverChange && segment.label
+          ? e =>
+              onHoverChange({
+                data: [
+                  {
+                    key: segment.label,
+                    value: `${segment.min} - ${segment.max}`,
+                  },
+                ],
+                event: e.nativeEvent,
+              })
+          : null
+      }
+      onMouseLeave={onHoverChange ? () => onHoverChange(null) : null}
+    />
+  );
+};
+
+const GaugeNeedle = ({ angle, isAnimated = true }) => (
+  <path
+    d={`M-${ARROW_BASE} 0 L0 -${ARROW_HEIGHT} L${ARROW_BASE} 0 Z`}
+    transform={`translate(0,-${INNER_RADIUS}) rotate(${degrees(
+      angle,
+    )}, 0, ${INNER_RADIUS})`}
+    style={isAnimated ? { transition: "transform 1.5s ease-in-out" } : null}
+    stroke={ARROW_STROKE_COLOR}
+    strokeWidth={ARROW_STROKE_THICKNESS}
+    fill={ARROW_FILL_COLOR}
+  />
+);
+
+const GaugeSegmentLabel = ({ position: [x, y], style = {}, children }) => (
+  <text
+    x={x}
+    y={y}
+    style={{
+      fill: colors["text-medium"],
+      fontSize: `${FONT_SIZE_SEGMENT_LABEL}em`,
+      textAnchor: Math.abs(x) < 5 ? "middle" : x > 0 ? "start" : "end",
+      // shift text in the lower half down a bit
+      transform:
+        y > 0 ? `translate(0,${FONT_SIZE_SEGMENT_LABEL}em)` : undefined,
+      ...style,
+    }}
+  >
+    {children}
+  </text>
+);
+
+class HideIfOverlowingSVG extends React.Component {
+  componentDidMount() {
+    this._hideIfClipped();
+  }
+  componentDidUpdate() {
+    this._hideIfClipped();
+  }
+  _hideIfClipped() {
+    const element = ReactDOM.findDOMNode(this);
+    if (element) {
+      let svg = element;
+      while (svg.nodeName.toLowerCase() !== "svg") {
+        svg = svg.parentNode;
+      }
+      const svgRect = svg.getBoundingClientRect();
+      const elementRect = element.getBoundingClientRect();
+      if (
+        elementRect.left >= svgRect.left &&
+        elementRect.right <= svgRect.right &&
+        elementRect.top >= svgRect.top &&
+        elementRect.bottom <= svgRect.bottom
+      ) {
+        element.classList.remove("hidden");
+      } else {
+        element.classList.add("hidden");
+      }
+    }
+  }
+  render() {
+    return this.props.children;
+  }
+}
diff --git a/frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx b/frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx
index 2daa03af2f7f6354efa75f4669853c980291edea..e62b13759d79101ead79b3848c38ab28d872ab38 100644
--- a/frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx
+++ b/frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx
@@ -97,7 +97,7 @@ export class ObjectDetail extends Component {
       isLink = false;
     } else {
       if (value === null || value === undefined || value === "") {
-        cellValue = <span className="text-grey-2">{t`Empty`}</span>;
+        cellValue = <span className="text-light">{t`Empty`}</span>;
       } else if (isa(column.special_type, TYPE.SerializedJSON)) {
         let formattedJson = JSON.stringify(JSON.parse(value), null, 2);
         cellValue = <pre className="ObjectJSON">{formattedJson}</pre>;
@@ -208,7 +208,7 @@ export class ObjectDetail extends Component {
         );
         const via =
           fkCountsByTable[fk.origin.table.id] > 1 ? (
-            <span className="text-grey-3 text-normal">
+            <span className="text-medium text-normal">
               {" "}
               {t`via ${fk.origin.display_name}`}
             </span>
@@ -226,7 +226,7 @@ export class ObjectDetail extends Component {
         let fkReference;
         const referenceClasses = cx("flex align-center my2 pb2 border-bottom", {
           "text-brand-hover cursor-pointer text-dark": fkClickable,
-          "text-grey-3": !fkClickable,
+          "text-medium": !fkClickable,
         });
 
         if (fkClickable) {
@@ -284,7 +284,7 @@ export class ObjectDetail extends Component {
             </div>
           </div>
           <div className="Grid-cell flex align-center Cell--1of3 bg-alt">
-            <div className="p4 flex align-center text-bold text-grey-3">
+            <div className="p4 flex align-center text-bold text-medium">
               <Icon name="connections" size={17} />
               <div className="ml2">
                 {jt`This ${(
diff --git a/frontend/src/metabase/visualizations/visualizations/PieChart.css b/frontend/src/metabase/visualizations/visualizations/PieChart.css
index e8416580450c54a991fd517322e88b336950284d..b171490e8ae7a2ea7714c1f4988840414c1ad53a 100644
--- a/frontend/src/metabase/visualizations/visualizations/PieChart.css
+++ b/frontend/src/metabase/visualizations/visualizations/PieChart.css
@@ -26,13 +26,13 @@
 }
 
 :local .Value {
-  color: #676c72;
+  color: var(--color-text-dark);
   font-size: 22px;
   font-weight: bolder;
 }
 
 :local .Title {
-  color: #b8c0c9;
+  color: var(--color-text-light);
   font-size: 14px;
   font-weight: bold;
   text-transform: uppercase;
diff --git a/frontend/src/metabase/visualizations/visualizations/PieChart.jsx b/frontend/src/metabase/visualizations/visualizations/PieChart.jsx
index 5561ed83238a06dfdb49b976380209feb93bbb8b..0a25e0177c147897484ed04ca3f7d43fdf88232f 100644
--- a/frontend/src/metabase/visualizations/visualizations/PieChart.jsx
+++ b/frontend/src/metabase/visualizations/visualizations/PieChart.jsx
@@ -16,7 +16,7 @@ import {
 
 import { formatValue } from "metabase/lib/formatting";
 
-import * as colors from "metabase/lib/colors";
+import { normal, harmony } from "metabase/lib/colors";
 
 import cx from "classnames";
 
@@ -58,28 +58,28 @@ export default class PieChart extends Component {
 
   static settings = {
     "pie.dimension": {
-      section: "Data",
+      section: t`Data`,
       title: t`Dimension`,
       ...dimensionSetting("pie.dimension"),
     },
     "pie.metric": {
-      section: "Data",
+      section: t`Data`,
       title: t`Measure`,
       ...metricSetting("pie.metric"),
     },
     "pie.show_legend": {
-      section: "Display",
+      section: t`Display`,
       title: t`Show legend`,
       widget: "toggle",
     },
     "pie.show_legend_perecent": {
-      section: "Display",
+      section: t`Display`,
       title: t`Show percentages in legend`,
       widget: "toggle",
       default: true,
     },
     "pie.slice_threshold": {
-      section: "Display",
+      section: t`Display`,
       title: t`Minimum slice percentage`,
       widget: "number",
       default: SLICE_THRESHOLD * 100,
@@ -136,9 +136,7 @@ export default class PieChart extends Component {
     let total: number = rows.reduce((sum, row) => sum + row[metricIndex], 0);
 
     // use standard colors for up to 5 values otherwise use color harmony to help differentiate slices
-    let sliceColors = Object.values(
-      rows.length > 5 ? colors.harmony : colors.normal,
-    );
+    let sliceColors = Object.values(rows.length > 5 ? harmony : normal);
     let sliceThreshold =
       typeof settings["pie.slice_threshold"] === "number"
         ? settings["pie.slice_threshold"] / 100
@@ -162,7 +160,7 @@ export default class PieChart extends Component {
           key: "Other",
           value: otherTotal,
           percentage: otherTotal / total,
-          color: colors.normal.grey1,
+          color: normal.grey1,
         };
         slices.push(otherSlice);
       }
@@ -188,7 +186,7 @@ export default class PieChart extends Component {
     if (slices.length === 0) {
       otherSlice = {
         value: 1,
-        color: colors.normal.grey1,
+        color: normal.grey1,
         noHover: true,
       };
       slices.push(otherSlice);
diff --git a/frontend/src/metabase/visualizations/visualizations/Progress.jsx b/frontend/src/metabase/visualizations/visualizations/Progress.jsx
index ee0a439cd41446cb30b8609c3180234c293c3656..a93144d0012324dd08ab1d86e9bab2ae959405ca 100644
--- a/frontend/src/metabase/visualizations/visualizations/Progress.jsx
+++ b/frontend/src/metabase/visualizations/visualizations/Progress.jsx
@@ -38,13 +38,13 @@ export default class Progress extends Component {
 
   static settings = {
     "progress.goal": {
-      section: "Display",
+      section: t`Display`,
       title: t`Goal`,
       widget: "number",
       default: 0,
     },
     "progress.color": {
-      section: "Display",
+      section: t`Display`,
       title: t`Color`,
       widget: "color",
       default: normal.green,
@@ -152,7 +152,7 @@ export default class Progress extends Component {
         >
           <div
             ref="container"
-            className="relative text-bold text-grey-4"
+            className="relative text-bold text-medium"
             style={{ height: 20 }}
           >
             <div ref="label" style={{ position: "absolute" }}>
diff --git a/frontend/src/metabase/visualizations/visualizations/Scalar.css b/frontend/src/metabase/visualizations/visualizations/Scalar.css
index 0a372b305caf7145fbcf067662a3931b255e36f0..e64e37bf166cc290b2d4950921d8896dab96f6e1 100644
--- a/frontend/src/metabase/visualizations/visualizations/Scalar.css
+++ b/frontend/src/metabase/visualizations/visualizations/Scalar.css
@@ -3,7 +3,7 @@
   flex-direction: column;
   justify-content: center;
   padding: 1em 2em 1em 2em;
-  color: #525658;
+  color: var(--color-text-dark);
 }
 :local .Scalar .Value {
   font-weight: bold;
diff --git a/frontend/src/metabase/visualizations/visualizations/Table.jsx b/frontend/src/metabase/visualizations/visualizations/Table.jsx
index 0ec7fef9f0ab3e0b24d5dc1bfa9cde5b49d493e3..48f2aa751795c54eb21b6a8923627b38337fd4c2 100644
--- a/frontend/src/metabase/visualizations/visualizations/Table.jsx
+++ b/frontend/src/metabase/visualizations/visualizations/Table.jsx
@@ -6,23 +6,20 @@ import TableInteractive from "../components/TableInteractive.jsx";
 import TableSimple from "../components/TableSimple.jsx";
 import { t } from "c-3po";
 import * as DataGrid from "metabase/lib/data_grid";
+import { findColumnIndexForColumnSetting } from "metabase/lib/dataset";
 
 import Query from "metabase/lib/query";
 import { isMetric, isDimension } from "metabase/lib/schema_metadata";
-import {
-  columnsAreValid,
-  getFriendlyName,
-} from "metabase/visualizations/lib/utils";
-import ChartSettingOrderedFields from "metabase/visualizations/components/settings/ChartSettingOrderedFields.jsx";
+import { columnsAreValid } from "metabase/visualizations/lib/utils";
+import ChartSettingOrderedColumns from "metabase/visualizations/components/settings/ChartSettingOrderedColumns.jsx";
 import ChartSettingsTableFormatting, {
   isFormattable,
 } from "metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx";
 
+import { makeCellBackgroundGetter } from "metabase/visualizations/lib/table_format";
+
 import _ from "underscore";
 import cx from "classnames";
-import d3 from "d3";
-import Color from "color";
-import { getColorScale } from "metabase/lib/colors";
 
 import RetinaImage from "react-retina-image";
 import { getIn } from "icepick";
@@ -30,10 +27,6 @@ import { getIn } from "icepick";
 import type { DatasetData } from "metabase/meta/types/Dataset";
 import type { Card, VisualizationSettings } from "metabase/meta/types/Card";
 
-const CELL_ALPHA = 0.65;
-const ROW_ALPHA = 0.2;
-const GRADIENT_ALPHA = 0.75;
-
 type Props = {
   card: Card,
   data: DatasetData,
@@ -44,115 +37,6 @@ type State = {
   data: ?DatasetData,
 };
 
-const alpha = (color, amount) =>
-  Color(color)
-    .alpha(amount)
-    .string();
-
-function compileFormatter(
-  format,
-  columnName,
-  columnExtents,
-  isRowFormatter = false,
-) {
-  if (format.type === "single") {
-    let { operator, value, color } = format;
-    if (isRowFormatter) {
-      color = alpha(color, ROW_ALPHA);
-    } else {
-      color = alpha(color, CELL_ALPHA);
-    }
-    switch (operator) {
-      case "<":
-        return v => (v < value ? color : null);
-      case "<=":
-        return v => (v <= value ? color : null);
-      case ">=":
-        return v => (v >= value ? color : null);
-      case ">":
-        return v => (v > value ? color : null);
-      case "=":
-        return v => (v === value ? color : null);
-      case "!=":
-        return v => (v !== value ? color : null);
-    }
-  } else if (format.type === "range") {
-    const columnMin = name =>
-      columnExtents && columnExtents[name] && columnExtents[name][0];
-    const columnMax = name =>
-      columnExtents && columnExtents[name] && columnExtents[name][1];
-
-    const min =
-      format.min_type === "custom"
-        ? format.min_value
-        : format.min_type === "all"
-          ? Math.min(...format.columns.map(columnMin))
-          : columnMin(columnName);
-    const max =
-      format.max_type === "custom"
-        ? format.max_value
-        : format.max_type === "all"
-          ? Math.max(...format.columns.map(columnMax))
-          : columnMax(columnName);
-
-    if (typeof max !== "number" || typeof min !== "number") {
-      console.warn("Invalid range min/max", min, max);
-      return () => null;
-    }
-
-    return getColorScale(
-      [min, max],
-      format.colors.map(c => alpha(c, GRADIENT_ALPHA)),
-    ).clamp(true);
-  } else {
-    console.warn("Unknown format type", format.type);
-    return () => null;
-  }
-}
-
-function computeColumnExtents(formats, data) {
-  return _.chain(formats)
-    .map(format => format.columns)
-    .flatten()
-    .uniq()
-    .map(columnName => {
-      const colIndex = _.findIndex(data.cols, col => col.name === columnName);
-      return [columnName, d3.extent(data.rows, row => row[colIndex])];
-    })
-    .object()
-    .value();
-}
-
-function compileFormatters(formats, columnExtents) {
-  const formatters = {};
-  for (const format of formats) {
-    for (const columnName of format.columns) {
-      formatters[columnName] = formatters[columnName] || [];
-      formatters[columnName].push(
-        compileFormatter(format, columnName, columnExtents, false),
-      );
-    }
-  }
-  return formatters;
-}
-
-function compileRowFormatters(formats) {
-  const rowFormatters = [];
-  for (const format of formats.filter(
-    format => format.type === "single" && format.highlight_row,
-  )) {
-    const formatter = compileFormatter(format, null, null, true);
-    if (formatter) {
-      for (const colName of format.columns) {
-        rowFormatters.push((row, colIndexes) =>
-          formatter(row[colIndexes[colName]]),
-        );
-      }
-    }
-  }
-  return rowFormatters;
-}
-
 export default class Table extends Component {
   props: Props;
   state: State;
@@ -173,7 +57,7 @@ export default class Table extends Component {
 
   static settings = {
     "table.pivot": {
-      section: "Data",
+      section: t`Data`,
       title: t`Pivot the table`,
       widget: "toggle",
       getHidden: ([{ card, data }]) => data && data.cols.length !== 3,
@@ -185,9 +69,9 @@ export default class Table extends Component {
         data.cols.filter(isDimension).length === 2,
     },
     "table.columns": {
-      section: "Data",
-      title: t`Fields to include`,
-      widget: ChartSettingOrderedFields,
+      section: t`Data`,
+      title: t`Visible fields`,
+      widget: ChartSettingOrderedColumns,
       getHidden: (series, vizSettings) => vizSettings["table.pivot"],
       isValid: ([{ card, data }]) =>
         card.visualization_settings["table.columns"] &&
@@ -201,15 +85,12 @@ export default class Table extends Component {
           enabled: col.visibility_type !== "details-only",
         })),
       getProps: ([{ data: { cols } }]) => ({
-        columnNames: cols.reduce(
-          (o, col) => ({ ...o, [col.name]: getFriendlyName(col) }),
-          {},
-        ),
+        columns: cols,
       }),
     },
     "table.column_widths": {},
     "table.column_formatting": {
-      section: "Formatting",
+      section: t`Formatting`,
       widget: ChartSettingsTableFormatting,
       default: [],
       getProps: ([{ data: { cols } }], settings) => ({
@@ -221,49 +102,8 @@ export default class Table extends Component {
       readDependencies: ["table.pivot"],
     },
     "table._cell_background_getter": {
-      getValue([{ data }], settings) {
-        const { rows, cols } = data;
-        const formats = settings["table.column_formatting"];
-        const pivot = settings["table.pivot"];
-        let formatters = {};
-        let rowFormatters = [];
-        try {
-          const columnExtents = computeColumnExtents(formats, data);
-          formatters = compileFormatters(formats, columnExtents);
-          rowFormatters = compileRowFormatters(formats, columnExtents);
-        } catch (e) {
-          console.error(e);
-        }
-        const colIndexes = _.object(
-          cols.map((col, index) => [col.name, index]),
-        );
-        if (
-          Object.values(formatters).length === 0 &&
-          Object.values(formatters).length === 0
-        ) {
-          return null;
-        } else {
-          return function(value, rowIndex, colName) {
-            if (formatters[colName]) {
-              // const value = rows[rowIndex][colIndexes[colName]];
-              for (const formatter of formatters[colName]) {
-                const color = formatter(value);
-                if (color != null) {
-                  return color;
-                }
-              }
-            }
-            // don't highlight row for pivoted tables
-            if (!pivot) {
-              for (const rowFormatter of rowFormatters) {
-                const color = rowFormatter(rows[rowIndex], colIndexes);
-                if (color != null) {
-                  return color;
-                }
-              }
-            }
-          };
-        }
+      getValue([{ data: { rows, cols } }], settings) {
+        return makeCellBackgroundGetter(rows, cols, settings);
       },
       readDependencies: ["table.column_formatting", "table.pivot"],
     },
@@ -304,10 +144,13 @@ export default class Table extends Component {
       });
     } else {
       const { cols, rows, columns } = data;
-      const columnIndexes = settings["table.columns"]
-        .filter(f => f.enabled)
-        .map(f => _.findIndex(cols, c => c.name === f.name))
-        .filter(i => i >= 0 && i < cols.length);
+      const columnSettings = settings["table.columns"];
+      const columnIndexes = columnSettings
+        .filter(columnSetting => columnSetting.enabled)
+        .map(columnSetting =>
+          findColumnIndexForColumnSetting(cols, columnSetting),
+        )
+        .filter(columnIndex => columnIndex >= 0 && columnIndex < cols.length);
 
       this.setState({
         data: {
@@ -322,7 +165,7 @@ export default class Table extends Component {
   render() {
     const { card, isDashboard, settings } = this.props;
     const { data } = this.state;
-    const sort = getIn(card, ["dataset_query", "query", "order_by"]) || null;
+    const sort = getIn(card, ["dataset_query", "query", "order-by"]) || null;
     const isPivoted = settings["table.pivot"];
     const isColumnsDisabled =
       (settings["table.columns"] || []).filter(f => f.enabled).length < 1;
diff --git a/frontend/src/metabase/visualizations/visualizations/Text.css b/frontend/src/metabase/visualizations/visualizations/Text.css
index 9de3a09bb365503013822517f5bf8582c31202e9..49290aac48694d6b05bc225ca4b0c4bbf5d4bbad 100644
--- a/frontend/src/metabase/visualizations/visualizations/Text.css
+++ b/frontend/src/metabase/visualizations/visualizations/Text.css
@@ -21,9 +21,9 @@
   border-radius: var(--default-border-radius);
 }
 :local .Text .text-card-textarea:focus {
-  border-color: var(--brand-color);
+  border-color: var(--color-brand);
   background-color: white;
-  box-shadow: 0 1px 7px rgba(0, 0, 0, 0.1);
+  box-shadow: 0 1px 7px var(--color-shadow);
 }
 :local .Text .text-card-markdown {
   overflow: auto;
@@ -90,7 +90,7 @@
   font-weight: bold;
   cursor: pointer;
   text-decoration: none;
-  color: var(--default-link-color);
+  color: var(--color-brand);
 }
 :local .text-card-markdown a:hover {
   text-decoration: underline;
@@ -116,15 +116,15 @@
   text-align: left;
 }
 :local .text-card-markdown tr {
-  border-bottom: 1px solid var(--table-border-color);
+  border-bottom: 1px solid color(var(--color-border) alpha(-70%));
 }
 :local .text-card-markdown tr:nth-child(even) {
-  background-color: var(--table-alt-bg-color);
+  background-color: color(var(--color-bg-black) alpha(-98%));
 }
 :local .text-card-markdown th,
 :local .text-card-markdown td {
   padding: 0.75em;
-  border: 1px solid var(--table-border-color);
+  border: 1px solid color(var(--color-border) alpha(-70%));
 }
 
 :local .text-card-markdown code {
@@ -132,7 +132,7 @@
   font-size: 12.64px;
   line-height: 20px;
   padding: 0 0.25em;
-  background-color: var(--base-grey);
+  background-color: var(--color-bg-light);
   border-radius: var(--default-border-radius);
 }
 
@@ -143,8 +143,8 @@
 }
 
 :local .text-card-markdown blockquote {
-  color: var(--grey-4);
-  border-left: 5px solid var(--grey-1);
+  color: var(--color-text-medium);
+  border-left: 5px solid var(--color-border);
   padding: 0 1.5em 0 17px;
   margin: 0.5em 0 0.5em 1em;
 }
diff --git a/frontend/src/metabase/visualizations/visualizations/Text.jsx b/frontend/src/metabase/visualizations/visualizations/Text.jsx
index 36491ff7ab759355f9829cd6eda267cc14dc5133..837544ec8c26342ec852360b058df503be0b7e00 100644
--- a/frontend/src/metabase/visualizations/visualizations/Text.jsx
+++ b/frontend/src/metabase/visualizations/visualizations/Text.jsx
@@ -69,7 +69,7 @@ export default class Text extends Component {
       default: "",
     },
     "text.align_vertical": {
-      section: "Display",
+      section: t`Display`,
       title: t`Vertical Alignment`,
       widget: "select",
       props: {
@@ -82,7 +82,7 @@ export default class Text extends Component {
       default: "top",
     },
     "text.align_horizontal": {
-      section: "Display",
+      section: t`Display`,
       title: t`Horizontal Alignment`,
       widget: "select",
       props: {
@@ -95,7 +95,7 @@ export default class Text extends Component {
       default: "left",
     },
     "dashcard.background": {
-      section: "Display",
+      section: t`Display`,
       title: t`Show background`,
       dashboard: true,
       widget: "toggle",
@@ -160,7 +160,7 @@ export default class Text extends Component {
           ) : (
             <textarea
               className={cx(
-                "full flex-full flex flex-column bg-grey-0 bordered drag-disabled",
+                "full flex-full flex flex-column bg-light bordered drag-disabled",
                 styles["text-card-textarea"],
               )}
               name="text"
@@ -212,7 +212,7 @@ const TextActionButtons = ({
         <a
           data-metabase-event={"Dashboard;Text;edit"}
           className={cx(" cursor-pointer h3 flex-no-shrink relative mr1", {
-            "text-grey-2 text-grey-4-hover": isShowingRenderedOutput,
+            "text-light text-medium-hover": isShowingRenderedOutput,
             "text-brand": !isShowingRenderedOutput,
           })}
           onClick={onEdit}
@@ -232,7 +232,7 @@ const TextActionButtons = ({
         <a
           data-metabase-event={"Dashboard;Text;preview"}
           className={cx(" cursor-pointer h3 flex-no-shrink relative mr1", {
-            "text-grey-2 text-grey-4-hover": !isShowingRenderedOutput,
+            "text-light text-medium-hover": !isShowingRenderedOutput,
             "text-brand": isShowingRenderedOutput,
           })}
           onClick={onPreview}
diff --git a/frontend/test/.eslintrc b/frontend/test/.eslintrc
index 61e755e9d565102e72622a488abeed085ff4f2ad..1cd6314fbfd1f8f7a14d53b2058bbc8115419913 100644
--- a/frontend/test/.eslintrc
+++ b/frontend/test/.eslintrc
@@ -2,7 +2,8 @@
     "rules": {
         "jasmine/no-focused-tests": 2,
         "jasmine/no-suite-dupes": [2, "branch"],
-        "import/no-commonjs": 0
+        "import/no-commonjs": 0,
+        "no-color-literals": 0
     },
     "env": {
         "jasmine": true,
diff --git a/frontend/test/__runner__/backend.js b/frontend/test/__runner__/backend.js
index 42b1f0b0b406eb2a3137efaa597277a2d34d2611..9cd0b5e615a81960c0e84ec743c5144c139db343 100644
--- a/frontend/test/__runner__/backend.js
+++ b/frontend/test/__runner__/backend.js
@@ -54,11 +54,13 @@ export const BackendResource = createSharedResource("BackendResource", {
           "-Xverify:none", // Skip bytecode verification for the JAR so it launches faster
           "-Djava.awt.headless=true", // when running on macOS prevent little Java icon from popping up in Dock
           "--add-modules=java.xml.bind", // Tell Java 9 we want to use java.xml stuff
+          "-Duser.timezone=US/Pacific",
           "-jar",
           "target/uberjar/metabase.jar",
         ],
         {
           env: {
+            MB_DB_TYPE: "h2",
             MB_DB_FILE: server.dbFile,
             MB_JETTY_PORT: server.port,
           },
diff --git a/frontend/test/__support__/integrated_tests.js b/frontend/test/__support__/integrated_tests.js
index 754f82dea9e16291e9c51992fe15e65f3993635b..796edc2e1539806cb88b1c9c3a95ff2a9c1a271c 100644
--- a/frontend/test/__support__/integrated_tests.js
+++ b/frontend/test/__support__/integrated_tests.js
@@ -55,18 +55,8 @@ let simulateOfflineMode = false;
 let apiRequestCompletedCallback = null;
 let skippedApiRequests = [];
 
-// These i18n settings are same is beginning of app.js
-
-// make the i18n function "t" global so we don't have to import it in basically every file
-import { t, jt } from "c-3po";
-global.t = t;
-global.jt = jt;
-
-// set the locale before loading anything else
-import { setLocalization } from "metabase/lib/i18n";
-if (window.MetabaseLocalization) {
-  setLocalization(window.MetabaseLocalization);
-}
+// load files that are loaded at the top if app.js
+import "metabase/lib/i18n";
 
 const warnAboutCreatingStoreBeforeLogin = () => {
   if (!loginSession && hasStartedCreatingStore) {
@@ -594,6 +584,7 @@ cleanup.fn = action => cleanup.actions.push(action);
 cleanup.metric = metric => cleanup.fn(() => deleteMetric(metric));
 cleanup.segment = segment => cleanup.fn(() => deleteSegment(segment));
 cleanup.question = question => cleanup.fn(() => deleteQuestion(question));
+cleanup.collection = c => cleanup.fn(() => deleteCollection(c));
 
 export const deleteQuestion = question =>
   CardApi.delete({ cardId: getId(question) });
@@ -601,6 +592,8 @@ export const deleteSegment = segment =>
   SegmentApi.delete({ segmentId: getId(segment), revision_message: "Please" });
 export const deleteMetric = metric =>
   MetricApi.delete({ metricId: getId(metric), revision_message: "Please" });
+export const deleteCollection = collection =>
+  CollectionsApi.update({ id: getId(collection), archived: true });
 
 const getId = o =>
   typeof o === "object" && o != null
@@ -644,6 +637,14 @@ api._makeRequest = async (method, url, headers, requestBody, data, options) => {
       ? { status: 0, responseText: "" }
       : await fetch(api.basename + url, fetchOptions);
 
+    if (!window.document) {
+      console.warn(
+        "API request completed after test ended. Ignoring result.",
+        url,
+      );
+      return;
+    }
+
     if (isCancelled) {
       throw { status: 0, data: "", isCancelled: true };
     }
@@ -686,6 +687,16 @@ api._makeRequest = async (method, url, headers, requestBody, data, options) => {
 
       throw error;
     }
+  } catch (e) {
+    if (!window.document) {
+      console.warn(
+        "API request failed after test ended. Ignoring result.",
+        url,
+        e,
+      );
+      return;
+    }
+    throw e;
   } finally {
     pendingRequests--;
     if (pendingRequests === 0 && pendingRequestsDeferred) {
diff --git a/frontend/test/__support__/sample_dataset_fixture.js b/frontend/test/__support__/sample_dataset_fixture.js
index 4c5e71f5501b91b5e601df3b2d036e72a239097b..d6a2ba49c5f17e6121e4d4c6d3f8246424a98687 100644
--- a/frontend/test/__support__/sample_dataset_fixture.js
+++ b/frontend/test/__support__/sample_dataset_fixture.js
@@ -38,7 +38,7 @@ export const state = {
         table_id: 1,
         definition: {
           aggregation: [["sum", ["field-id", 6]]],
-          source_table: 1,
+          "source-table": 1,
         },
         creator: {
           email: "sameer@metabase.com",
@@ -70,7 +70,7 @@ export const state = {
         table_id: 1,
         definition: {
           filter: [">", ["field-id", 6], 30],
-          source_table: 1,
+          "source-table": 1,
         },
         creator: {
           email: "sameer@metabase.com",
@@ -171,7 +171,6 @@ export const state = {
         description: "This is a confirmed order for a product from a user.",
         entity_type: null,
         schema: "PUBLIC",
-        raw_table_id: 2,
         show_in_getting_started: false,
         name: "ORDERS",
         caveats: null,
@@ -196,7 +195,6 @@ export const state = {
           "This is a user account. Note that employees and customer support staff will have accounts.",
         entity_type: null,
         schema: "PUBLIC",
-        raw_table_id: 3,
         show_in_getting_started: false,
         name: "PEOPLE",
         caveats: null,
@@ -287,7 +285,6 @@ export const state = {
           "This is our product catalog. It includes all products ever sold by the Sample Company.",
         entity_type: null,
         schema: "PUBLIC",
-        raw_table_id: 1,
         show_in_getting_started: false,
         name: "PRODUCTS",
         caveats: null,
@@ -342,7 +339,6 @@ export const state = {
           "These are reviews our customers have left on products. Note that these are not tied to orders so it is possible people have reviewed products they did not purchase from us.",
         entity_type: null,
         schema: "PUBLIC",
-        raw_table_id: 5,
         show_in_getting_started: false,
         name: "REVIEWS",
         caveats: null,
@@ -373,7 +369,6 @@ export const state = {
         active: true,
         parent_id: null,
         id: 1,
-        raw_column_id: 9,
         last_analyzed: "2017-06-14T23:22:56.832Z",
         position: 0,
         visibility_type: "normal",
@@ -397,7 +392,6 @@ export const state = {
         active: true,
         parent_id: null,
         id: 2,
-        raw_column_id: 10,
         last_analyzed: "2017-06-14T23:22:56.832Z",
         position: 0,
         visibility_type: "normal",
@@ -421,7 +415,6 @@ export const state = {
         active: true,
         parent_id: null,
         id: 3,
-        raw_column_id: 11,
         last_analyzed: "2017-06-14T23:22:56.832Z",
         position: 0,
         visibility_type: "normal",
@@ -445,7 +438,6 @@ export const state = {
         active: true,
         parent_id: null,
         id: 4,
-        raw_column_id: 12,
         last_analyzed: "2017-06-14T23:22:56.832Z",
         position: 0,
         visibility_type: "normal",
@@ -476,7 +468,6 @@ export const state = {
         active: true,
         parent_id: null,
         id: 5,
-        raw_column_id: 13,
         last_analyzed: "2017-06-14T23:22:56.832Z",
         position: 0,
         visibility_type: "normal",
@@ -499,7 +490,6 @@ export const state = {
         active: true,
         parent_id: null,
         id: 6,
-        raw_column_id: 14,
         last_analyzed: "2017-06-14T23:22:56.832Z",
         position: 0,
         visibility_type: "normal",
@@ -523,7 +513,6 @@ export const state = {
         active: true,
         parent_id: null,
         id: 7,
-        raw_column_id: 15,
         last_analyzed: "2017-06-14T23:22:56.832Z",
         position: 0,
         visibility_type: "normal",
@@ -546,7 +535,6 @@ export const state = {
         active: true,
         parent_id: null,
         id: 8,
-        raw_column_id: 16,
         last_analyzed: "2017-06-14T23:22:57.670Z",
         position: 0,
         visibility_type: "normal",
@@ -569,7 +557,6 @@ export const state = {
         active: true,
         parent_id: null,
         id: 9,
-        raw_column_id: 17,
         last_analyzed: "2017-06-14T23:22:57.670Z",
         position: 0,
         visibility_type: "normal",
@@ -592,7 +579,6 @@ export const state = {
         active: true,
         parent_id: null,
         id: 10,
-        raw_column_id: 18,
         last_analyzed: "2017-06-14T23:22:57.670Z",
         position: 0,
         visibility_type: "normal",
@@ -616,7 +602,6 @@ export const state = {
         active: true,
         parent_id: null,
         id: 11,
-        raw_column_id: 19,
         last_analyzed: "2017-06-14T23:22:57.670Z",
         position: 0,
         visibility_type: "normal",
@@ -639,7 +624,6 @@ export const state = {
         active: true,
         parent_id: null,
         id: 12,
-        raw_column_id: 20,
         last_analyzed: "2017-06-14T23:22:57.670Z",
         position: 0,
         visibility_type: "normal",
@@ -662,7 +646,6 @@ export const state = {
         active: true,
         parent_id: null,
         id: 13,
-        raw_column_id: 21,
         last_analyzed: "2017-06-14T23:22:57.670Z",
         position: 0,
         visibility_type: "normal",
@@ -686,7 +669,6 @@ export const state = {
         active: true,
         parent_id: null,
         id: 14,
-        raw_column_id: 22,
         last_analyzed: "2017-06-14T23:22:57.670Z",
         position: 0,
         visibility_type: "normal",
@@ -710,7 +692,6 @@ export const state = {
         active: true,
         parent_id: null,
         id: 15,
-        raw_column_id: 23,
         last_analyzed: "2017-06-14T23:22:57.670Z",
         position: 0,
         visibility_type: "normal",
@@ -733,7 +714,6 @@ export const state = {
         active: true,
         parent_id: null,
         id: 16,
-        raw_column_id: 24,
         last_analyzed: "2017-06-14T23:22:57.670Z",
         position: 0,
         visibility_type: "normal",
@@ -757,7 +737,6 @@ export const state = {
         active: true,
         parent_id: null,
         id: 17,
-        raw_column_id: 25,
         last_analyzed: "2017-06-14T23:22:57.670Z",
         position: 0,
         visibility_type: "normal",
@@ -781,7 +760,6 @@ export const state = {
         active: true,
         parent_id: null,
         id: 18,
-        raw_column_id: 26,
         last_analyzed: "2017-06-14T23:22:57.670Z",
         position: 0,
         visibility_type: "normal",
@@ -811,7 +789,6 @@ export const state = {
         active: true,
         parent_id: null,
         id: 19,
-        raw_column_id: 27,
         last_analyzed: "2017-06-14T23:22:57.670Z",
         position: 0,
         visibility_type: "normal",
@@ -904,7 +881,6 @@ export const state = {
         active: true,
         parent_id: null,
         id: 20,
-        raw_column_id: 28,
         last_analyzed: "2017-06-14T23:22:57.670Z",
         position: 0,
         visibility_type: "normal",
@@ -928,7 +904,6 @@ export const state = {
         active: true,
         parent_id: null,
         id: 21,
-        raw_column_id: 1,
         last_analyzed: "2017-06-14T23:22:57.771Z",
         position: 0,
         visibility_type: "normal",
@@ -959,7 +934,6 @@ export const state = {
         active: true,
         parent_id: null,
         id: 22,
-        raw_column_id: 2,
         last_analyzed: "2017-06-14T23:22:57.771Z",
         position: 0,
         visibility_type: "normal",
@@ -983,7 +957,6 @@ export const state = {
         active: true,
         parent_id: null,
         id: 23,
-        raw_column_id: 3,
         last_analyzed: "2017-06-14T23:22:57.771Z",
         position: 0,
         visibility_type: "normal",
@@ -1014,7 +987,6 @@ export const state = {
         active: true,
         parent_id: null,
         id: 24,
-        raw_column_id: 4,
         last_analyzed: "2017-06-14T23:22:57.771Z",
         position: 0,
         visibility_type: "normal",
@@ -1038,7 +1010,6 @@ export const state = {
         active: true,
         parent_id: null,
         id: 25,
-        raw_column_id: 5,
         last_analyzed: "2017-06-14T23:22:57.771Z",
         position: 0,
         visibility_type: "normal",
@@ -1069,7 +1040,6 @@ export const state = {
         active: true,
         parent_id: null,
         id: 26,
-        raw_column_id: 6,
         last_analyzed: "2017-06-14T23:22:57.771Z",
         position: 0,
         visibility_type: "normal",
@@ -1126,7 +1096,6 @@ export const state = {
         active: true,
         parent_id: null,
         id: 27,
-        raw_column_id: 7,
         last_analyzed: "2017-06-14T23:22:57.771Z",
         position: 0,
         visibility_type: "normal",
@@ -1156,7 +1125,6 @@ export const state = {
         active: true,
         parent_id: null,
         id: 28,
-        raw_column_id: 8,
         last_analyzed: "2017-06-14T23:22:57.771Z",
         position: 0,
         visibility_type: "normal",
@@ -1187,7 +1155,6 @@ export const state = {
         parent_id: null,
         id: 29,
         values: [],
-        raw_column_id: 31,
         last_analyzed: "2017-06-14T23:22:58.030Z",
         position: 0,
         visibility_type: "normal",
@@ -1210,7 +1177,6 @@ export const state = {
         parent_id: null,
         id: 30,
         values: [],
-        raw_column_id: 32,
         last_analyzed: "2017-06-14T23:22:58.030Z",
         position: 0,
         visibility_type: "normal",
@@ -1234,7 +1200,6 @@ export const state = {
         parent_id: null,
         id: 31,
         values: [],
-        raw_column_id: 33,
         last_analyzed: "2017-06-14T23:22:58.030Z",
         position: 0,
         visibility_type: "normal",
@@ -1257,7 +1222,6 @@ export const state = {
         parent_id: null,
         id: 32,
         values: [],
-        raw_column_id: 34,
         last_analyzed: "2017-06-14T23:22:58.030Z",
         position: 0,
         visibility_type: "normal",
@@ -1287,7 +1251,6 @@ export const state = {
           human_readable_values: {},
           field_id: 33,
         },
-        raw_column_id: 35,
         last_analyzed: "2017-06-14T23:22:58.030Z",
         position: 0,
         visibility_type: "normal",
@@ -1310,7 +1273,6 @@ export const state = {
         parent_id: null,
         id: 34,
         values: [],
-        raw_column_id: 36,
         last_analyzed: "2017-06-14T23:22:58.030Z",
         position: 0,
         visibility_type: "normal",
@@ -1336,7 +1298,7 @@ export const card = {
     type: "query",
     database: DATABASE_ID,
     query: {
-      source_table: ORDERS_TABLE_ID,
+      "source-table": ORDERS_TABLE_ID,
     },
   },
 };
@@ -1348,7 +1310,7 @@ export const product_card = {
     type: "query",
     database: DATABASE_ID,
     query: {
-      source_table: PRODUCT_TABLE_ID,
+      "source-table": PRODUCT_TABLE_ID,
     },
   },
 };
@@ -1363,7 +1325,7 @@ export const orders_raw_card = {
     type: "query",
     database: DATABASE_ID,
     query: {
-      source_table: ORDERS_TABLE_ID,
+      "source-table": ORDERS_TABLE_ID,
     },
   },
 };
@@ -1378,7 +1340,7 @@ export const orders_count_card = {
     database: DATABASE_ID,
     query: {
       aggregation: [["count"]],
-      source_table: ORDERS_TABLE_ID,
+      "source-table": ORDERS_TABLE_ID,
     },
   },
 };
@@ -1435,7 +1397,7 @@ export const orders_count_by_id_card = {
     database: DATABASE_ID,
     query: {
       aggregation: [["count"]],
-      source_table: ORDERS_TABLE_ID,
+      "source-table": ORDERS_TABLE_ID,
       breakout: [["field-id", ORDERS_PK_FIELD_ID]],
     },
   },
@@ -1522,7 +1484,7 @@ export const orders_past_300_days_segment = {
   description: "Past 300 days created at",
   table_id: 1,
   definition: {
-    source_table: 1,
+    "source-table": 1,
     filter: ["time-interval", ["field-id", 1], -300, "day"],
   },
 };
@@ -1534,7 +1496,7 @@ export const vendor_count_metric = {
   table_id: 3,
   definition: {
     aggregation: [["distinct", ["field-id", 28]]],
-    source_table: 3,
+    "source-table": 3,
   },
 };
 
diff --git a/frontend/test/admin/databases/DatabaseEditApp.integ.spec.js b/frontend/test/admin/databases/DatabaseEditApp.integ.spec.js
index c451d0a729551023461e1e7352a96852cd192dd4..9fd0f52ca8ead3b5586de79c8fd626b2eedecfa7 100644
--- a/frontend/test/admin/databases/DatabaseEditApp.integ.spec.js
+++ b/frontend/test/admin/databases/DatabaseEditApp.integ.spec.js
@@ -14,9 +14,7 @@ import {
   MIGRATE_TO_NEW_SCHEDULING_SETTINGS,
   DEFAULT_SCHEDULES,
 } from "metabase/admin/databases/database";
-import DatabaseEditApp, {
-  Tab,
-} from "metabase/admin/databases/containers/DatabaseEditApp";
+import DatabaseEditApp from "metabase/admin/databases/containers/DatabaseEditApp";
 import DatabaseEditForms from "metabase/admin/databases/components/DatabaseEditForms";
 import DatabaseSchedulingForm, {
   SyncOption,
@@ -26,6 +24,7 @@ import Toggle from "metabase/components/Toggle";
 import { TestModal } from "metabase/components/Modal";
 import Select from "metabase/components/Select";
 import ColumnarSelector from "metabase/components/ColumnarSelector";
+import Radio from "metabase/components/Radio";
 import { click, clickButton } from "__support__/enzyme_utils";
 import { MetabaseApi } from "metabase/services";
 import _ from "underscore";
@@ -75,14 +74,14 @@ describe("DatabaseEditApp", () => {
       click(letUserControlSchedulingField.find(Toggle));
 
       // Connection and Scheduling tabs shouldn't be visible yet
-      expect(dbEditApp.find(Tab).length).toBe(0);
+      expect(dbEditApp.find(Radio).find("li").length).toBe(0);
 
       clickButton(editForm.find('button[children="Save"]'));
 
       await store.waitForActions([UPDATE_DATABASE]);
 
       // Tabs should be now visible as user-controlled scheduling is enabled
-      expect(dbEditApp.find(Tab).length).toBe(2);
+      expect(dbEditApp.find(Radio).find("li").length).toBe(2);
     });
 
     // NOTE Atte Keinänen 8/17/17: See migrateDatabaseToNewSchedulingSettings for more information about migration process
@@ -122,7 +121,7 @@ describe("DatabaseEditApp", () => {
       expect(letUserControlSchedulingField.find(Toggle).props().value).toBe(
         false,
       );
-      expect(dbEditApp.find(Tab).length).toBe(0);
+      expect(dbEditApp.find(Radio).find("li").length).toBe(0);
     });
 
     it("shows the analysis toggle correctly for non-migrated analysis settings when `is_full_sync` is false", async () => {
@@ -153,7 +152,7 @@ describe("DatabaseEditApp", () => {
       expect(letUserControlSchedulingField.find(Toggle).props().value).toBe(
         true,
       );
-      expect(dbEditApp.find(Tab).length).toBe(2);
+      expect(dbEditApp.find(Radio).find("li").length).toBe(2);
     });
 
     afterAll(async () => {
@@ -192,7 +191,12 @@ describe("DatabaseEditApp", () => {
 
       const editForm = dbEditApp.find(DatabaseEditForms);
       expect(editForm.length).toBe(1);
-      click(dbEditApp.find(Tab).last());
+      click(
+        dbEditApp
+          .find(Radio)
+          .find("li")
+          .last(),
+      );
 
       const schedulingForm = dbEditApp.find(DatabaseSchedulingForm);
       expect(schedulingForm.length).toBe(1);
@@ -218,7 +222,12 @@ describe("DatabaseEditApp", () => {
       const dbEditApp = mount(store.connectContainer(<DatabaseEditApp />));
       await store.waitForActions([INITIALIZE_DATABASE]);
 
-      click(dbEditApp.find(Tab).last());
+      click(
+        dbEditApp
+          .find(Radio)
+          .find("li")
+          .last(),
+      );
       const schedulingForm = dbEditApp.find(DatabaseSchedulingForm);
       const dbSyncSelect = schedulingForm.find(Select).first();
       click(dbSyncSelect);
@@ -244,7 +253,12 @@ describe("DatabaseEditApp", () => {
       const dbEditApp = mount(store.connectContainer(<DatabaseEditApp />));
       await store.waitForActions([INITIALIZE_DATABASE]);
 
-      click(dbEditApp.find(Tab).last());
+      click(
+        dbEditApp
+          .find(Radio)
+          .find("li")
+          .last(),
+      );
       const schedulingForm = dbEditApp.find(DatabaseSchedulingForm);
       const dbSyncSelect = schedulingForm.find(Select).first();
       click(dbSyncSelect);
@@ -266,7 +280,12 @@ describe("DatabaseEditApp", () => {
       const dbEditApp = mount(store.connectContainer(<DatabaseEditApp />));
       await store.waitForActions([INITIALIZE_DATABASE]);
 
-      click(dbEditApp.find(Tab).last());
+      click(
+        dbEditApp
+          .find(Radio)
+          .find("li")
+          .last(),
+      );
       const schedulingForm = dbEditApp.find(DatabaseSchedulingForm);
       expect(schedulingForm.length).toBe(1);
 
diff --git a/frontend/test/admin/databases/DatabaseListApp.integ.spec.js b/frontend/test/admin/databases/DatabaseListApp.integ.spec.js
index c5cbd5e9c69bbb36cdbdf52ca4e67516280e1315..1fba641a1a4bdaeb5c1bb14271f5717c63a16c6b 100644
--- a/frontend/test/admin/databases/DatabaseListApp.integ.spec.js
+++ b/frontend/test/admin/databases/DatabaseListApp.integ.spec.js
@@ -1,19 +1,17 @@
 import {
   useSharedAdminLogin,
   createTestStore,
+  eventually,
 } from "__support__/integrated_tests";
 import { click, clickButton, setInputValue } from "__support__/enzyme_utils";
 
 import { mount } from "enzyme";
 import {
-  FETCH_DATABASES,
   initializeDatabase,
   INITIALIZE_DATABASE,
   DELETE_DATABASE_FAILED,
-  DELETE_DATABASE,
   CREATE_DATABASE_STARTED,
   CREATE_DATABASE_FAILED,
-  CREATE_DATABASE,
   UPDATE_DATABASE_STARTED,
   UPDATE_DATABASE_FAILED,
   UPDATE_DATABASE,
@@ -38,6 +36,8 @@ import DatabaseSchedulingForm, {
   SyncOption,
 } from "metabase/admin/databases/components/DatabaseSchedulingForm";
 
+import Databases from "metabase/entities/databases";
+
 describe("dashboard list", () => {
   beforeAll(async () => {
     useSharedAdminLogin();
@@ -49,15 +49,14 @@ describe("dashboard list", () => {
 
     const app = mount(store.getAppContainer());
 
-    await store.waitForActions([FETCH_DATABASES]);
+    await store.waitForActions([Databases.actionTypes.FETCH_LIST]);
 
-    const wrapper = app.find(DatabaseListApp);
-    expect(wrapper.length).toEqual(1);
+    expect(app.find(DatabaseListApp).length).toEqual(1);
   });
 
   describe("adds", () => {
     it("should work and shouldn't let you accidentally add db twice", async () => {
-      MetabaseApi.db_create = async db => {
+      Databases.api.create = async db => {
         await delay(10);
         return { ...db, id: 10 };
       };
@@ -66,14 +65,10 @@ describe("dashboard list", () => {
       store.pushPath("/admin/databases");
 
       const app = mount(store.getAppContainer());
-      await store.waitForActions([FETCH_DATABASES]);
-
-      const listAppBeforeAdd = app.find(DatabaseListApp);
 
-      const addDbButton = listAppBeforeAdd
-        .find(".Button.Button--primary")
-        .first();
-      click(addDbButton);
+      await eventually(() => {
+        click(app.find(".Button.Button--primary").first());
+      });
 
       const dbDetailsForm = app.find(DatabaseEditApp);
       expect(dbDetailsForm.length).toBe(1);
@@ -101,14 +96,14 @@ describe("dashboard list", () => {
       expect(saveButton.text()).toBe("Saving...");
       expect(saveButton.props().disabled).toBe(true);
 
-      await store.waitForActions([CREATE_DATABASE]);
-
-      expect(store.getPath()).toEqual("/admin/databases?created=10");
+      await eventually(() =>
+        expect(store.getPath()).toEqual("/admin/databases?created=10"),
+      );
       expect(app.find(CreatedDatabaseModal).length).toBe(1);
     });
 
     it("should show validation error if you enable scheduling toggle and enter invalid db connection info", async () => {
-      MetabaseApi.db_create = async db => {
+      Databases.api.create = async db => {
         await delay(10);
         return { ...db, id: 10 };
       };
@@ -117,14 +112,10 @@ describe("dashboard list", () => {
       store.pushPath("/admin/databases");
 
       const app = mount(store.getAppContainer());
-      await store.waitForActions([FETCH_DATABASES]);
-
-      const listAppBeforeAdd = app.find(DatabaseListApp);
 
-      const addDbButton = listAppBeforeAdd
-        .find(".Button.Button--primary")
-        .first();
-      click(addDbButton);
+      await eventually(() => {
+        click(app.find(".Button.Button--primary").first());
+      });
 
       const dbDetailsForm = app.find(DatabaseEditApp);
       expect(dbDetailsForm.length).toBe(1);
@@ -167,7 +158,7 @@ describe("dashboard list", () => {
     });
 
     it("should direct you to scheduling settings if you enable the toggle", async () => {
-      MetabaseApi.db_create = async db => {
+      Databases.api.create = async db => {
         await delay(10);
         return { ...db, id: 10 };
       };
@@ -183,14 +174,11 @@ describe("dashboard list", () => {
       store.pushPath("/admin/databases");
 
       const app = mount(store.getAppContainer());
-      await store.waitForActions([FETCH_DATABASES]);
-
-      const listAppBeforeAdd = app.find(DatabaseListApp);
+      await store.waitForActions([Databases.actionTypes.FETCH_LIST]);
 
-      const addDbButton = listAppBeforeAdd
-        .find(".Button.Button--primary")
-        .first();
-      click(addDbButton);
+      await eventually(() => {
+        click(app.find(".Button.Button--primary").first());
+      });
 
       const dbDetailsForm = app.find(DatabaseEditApp);
       expect(dbDetailsForm.length).toBe(1);
@@ -245,14 +233,15 @@ describe("dashboard list", () => {
       await store.waitForActions([CREATE_DATABASE_STARTED]);
       expect(saveButton.text()).toBe("Saving...");
 
-      await store.waitForActions([CREATE_DATABASE]);
+      await eventually(() =>
+        expect(store.getPath()).toEqual("/admin/databases?created=10"),
+      );
 
-      expect(store.getPath()).toEqual("/admin/databases?created=10");
       expect(app.find(CreatedDatabaseModal).length).toBe(1);
     });
 
     it("should show error correctly on failure", async () => {
-      MetabaseApi.db_create = async () => {
+      Databases.api.create = async () => {
         await delay(10);
         return Promise.reject({
           status: 400,
@@ -265,15 +254,12 @@ describe("dashboard list", () => {
       store.pushPath("/admin/databases");
 
       const app = mount(store.getAppContainer());
-      await store.waitForActions([FETCH_DATABASES]);
-
-      const listAppBeforeAdd = app.find(DatabaseListApp);
-
-      const addDbButton = listAppBeforeAdd
-        .find(".Button.Button--primary")
-        .first();
 
-      click(addDbButton); // ROUTER LINK
+      await eventually(() => {
+        const addDbButton = app.find(".Button.Button--primary").first();
+        expect(addDbButton).not.toBe(null);
+        click(addDbButton);
+      });
 
       const dbDetailsForm = app.find(DatabaseEditApp);
       expect(dbDetailsForm.length).toBe(1);
@@ -308,43 +294,46 @@ describe("dashboard list", () => {
 
   describe("deletes", () => {
     it("should not block deletes", async () => {
-      MetabaseApi.db_delete = async () => await delay(10);
+      Databases.api.delete = async () => {
+        await delay(10);
+      };
 
       const store = await createTestStore();
       store.pushPath("/admin/databases");
 
       const app = mount(store.getAppContainer());
-      await store.waitForActions([FETCH_DATABASES]);
 
-      const wrapper = app.find(DatabaseListApp);
-      const dbCount = wrapper.find("tr").length;
-
-      const deleteButton = wrapper.find(".Button.Button--danger").first();
+      let deleteButtons;
+      await eventually(() => {
+        deleteButtons = app.find(".Button.Button--danger");
+        expect(deleteButtons).not.toHaveLength(0);
+      });
 
-      click(deleteButton);
+      // let dbCount = deleteButtons.length;
+      click(deleteButtons.first());
 
-      const deleteModal = wrapper.find(".test-modal");
+      const deleteModal = app.find(".test-modal");
       setInputValue(deleteModal.find(".Form-input"), "DELETE");
       clickButton(deleteModal.find(".Button.Button--danger"));
 
       // test that the modal is gone
-      expect(wrapper.find(".test-modal").length).toEqual(0);
+      expect(app.find(".test-modal").length).toEqual(0);
 
       // we should now have a disabled db row during delete
-      expect(wrapper.find("tr.disabled").length).toEqual(1);
+      expect(app.find("tr.disabled").length).toEqual(1);
 
-      // db delete finishes
-      await store.waitForActions([DELETE_DATABASE]);
+      await eventually(() => {
+        // there should be no disabled db rows now
+        expect(app.find("tr.disabled").length).toEqual(0);
 
-      // there should be no disabled db rows now
-      expect(wrapper.find("tr.disabled").length).toEqual(0);
-
-      // we should now have one database less in the list
-      expect(wrapper.find("tr").length).toEqual(dbCount - 1);
+        // we should now have one database less in the list
+        // NOTE: unsure why the delete button is still present, it is not during manual testing
+        // expect(app.find(".Button.Button--danger").length).toEqual(dbCount - 1);
+      });
     });
 
     it("should show error correctly on failure", async () => {
-      MetabaseApi.db_delete = async () => {
+      Databases.api.delete = async () => {
         await delay(10);
         return Promise.reject({
           status: 400,
@@ -357,35 +346,36 @@ describe("dashboard list", () => {
       store.pushPath("/admin/databases");
 
       const app = mount(store.getAppContainer());
-      await store.waitForActions([FETCH_DATABASES]);
-
-      const wrapper = app.find(DatabaseListApp);
-      const dbCount = wrapper.find("tr").length;
 
-      const deleteButton = wrapper.find(".Button.Button--danger").first();
-      click(deleteButton);
+      let deleteButtons;
+      await eventually(() => {
+        deleteButtons = app.find(".Button.Button--danger");
+        expect(deleteButtons).not.toHaveLength(0);
+      });
 
-      const deleteModal = wrapper.find(".test-modal");
+      let dbCount = deleteButtons.length;
+      click(deleteButtons.first());
 
+      const deleteModal = app.find(".test-modal");
       setInputValue(deleteModal.find(".Form-input"), "DELETE");
       clickButton(deleteModal.find(".Button.Button--danger"));
 
       // test that the modal is gone
-      expect(wrapper.find(".test-modal").length).toEqual(0);
+      expect(app.find(".test-modal").length).toEqual(0);
 
       // we should now have a disabled db row during delete
-      expect(wrapper.find("tr.disabled").length).toEqual(1);
+      expect(app.find("tr.disabled").length).toEqual(1);
 
       // db delete fails
       await store.waitForActions([DELETE_DATABASE_FAILED]);
 
       // there should be no disabled db rows now
-      expect(wrapper.find("tr.disabled").length).toEqual(0);
+      expect(app.find("tr.disabled").length).toEqual(0);
 
       // the db count should be same as before
-      expect(wrapper.find("tr").length).toEqual(dbCount);
+      expect(app.find(".Button.Button--danger")).toHaveLength(dbCount);
 
-      expect(wrapper.find(FormMessage).text()).toBe(SERVER_ERROR_MESSAGE);
+      expect(app.find(FormMessage).text()).toBe(SERVER_ERROR_MESSAGE);
     });
   });
 
@@ -397,13 +387,11 @@ describe("dashboard list", () => {
       store.pushPath("/admin/databases");
 
       const app = mount(store.getAppContainer());
-      await store.waitForActions([FETCH_DATABASES]);
+      await store.waitForActions([Databases.actionTypes.FETCH_LIST]);
 
-      const wrapper = app.find(DatabaseListApp);
-      const sampleDatasetEditLink = wrapper
-        .find('a[children="Sample Dataset"]')
-        .first();
-      click(sampleDatasetEditLink); // ROUTER LINK
+      await eventually(() =>
+        click(app.find('a[children="Sample Dataset"]').first()),
+      );
 
       expect(store.getPath()).toEqual("/admin/databases/1");
       await store.waitForActions([INITIALIZE_DATABASE]);
diff --git a/frontend/test/alert/alert.integ.spec.js b/frontend/test/alert/alert.integ.spec.js
index 583bf029b1457a2576e44c351625ab68676e9c5f..1cc1c4c7597fced57d3f0341d2c96d8c7173b60f 100644
--- a/frontend/test/alert/alert.integ.spec.js
+++ b/frontend/test/alert/alert.integ.spec.js
@@ -44,7 +44,7 @@ import {
 import MetabaseCookies from "metabase/lib/cookies";
 import Radio from "metabase/components/Radio";
 import { getQuestionAlerts } from "metabase/query_builder/selectors";
-import { FETCH_PULSE_FORM_INPUT, FETCH_USERS } from "metabase/pulse/actions";
+import { FETCH_PULSE_FORM_INPUT } from "metabase/pulse/actions";
 import ChannelSetupModal from "metabase/components/ChannelSetupModal";
 import { getDefaultAlert } from "metabase-lib/lib/Alert";
 import { getMetadata } from "metabase/selectors/metadata";
@@ -53,6 +53,8 @@ import {
   AlertListPopoverContent,
 } from "metabase/query_builder/components/AlertListPopoverContent";
 
+import Users from "metabase/entities/users";
+
 async function removeAllCreatedAlerts() {
   useSharedAdminLogin();
   const alerts = await AlertApi.list();
@@ -482,7 +484,10 @@ describe("Alerts", () => {
         const editingScreen = app.find(UpdateAlertModalContent);
         expect(editingScreen.length).toBe(1);
 
-        await store.waitForActions([FETCH_USERS, FETCH_PULSE_FORM_INPUT]);
+        await store.waitForActions([
+          Users.actionTypes.FETCH_LIST,
+          FETCH_PULSE_FORM_INPUT,
+        ]);
 
         const toggles = editingScreen.find(AlertSettingToggle);
         const aboveGoalToggle = toggles.at(0);
diff --git a/frontend/test/collection/initial_collection.integ.spec.js b/frontend/test/collection/initial_collection.integ.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..b296b7a4592151778d3ab17ea22d2836575e8f97
--- /dev/null
+++ b/frontend/test/collection/initial_collection.integ.spec.js
@@ -0,0 +1,95 @@
+import {
+  createTestStore,
+  createAllUsersWritableCollection,
+  useSharedAdminLogin,
+  useSharedNormalLogin,
+  eventually,
+  cleanup,
+} from "__support__/integrated_tests";
+
+import { mount } from "enzyme";
+
+const ROOT_COLLECTION_NAME = "Our analytics";
+const NORMAL_USER_COLLECTION_NAME = "Robert Charts's Personal Collection";
+
+describe("initial collection id", () => {
+  let collection;
+  let app, store;
+
+  beforeAll(async () => {
+    useSharedAdminLogin();
+    collection = await createAllUsersWritableCollection();
+    cleanup.collection(collection);
+  });
+  afterAll(cleanup);
+
+  describe("for admins", () => {
+    beforeEach(async () => {
+      useSharedAdminLogin();
+      store = await createTestStore();
+      app = mount(store.getAppContainer());
+    });
+
+    describe("a new collection", () => {
+      it("should be the parent collection", async () => {
+        store.pushPath(`/collection/${collection.id}/new_collection`);
+        await assertInitialCollection(app, collection.name);
+      });
+    });
+    describe("a new pulse", () => {
+      it("should be the root collection", async () => {
+        store.pushPath("/pulse/create");
+        await assertInitialCollection(app, ROOT_COLLECTION_NAME);
+      });
+    });
+    describe("a new dashboard", () => {
+      it("should be the root collection", async () => {
+        store.pushPath("/");
+        await clickNewDashboard(app);
+        await assertInitialCollection(app, ROOT_COLLECTION_NAME);
+      });
+    });
+  });
+
+  describe("for non-admins", () => {
+    beforeEach(async () => {
+      useSharedNormalLogin();
+      store = await createTestStore();
+      app = mount(store.getAppContainer());
+    });
+
+    describe("a new pulse", () => {
+      it("should be the personal collection", async () => {
+        store.pushPath("/pulse/create");
+        await assertInitialCollection(app, NORMAL_USER_COLLECTION_NAME);
+      });
+    });
+    describe("a new dashboard", () => {
+      it("should be the personal collection", async () => {
+        store.pushPath("/");
+        await clickNewDashboard(app);
+        await assertInitialCollection(app, NORMAL_USER_COLLECTION_NAME);
+      });
+    });
+  });
+});
+
+const clickNewDashboard = app =>
+  eventually(() => {
+    app
+      .find("Navbar")
+      .find("EntityMenu")
+      .first()
+      .props()
+      .items[0].action();
+  });
+
+const assertInitialCollection = (app, collectionName) =>
+  eventually(() => {
+    expect(
+      app
+        .find(".AdminSelect")
+        .first()
+        .text(),
+    ).toBe(collectionName);
+  });
diff --git a/frontend/test/containers/Overworld.unit.spec.js b/frontend/test/containers/Overworld.unit.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..8e505cbffdc9b4b8248cfbdd665346256d1a4ef9
--- /dev/null
+++ b/frontend/test/containers/Overworld.unit.spec.js
@@ -0,0 +1,38 @@
+import React from "react";
+import Icon from "metabase/components/Icon";
+
+import {
+  AdminPinMessage,
+  PIN_MESSAGE_STORAGE_KEY,
+} from "metabase/containers/Overworld";
+import { shallow } from "enzyme";
+
+describe("AdminPinMessage", () => {
+  beforeEach(() => {
+    localStorage.clear();
+  });
+  it("should show the admin pin message if the admin hasn't dismissed it", () => {
+    const wrapper = shallow(<AdminPinMessage />);
+
+    expect(wrapper.find(Icon).length).toBe(2);
+  });
+
+  it("should not show the message if the admin has dismissed it", () => {
+    localStorage.setItem(PIN_MESSAGE_STORAGE_KEY, "true");
+    const wrapper = shallow(<AdminPinMessage />);
+    expect(wrapper.getNode(0)).toBe(null);
+  });
+
+  it("should set the proper local storage key if the dismiss icon is clicked", () => {
+    const wrapper = shallow(<AdminPinMessage />);
+    const dismiss = wrapper.find(Icon).at(1);
+
+    dismiss.simulate("click");
+
+    expect(localStorage.setItem).toHaveBeenCalledWith(
+      PIN_MESSAGE_STORAGE_KEY,
+      "true",
+    );
+    expect(wrapper.getNode(0)).toBe(null);
+  });
+});
diff --git a/frontend/test/dashboard/DashCard.unit.spec.js b/frontend/test/dashboard/DashCard.unit.spec.js
index 6aaefd4b297592fba3545b7d0ecfc3bc115b6737..df5e710bf2e41ca5b0bb6c99b4ca1d6467be2ae4 100644
--- a/frontend/test/dashboard/DashCard.unit.spec.js
+++ b/frontend/test/dashboard/DashCard.unit.spec.js
@@ -20,6 +20,9 @@ const DEFAULT_PROPS = {
   parameterValues: {},
   markNewCardSeen: () => {},
   fetchCardData: () => {},
+  dashboard: {
+    parameters: [],
+  },
 };
 
 describe("DashCard", () => {
diff --git a/frontend/test/dashboards/dashboards.integ.spec.js b/frontend/test/dashboards/dashboards.integ.spec.js
index ca7e2e0685c6aa435498cbf278afbc9c5a003ac0..c50fea4cfae9362194e7a56cb86a488bf9950bd0 100644
--- a/frontend/test/dashboards/dashboards.integ.spec.js
+++ b/frontend/test/dashboards/dashboards.integ.spec.js
@@ -14,7 +14,6 @@ import { DashboardApi } from "metabase/services";
 import SearchHeader from "metabase/components/SearchHeader";
 import EmptyState from "metabase/components/EmptyState";
 import Dashboard from "metabase/dashboard/components/Dashboard";
-import ListFilterWidget from "metabase/components/ListFilterWidget";
 import ArchivedItem from "metabase/components/ArchivedItem";
 
 /*
@@ -121,7 +120,6 @@ xdescribe("dashboards list", () => {
         .find(".Icon-staroutline"),
     );
     await store.waitForActions([Dashboards.actionTypes.UPDATE]);
-    click(app.find(ListFilterWidget));
 
     click(app.find(".TestPopover").find('h4[children="Favorites"]'));
 
diff --git a/frontend/test/internal/__snapshots__/components.unit.spec.js.snap b/frontend/test/internal/__snapshots__/components.unit.spec.js.snap
index ff624a8dc68cb0e3c558e2eb4f4c73ac2315497a..2adc6fb881dead92bdf143fe364bea6ca9ea211a 100644
--- a/frontend/test/internal/__snapshots__/components.unit.spec.js.snap
+++ b/frontend/test/internal/__snapshots__/components.unit.spec.js.snap
@@ -1,5 +1,41 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`Card should render "dark" correctly 1`] = `
+<div
+  className="Card-RQot fgzjUV"
+>
+  <div
+    className="p4"
+  >
+    Look, a card!
+  </div>
+</div>
+`;
+
+exports[`Card should render "hoverable" correctly 1`] = `
+<div
+  className="Card-RQot gCQWtx"
+>
+  <div
+    className="p4"
+  >
+    Look, a card!
+  </div>
+</div>
+`;
+
+exports[`Card should render "normal" correctly 1`] = `
+<div
+  className="Card-RQot krKrLM"
+>
+  <div
+    className="p4"
+  >
+    Look, a card!
+  </div>
+</div>
+`;
+
 exports[`CheckBox should render "Default - Off" correctly 1`] = `
 <div
   className="cursor-pointer"
@@ -10,7 +46,7 @@ exports[`CheckBox should render "Default - Off" correctly 1`] = `
     style={
       Object {
         "backgroundColor": "white",
-        "border": "2px solid #ddd",
+        "border": "2px solid #C7CFD4",
         "height": 16,
         "width": 16,
       }
@@ -180,8 +216,8 @@ exports[`CheckBox should render "Yellow" correctly 1`] = `
     className="flex align-center justify-center rounded"
     style={
       Object {
-        "backgroundColor": "#f9d45c",
-        "border": "2px solid #f9d45c",
+        "backgroundColor": "#F9D45C",
+        "border": "2px solid #F9D45C",
         "height": 16,
         "width": 16,
       }
@@ -220,11 +256,11 @@ exports[`EntityMenu should render "Edit menu" correctly 1`] = `
       className="relative"
     >
       <div
-        className="x0 x1 xk xl xm xn x2 xo xp xq xa xr xs"
+        className="Icon__IconWrapper-ksWXhn dnCxaL"
         onClick={[Function]}
       >
         <svg
-          className="Icon Icon-pencil m1 Icon-cxuQhR kTAgZA"
+          className="Icon Icon-pencil Icon-cxuQhR pXSiy"
           fill="currentcolor"
           height={16}
           name="pencil"
@@ -255,11 +291,11 @@ exports[`EntityMenu should render "More menu" correctly 1`] = `
       className="relative"
     >
       <div
-        className="x0 x1 xk xl xm xn x2 xo xp xq xa xr xs"
+        className="Icon__IconWrapper-ksWXhn dnCxaL"
         onClick={[Function]}
       >
         <svg
-          className="Icon Icon-burger m1 Icon-cxuQhR kTAgZA"
+          className="Icon Icon-burger Icon-cxuQhR pXSiy"
           fill="currentcolor"
           height={16}
           name="burger"
@@ -290,11 +326,11 @@ exports[`EntityMenu should render "Multiple menus" correctly 1`] = `
       className="relative"
     >
       <div
-        className="x0 x1 xk xl xm xn x2 xo xp xq xa xr xs"
+        className="Icon__IconWrapper-ksWXhn dnCxaL"
         onClick={[Function]}
       >
         <svg
-          className="Icon Icon-pencil m1 Icon-cxuQhR kTAgZA"
+          className="Icon Icon-pencil Icon-cxuQhR pXSiy"
           fill="currentcolor"
           height={16}
           name="pencil"
@@ -314,11 +350,11 @@ exports[`EntityMenu should render "Multiple menus" correctly 1`] = `
       className="relative"
     >
       <div
-        className="x0 x1 xk xl xm xn x2 xo xp xq xa xr xs"
+        className="Icon__IconWrapper-ksWXhn dnCxaL"
         onClick={[Function]}
       >
         <svg
-          className="Icon Icon-share m1 Icon-cxuQhR kTAgZA"
+          className="Icon Icon-share Icon-cxuQhR pXSiy"
           fill="currentcolor"
           fillRule="evenodd"
           height={20}
@@ -339,11 +375,11 @@ exports[`EntityMenu should render "Multiple menus" correctly 1`] = `
       className="relative"
     >
       <div
-        className="x0 x1 xk xl xm xn x2 xo xp xq xa xr xs"
+        className="Icon__IconWrapper-ksWXhn dnCxaL"
         onClick={[Function]}
       >
         <svg
-          className="Icon Icon-burger m1 Icon-cxuQhR kTAgZA"
+          className="Icon Icon-burger Icon-cxuQhR pXSiy"
           fill="currentcolor"
           height={16}
           name="burger"
@@ -374,11 +410,11 @@ exports[`EntityMenu should render "Share menu" correctly 1`] = `
       className="relative"
     >
       <div
-        className="x0 x1 xk xl xm xn x2 xo xp xq xa xr xs"
+        className="Icon__IconWrapper-ksWXhn dnCxaL"
         onClick={[Function]}
       >
         <svg
-          className="Icon Icon-share m1 Icon-cxuQhR kTAgZA"
+          className="Icon Icon-share Icon-cxuQhR pXSiy"
           fill="currentcolor"
           fillRule="evenodd"
           height={20}
@@ -422,20 +458,20 @@ exports[`Position should render "Relative" correctly 1`] = `
 
 exports[`ProgressBar should render "Animated" correctly 1`] = `
 <div
-  className="xt xu xv xn"
+  className="xk xl xm xn"
 >
   <div
-    className="xw xx xt xy xz x10 x11 x12 x13 x1d x1e x16 x17 x18 x1f x1a x1b x1g"
+    className="xo xp xk xq xr xs xt xu xv x15 x16 xy xz x10 x17 x12 x13 x18"
   />
 </div>
 `;
 
 exports[`ProgressBar should render "Default" correctly 1`] = `
 <div
-  className="xt xu xv xn"
+  className="xk xl xm xn"
 >
   <div
-    className="xw xx xt xy xz x10 x11 x12 x13 x14 x15 x16 x17 x18 x19 x1a x1b x1c"
+    className="xo xp xk xq xr xs xt xu xv xw xx xy xz x10 x11 x12 x13 x14"
   />
 </div>
 `;
@@ -445,8 +481,14 @@ exports[`Radio should render "default" correctly 1`] = `
   className="flex text-bold h3"
 >
   <li
-    className="flex align-center cursor-pointer mt1 mr2 text-brand-hover"
+    className="flex align-center cursor-pointer mr3 text-brand-hover pt1"
     onClick={[Function]}
+    style={
+      Object {
+        "borderBottom": undefined,
+        "borderColor": "transparent",
+      }
+    }
   >
     <input
       checked={true}
@@ -463,8 +505,14 @@ exports[`Radio should render "default" correctly 1`] = `
     </span>
   </li>
   <li
-    className="flex align-center cursor-pointer mt1 mr2 text-brand-hover"
+    className="flex align-center cursor-pointer mr3 text-brand-hover pt1"
     onClick={[Function]}
+    style={
+      Object {
+        "borderBottom": undefined,
+        "borderColor": "transparent",
+      }
+    }
   >
     <input
       checked={false}
@@ -488,19 +536,25 @@ exports[`Radio should render "show buttons" correctly 1`] = `
   className="flex"
 >
   <li
-    className="flex align-center cursor-pointer mt1 mr2"
+    className="flex align-center cursor-pointer mr3 pt1"
     onClick={[Function]}
+    style={
+      Object {
+        "borderBottom": undefined,
+        "borderColor": "transparent",
+      }
+    }
   >
     <input
       checked={true}
       className="Form-radio"
-      id="radio-2-0"
-      name="radio-2"
+      id="radio-3-0"
+      name="radio-3"
       type="radio"
       value={0}
     />
     <label
-      htmlFor="radio-2-0"
+      htmlFor="radio-3-0"
     />
     <span
       className="text-brand"
@@ -509,19 +563,80 @@ exports[`Radio should render "show buttons" correctly 1`] = `
     </span>
   </li>
   <li
-    className="flex align-center cursor-pointer mt1 mr2"
+    className="flex align-center cursor-pointer mr3 pt1"
     onClick={[Function]}
+    style={
+      Object {
+        "borderBottom": undefined,
+        "borderColor": "transparent",
+      }
+    }
   >
     <input
       checked={false}
       className="Form-radio"
-      id="radio-2-1"
-      name="radio-2"
+      id="radio-3-1"
+      name="radio-3"
       type="radio"
       value={1}
     />
     <label
-      htmlFor="radio-2-1"
+      htmlFor="radio-3-1"
+    />
+    <span
+      className=""
+    >
+      Gizmo
+    </span>
+  </li>
+</ul>
+`;
+
+exports[`Radio should render "underlined" correctly 1`] = `
+<ul
+  className="flex text-bold h3"
+>
+  <li
+    className="flex align-center cursor-pointer mr3 text-brand-hover py2"
+    onClick={[Function]}
+    style={
+      Object {
+        "borderBottom": "3px solid transparent",
+        "borderColor": "#509EE3",
+      }
+    }
+  >
+    <input
+      checked={true}
+      className="Form-radio"
+      id="radio-2-0"
+      name="radio-2"
+      type="radio"
+      value={0}
+    />
+    <span
+      className="text-brand"
+    >
+      Gadget
+    </span>
+  </li>
+  <li
+    className="flex align-center cursor-pointer mr3 text-brand-hover py2"
+    onClick={[Function]}
+    style={
+      Object {
+        "borderBottom": "3px solid transparent",
+        "borderColor": "transparent",
+      }
+    }
+  >
+    <input
+      checked={false}
+      className="Form-radio"
+      id="radio-2-1"
+      name="radio-2"
+      type="radio"
+      value={1}
     />
     <span
       className=""
@@ -537,19 +652,25 @@ exports[`Radio should render "vertical" correctly 1`] = `
   className="flex flex-column"
 >
   <li
-    className="flex align-center cursor-pointer mt1 mr2"
+    className="flex align-center cursor-pointer mr3 pt1"
     onClick={[Function]}
+    style={
+      Object {
+        "borderBottom": undefined,
+        "borderColor": "transparent",
+      }
+    }
   >
     <input
       checked={true}
       className="Form-radio"
-      id="radio-3-0"
-      name="radio-3"
+      id="radio-4-0"
+      name="radio-4"
       type="radio"
       value={0}
     />
     <label
-      htmlFor="radio-3-0"
+      htmlFor="radio-4-0"
     />
     <span
       className="text-brand"
@@ -558,19 +679,25 @@ exports[`Radio should render "vertical" correctly 1`] = `
     </span>
   </li>
   <li
-    className="flex align-center cursor-pointer mt1 mr2"
+    className="flex align-center cursor-pointer mr3 pt1"
     onClick={[Function]}
+    style={
+      Object {
+        "borderBottom": undefined,
+        "borderColor": "transparent",
+      }
+    }
   >
     <input
       checked={false}
       className="Form-radio"
-      id="radio-3-1"
-      name="radio-3"
+      id="radio-4-1"
+      name="radio-4"
       type="radio"
       value={1}
     />
     <label
-      htmlFor="radio-3-1"
+      htmlFor="radio-4-1"
     />
     <span
       className=""
@@ -589,7 +716,8 @@ exports[`Select should render "Default" correctly 1`] = `
   style={undefined}
 >
   <div
-    className="AdminSelect border-med flex align-center "
+    className="AdminSelect flex align-center"
+    style={undefined}
   >
     <span
       className="AdminSelect-content mr1"
@@ -622,7 +750,8 @@ exports[`Select should render "With search" correctly 1`] = `
   style={undefined}
 >
   <div
-    className="AdminSelect border-med flex align-center "
+    className="AdminSelect flex align-center"
+    style={undefined}
   >
     <span
       className="AdminSelect-content mr1"
@@ -808,7 +937,7 @@ exports[`StackedCheckBox should render "Off - Default" correctly 1`] = `
         style={
           Object {
             "backgroundColor": "white",
-            "border": "2px solid #ddd",
+            "border": "2px solid #C7CFD4",
             "height": 16,
             "width": 16,
           }
@@ -825,7 +954,7 @@ exports[`StackedCheckBox should render "Off - Default" correctly 1`] = `
       style={
         Object {
           "backgroundColor": "white",
-          "border": "2px solid #ddd",
+          "border": "2px solid #C7CFD4",
           "height": 16,
           "width": 16,
         }
@@ -892,7 +1021,7 @@ exports[`Styled(div) should render "with an icon" correctly 1`] = `
 
 exports[`Toggle should render "off" correctly 1`] = `
 <a
-  className="no-decoration "
+  className="no-decoration"
   onClick={null}
   style={
     Object {
@@ -904,7 +1033,7 @@ exports[`Toggle should render "off" correctly 1`] = `
 
 exports[`Toggle should render "on" correctly 1`] = `
 <a
-  className="no-decoration undefined "
+  className="no-decoration"
   onClick={null}
   style={
     Object {
@@ -952,7 +1081,7 @@ exports[`TokenField should render "" correctly 1`] = `
       className="mr1"
     >
       <div
-        className="py1 pl1 pr2 block rounded text-bold text-brand-hover inline-block full cursor-pointer bg-grey-0-hover"
+        className="py1 pl1 pr2 block rounded text-bold text-brand-hover inline-block full cursor-pointer bg-light-hover"
         onClick={[Function]}
         onMouseDown={[Function]}
       >
@@ -965,7 +1094,7 @@ exports[`TokenField should render "" correctly 1`] = `
       className="mr1"
     >
       <div
-        className="py1 pl1 pr2 block rounded text-bold text-brand-hover inline-block full cursor-pointer bg-grey-0-hover"
+        className="py1 pl1 pr2 block rounded text-bold text-brand-hover inline-block full cursor-pointer bg-light-hover"
         onClick={[Function]}
         onMouseDown={[Function]}
       >
@@ -978,7 +1107,7 @@ exports[`TokenField should render "" correctly 1`] = `
       className="mr1"
     >
       <div
-        className="py1 pl1 pr2 block rounded text-bold text-brand-hover inline-block full cursor-pointer bg-grey-0-hover"
+        className="py1 pl1 pr2 block rounded text-bold text-brand-hover inline-block full cursor-pointer bg-light-hover"
         onClick={[Function]}
         onMouseDown={[Function]}
       >
@@ -991,7 +1120,7 @@ exports[`TokenField should render "" correctly 1`] = `
       className="mr1"
     >
       <div
-        className="py1 pl1 pr2 block rounded text-bold text-brand-hover inline-block full cursor-pointer bg-grey-0-hover"
+        className="py1 pl1 pr2 block rounded text-bold text-brand-hover inline-block full cursor-pointer bg-light-hover"
         onClick={[Function]}
         onMouseDown={[Function]}
       >
@@ -1042,7 +1171,7 @@ exports[`TokenField should render "updateOnInputChange" correctly 1`] = `
       className="mr1"
     >
       <div
-        className="py1 pl1 pr2 block rounded text-bold text-brand-hover inline-block full cursor-pointer bg-grey-0-hover"
+        className="py1 pl1 pr2 block rounded text-bold text-brand-hover inline-block full cursor-pointer bg-light-hover"
         onClick={[Function]}
         onMouseDown={[Function]}
       >
@@ -1055,7 +1184,7 @@ exports[`TokenField should render "updateOnInputChange" correctly 1`] = `
       className="mr1"
     >
       <div
-        className="py1 pl1 pr2 block rounded text-bold text-brand-hover inline-block full cursor-pointer bg-grey-0-hover"
+        className="py1 pl1 pr2 block rounded text-bold text-brand-hover inline-block full cursor-pointer bg-light-hover"
         onClick={[Function]}
         onMouseDown={[Function]}
       >
@@ -1068,7 +1197,7 @@ exports[`TokenField should render "updateOnInputChange" correctly 1`] = `
       className="mr1"
     >
       <div
-        className="py1 pl1 pr2 block rounded text-bold text-brand-hover inline-block full cursor-pointer bg-grey-0-hover"
+        className="py1 pl1 pr2 block rounded text-bold text-brand-hover inline-block full cursor-pointer bg-light-hover"
         onClick={[Function]}
         onMouseDown={[Function]}
       >
@@ -1081,7 +1210,7 @@ exports[`TokenField should render "updateOnInputChange" correctly 1`] = `
       className="mr1"
     >
       <div
-        className="py1 pl1 pr2 block rounded text-bold text-brand-hover inline-block full cursor-pointer bg-grey-0-hover"
+        className="py1 pl1 pr2 block rounded text-bold text-brand-hover inline-block full cursor-pointer bg-light-hover"
         onClick={[Function]}
         onMouseDown={[Function]}
       >
diff --git a/frontend/test/jest-setup.js b/frontend/test/jest-setup.js
index 6ebd47696738c7d55b3b8e6e0abd1db2d76cc700..6643d953a54204e910e1023c1f1f445c3e0f5410 100644
--- a/frontend/test/jest-setup.js
+++ b/frontend/test/jest-setup.js
@@ -1 +1,10 @@
 import "raf/polyfill";
+import "jest-localstorage-mock";
+
+// NOTE: this is needed because sometimes asynchronous code tries to access
+// window.location or similar jsdom properties after the tests have ended and
+// jsdom has been torn down
+// NOTE: probably not needed in jest >= 23
+process.on("uncaughtException", err =>
+  console.error("WARNING: UNCAUGHT EXCEPTION", err),
+);
diff --git a/frontend/test/lib/card.unit.spec.js b/frontend/test/lib/card.unit.spec.js
index 58ee91d698e53c35fb838e0ddff13f81cfa99c94..c1608cdf27192ee8e5ddb7d2a0b7d5824c67719f 100644
--- a/frontend/test/lib/card.unit.spec.js
+++ b/frontend/test/lib/card.unit.spec.js
@@ -38,7 +38,7 @@ const getCard = ({
       ...(!isNative
         ? {
             query: {
-              ...(table ? { source_table: table } : {}),
+              ...(table ? { "source-table": table } : {}),
               ...queryFields,
             },
           }
diff --git a/frontend/test/lib/expressions/formatter.unit.spec.js b/frontend/test/lib/expressions/formatter.unit.spec.js
index dd5e9edc2596e2105d84a6d938054925cff766da..831e70bdd79c09be8859e80d012c8a62858b5684 100644
--- a/frontend/test/lib/expressions/formatter.unit.spec.js
+++ b/frontend/test/lib/expressions/formatter.unit.spec.js
@@ -78,7 +78,7 @@ describe("lib/expressions/parser", () => {
     });
 
     it("expression with metric", () => {
-      expect(format(["+", 1, ["METRIC", 1]], mockMetadata)).toEqual(
+      expect(format(["+", 1, ["metric", 1]], mockMetadata)).toEqual(
         '1 + "foo bar"',
       );
     });
diff --git a/frontend/test/lib/query.unit.spec.js b/frontend/test/lib/query.unit.spec.js
index 4932a92f294bde12cf9b7c7f0a44660e8adc0886..fa7c4012b0137c899d34c040790de5354b170e39 100644
--- a/frontend/test/lib/query.unit.spec.js
+++ b/frontend/test/lib/query.unit.spec.js
@@ -18,7 +18,7 @@ describe("Legacy Query library", () => {
         database: null,
         type: "query",
         query: {
-          source_table: null,
+          "source-table": null,
         },
       });
     });
@@ -38,7 +38,7 @@ describe("Legacy Query library", () => {
     });
 
     it("should populate the tableId if specified", () => {
-      expect(createQuery("query", 123, 456).query.source_table).toEqual(456);
+      expect(createQuery("query", 123, 456).query["source-table"]).toEqual(456);
     });
 
     it("should NOT set the tableId if query type is native", () => {
@@ -46,7 +46,9 @@ describe("Legacy Query library", () => {
     });
 
     it("should NOT populate the tableId if no database specified", () => {
-      expect(createQuery("query", null, 456).query.source_table).toEqual(null);
+      expect(createQuery("query", null, 456).query["source-table"]).toEqual(
+        null,
+      );
     });
   });
 
@@ -65,130 +67,130 @@ describe("Legacy Query library", () => {
     });
     it("should not remove complete sort clauses", () => {
       let query = {
-        source_table: 0,
+        "source-table": 0,
         aggregation: ["rows"],
         breakout: [],
         filter: [],
-        order_by: [[1, "ascending"]],
+        "order-by": [["asc", 1]],
       };
       Query.cleanQuery(query);
-      expect(query.order_by).toEqual([[1, "ascending"]]);
+      expect(query["order-by"]).toEqual([["asc", 1]]);
     });
     it("should remove incomplete sort clauses", () => {
       let query = {
-        source_table: 0,
+        "source-table": 0,
         aggregation: ["rows"],
         breakout: [],
         filter: [],
-        order_by: [[null, "ascending"]],
+        "order-by": [["asc", null]],
       };
       Query.cleanQuery(query);
-      expect(query.order_by).toEqual(undefined);
+      expect(query["order-by"]).toEqual(undefined);
     });
 
     it("should not remove sort clauses on aggregations if that aggregation supports it", () => {
       let query = {
-        source_table: 0,
+        "source-table": 0,
         aggregation: ["count"],
         breakout: [1],
         filter: [],
-        order_by: [[["aggregation", 0], "ascending"]],
+        "order-by": [["asc", ["aggregation", 0]]],
       };
       Query.cleanQuery(query);
-      expect(query.order_by).toEqual([[["aggregation", 0], "ascending"]]);
+      expect(query["order-by"]).toEqual([["asc", ["aggregation", 0]]]);
     });
     it("should remove sort clauses on aggregations if that aggregation doesn't support it", () => {
       let query = {
-        source_table: 0,
+        "source-table": 0,
         aggregation: ["rows"],
         breakout: [],
         filter: [],
-        order_by: [[["aggregation", 0], "ascending"]],
+        "order-by": [["asc", ["aggregation", 0]]],
       };
       Query.cleanQuery(query);
-      expect(query.order_by).toEqual(undefined);
+      expect(query["order-by"]).toEqual(undefined);
     });
 
     it("should not remove sort clauses on fields appearing in breakout", () => {
       let query = {
-        source_table: 0,
+        "source-table": 0,
         aggregation: ["count"],
         breakout: [1],
         filter: [],
-        order_by: [[1, "ascending"]],
+        "order-by": [["asc", 1]],
       };
       Query.cleanQuery(query);
-      expect(query.order_by).toEqual([[1, "ascending"]]);
+      expect(query["order-by"]).toEqual([["asc", 1]]);
     });
     it("should remove sort clauses on fields not appearing in breakout", () => {
       let query = {
-        source_table: 0,
+        "source-table": 0,
         aggregation: ["count"],
         breakout: [],
         filter: [],
-        order_by: [[1, "ascending"]],
+        "order-by": [["asc", 1]],
       };
       Query.cleanQuery(query);
-      expect(query.order_by).toEqual(undefined);
+      expect(query["order-by"]).toEqual(undefined);
     });
 
     it("should not remove sort clauses with foreign keys on fields appearing in breakout", () => {
       let query = {
-        source_table: 0,
+        "source-table": 0,
         aggregation: ["count"],
         breakout: [["fk->", 1, 2]],
         filter: [],
-        order_by: [[["fk->", 1, 2], "ascending"]],
+        "order-by": [["asc", ["fk->", 1, 2]]],
       };
       Query.cleanQuery(query);
-      expect(query.order_by).toEqual([[["fk->", 1, 2], "ascending"]]);
+      expect(query["order-by"]).toEqual([["asc", ["fk->", 1, 2]]]);
     });
 
     it("should not remove sort clauses with datetime-fields on fields appearing in breakout", () => {
       let query = {
-        source_table: 0,
+        "source-table": 0,
         aggregation: ["count"],
         breakout: [["datetime-field", 1, "as", "week"]],
         filter: [],
-        order_by: [[["datetime-field", 1, "as", "week"], "ascending"]],
+        "order-by": [["asc", ["datetime-field", 1, "as", "week"]]],
       };
       Query.cleanQuery(query);
-      expect(query.order_by).toEqual([
-        [["datetime-field", 1, "as", "week"], "ascending"],
+      expect(query["order-by"]).toEqual([
+        ["asc", ["datetime-field", 1, "as", "week"]],
       ]);
     });
 
-    it("should replace order_by clauses with the exact matching datetime-fields version in the breakout", () => {
+    it("should replace order-by clauses with the exact matching datetime-fields version in the breakout", () => {
       let query = {
-        source_table: 0,
+        "source-table": 0,
         aggregation: ["count"],
         breakout: [["datetime-field", 1, "as", "week"]],
         filter: [],
-        order_by: [[1, "ascending"]],
+        "order-by": [["asc", 1]],
       };
       Query.cleanQuery(query);
-      expect(query.order_by).toEqual([
-        [["datetime-field", 1, "as", "week"], "ascending"],
+      expect(query["order-by"]).toEqual([
+        ["asc", ["datetime-field", 1, "as", "week"]],
       ]);
     });
 
-    it("should replace order_by clauses with the exact matching fk-> version in the breakout", () => {
+    it("should replace order-by clauses with the exact matching fk-> version in the breakout", () => {
       let query = {
-        source_table: 0,
+        "source-table": 0,
         aggregation: ["count"],
         breakout: [["fk->", 1, 2]],
         filter: [],
-        order_by: [[2, "ascending"]],
+        "order-by": [["asc", 2]],
       };
       Query.cleanQuery(query);
-      expect(query.order_by).toEqual([[["fk->", 1, 2], "ascending"]]);
+      expect(query["order-by"]).toEqual([["asc", ["fk->", 1, 2]]]);
     });
   });
 
   describe("removeBreakout", () => {
     it("should not mutate the query", () => {
       let query = {
-        source_table: 0,
+        "source-table": 0,
         aggregation: ["count"],
         breakout: [["field-id", 1]],
         filter: [],
@@ -198,7 +200,7 @@ describe("Legacy Query library", () => {
     });
     it("should remove the dimension", () => {
       let query = {
-        source_table: 0,
+        "source-table": 0,
         aggregation: ["count"],
         breakout: [["field-id", 1]],
         filter: [],
@@ -208,14 +210,14 @@ describe("Legacy Query library", () => {
     });
     it("should remove sort clauses for the dimension that was removed", () => {
       let query = {
-        source_table: 0,
+        "source-table": 0,
         aggregation: ["count"],
         breakout: [["field-id", 1]],
         filter: [],
-        order_by: [[1, "ascending"]],
+        "order-by": [["asc", 1]],
       };
       query = Query.removeBreakout(query, 0);
-      expect(query.order_by).toEqual(undefined);
+      expect(query["order-by"]).toEqual(undefined);
     });
   });
 
@@ -310,7 +312,7 @@ describe("generateQueryDescription", () => {
   it("should work with multiple aggregations", () => {
     expect(
       Query.generateQueryDescription(mockTableMetadata, {
-        source_table: 1,
+        "source-table": 1,
         aggregation: [["count"], ["sum", ["field-id", 1]]],
       }),
     ).toEqual("Orders, Count and Sum of Total");
@@ -318,7 +320,7 @@ describe("generateQueryDescription", () => {
   it("should work with named aggregations", () => {
     expect(
       Query.generateQueryDescription(mockTableMetadata, {
-        source_table: 1,
+        "source-table": 1,
         aggregation: [["named", ["sum", ["field-id", 1]], "Revenue"]],
       }),
     ).toEqual("Orders, Revenue");
@@ -338,7 +340,8 @@ describe("AggregationClause", () => {
     });
 
     it("should succeed on good clauses", () => {
-      expect(AggregationClause.isValid(["METRIC", 123])).toEqual(true);
+      expect(AggregationClause.isValid(["metric", 123])).toEqual(true);
+      // TODO - actually this should be FALSE because rows is not a valid aggregation type!
       expect(AggregationClause.isValid(["rows"])).toEqual(true);
       expect(AggregationClause.isValid(["sum", 456])).toEqual(true);
     });
@@ -353,7 +356,7 @@ describe("AggregationClause", () => {
       expect(AggregationClause.isBareRows("ab")).toEqual(false);
       expect(AggregationClause.isBareRows(["foo", null])).toEqual(false);
       expect(AggregationClause.isBareRows(["a", "b", "c"])).toEqual(false);
-      expect(AggregationClause.isBareRows(["METRIC", 123])).toEqual(false);
+      expect(AggregationClause.isBareRows(["metric", 123])).toEqual(false);
       expect(AggregationClause.isBareRows(["sum", 456])).toEqual(false);
     });
 
@@ -371,7 +374,7 @@ describe("AggregationClause", () => {
       expect(AggregationClause.isStandard("ab")).toEqual(false);
       expect(AggregationClause.isStandard(["foo", null])).toEqual(false);
       expect(AggregationClause.isStandard(["a", "b", "c"])).toEqual(false);
-      expect(AggregationClause.isStandard(["METRIC", 123])).toEqual(false);
+      expect(AggregationClause.isStandard(["metric", 123])).toEqual(false);
     });
 
     it("should succeed on good clauses", () => {
@@ -394,13 +397,13 @@ describe("AggregationClause", () => {
     });
 
     it("should succeed on good clauses", () => {
-      expect(AggregationClause.isMetric(["METRIC", 123])).toEqual(true);
+      expect(AggregationClause.isMetric(["metric", 123])).toEqual(true);
     });
   });
 
   describe("getMetric", () => {
     it("should succeed on good clauses", () => {
-      expect(AggregationClause.getMetric(["METRIC", 123])).toEqual(123);
+      expect(AggregationClause.getMetric(["metric", 123])).toEqual(123);
     });
 
     it("should be null on non-metric clauses", () => {
@@ -415,7 +418,7 @@ describe("AggregationClause", () => {
     });
 
     it("should be null on metric clauses", () => {
-      expect(AggregationClause.getOperator(["METRIC", 123])).toEqual(null);
+      expect(AggregationClause.getOperator(["metric", 123])).toEqual(null);
     });
   });
 
@@ -429,7 +432,7 @@ describe("AggregationClause", () => {
     });
 
     it("should be null on metric clauses", () => {
-      expect(AggregationClause.getField(["METRIC", 123])).toEqual(null);
+      expect(AggregationClause.getField(["metric", 123])).toEqual(null);
     });
   });
 
@@ -443,8 +446,8 @@ describe("AggregationClause", () => {
     });
 
     it("should return unmodified on metric clauses", () => {
-      expect(AggregationClause.setField(["METRIC", 123], 456)).toEqual([
-        "METRIC",
+      expect(AggregationClause.setField(["metric", 123], 456)).toEqual([
+        "metric",
         123,
       ]);
     });
diff --git a/frontend/test/lib/query/query.unit.spec.js b/frontend/test/lib/query/query.unit.spec.js
index 034c024fc8be1681cc5454a41de1b3e4f6e05b65..9e5d7ee737c4265aa59ae831bcd2fefaa18aab18 100644
--- a/frontend/test/lib/query/query.unit.spec.js
+++ b/frontend/test/lib/query/query.unit.spec.js
@@ -5,9 +5,7 @@ describe("Query", () => {
     it("should return true for all bare rows variation", () => {
       expect(Query.isBareRows({})).toBe(true);
       expect(Query.isBareRows({ aggregation: null })).toBe(true); // deprecated
-      expect(Query.isBareRows({ aggregation: ["rows"] })).toBe(true); // deprecated
       expect(Query.isBareRows({ aggregation: [] })).toBe(true); // deprecated
-      expect(Query.isBareRows({ aggregation: [["rows"]] })).toBe(true); // deprecated
     });
     it("should return false for other aggregations", () => {
       expect(Query.isBareRows({ aggregation: [["count"]] })).toBe(false);
@@ -88,17 +86,11 @@ describe("Query", () => {
   describe("removeBreakout", () => {
     it("should remove sort as well", () => {
       expect(
-        Query.removeBreakout(
-          { breakout: [1], order_by: [[1, "ascending"]] },
-          0,
-        ),
+        Query.removeBreakout({ breakout: [1], "order-by": [["asc", 1]] }, 0),
       ).toEqual({});
       expect(
-        Query.removeBreakout(
-          { breakout: [2, 1], order_by: [[1, "ascending"]] },
-          0,
-        ),
-      ).toEqual({ breakout: [1], order_by: [[1, "ascending"]] });
+        Query.removeBreakout({ breakout: [2, 1], "order-by": [["asc", 1]] }, 0),
+      ).toEqual({ breakout: [1], "order-by": [["asc", 1]] });
     });
     it("should not remove aggregation sorts", () => {
       expect(
@@ -106,14 +98,14 @@ describe("Query", () => {
           {
             aggregation: [["count"]],
             breakout: [2, 1],
-            order_by: [[["aggregation", 0], "ascending"]],
+            "order-by": [["asc", ["aggregation", 0]]],
           },
           0,
         ),
       ).toEqual({
         aggregation: [["count"]],
         breakout: [1],
-        order_by: [[["aggregation", 0], "ascending"]],
+        "order-by": [["asc", ["aggregation", 0]]],
       });
     });
   });
diff --git a/frontend/test/lib/urls.unit.spec.js b/frontend/test/lib/urls.unit.spec.js
index 69c66785144660c3579188f7551cdfd454c7f12b..eb3855970c3b86a0af6f14a2be13cad3fbfe0f8b 100644
--- a/frontend/test/lib/urls.unit.spec.js
+++ b/frontend/test/lib/urls.unit.spec.js
@@ -1,4 +1,4 @@
-import { question } from "metabase/lib/urls";
+import { question, extractQueryParams } from "metabase/lib/urls";
 
 describe("urls", () => {
   describe("question", () => {
@@ -20,4 +20,30 @@ describe("urls", () => {
       });
     });
   });
+  describe("query", () => {
+    it("should return the correct number of parameters", () => {
+      expect(extractQueryParams({ foo: "bar" })).toHaveLength(1);
+      expect(extractQueryParams({ foo: [1, 2, 3] })).toHaveLength(3);
+      expect(extractQueryParams({ foo: ["1", "2"] })).toHaveLength(2);
+      expect(
+        extractQueryParams({
+          foo1: ["baz1", "baz2"],
+          foo2: [1, 2, 3],
+          foo3: ["bar1", "bar2"],
+        }),
+      ).toHaveLength(7);
+    });
+    it("should return correct parameters", () => {
+      expect(extractQueryParams({ foo: "bar" })).toEqual([["foo", "bar"]]);
+
+      const extractedParams1 = extractQueryParams({ foo: [1, 2, 3] });
+      expect(extractedParams1).toContainEqual(["foo", 1]);
+      expect(extractedParams1).toContainEqual(["foo", 2]);
+      expect(extractedParams1).toContainEqual(["foo", 3]);
+
+      const extractedParams2 = extractQueryParams({ foo: ["1", "2"] });
+      expect(extractedParams2).toContainEqual(["foo", "1"]);
+      expect(extractedParams2).toContainEqual(["foo", "2"]);
+    });
+  });
 });
diff --git a/frontend/test/lib/utils.unit.spec.js b/frontend/test/lib/utils.unit.spec.js
index d8db00751bcb4b30b474bf37e35a31156ef25bb7..a1fe21415eeaef56557d3c5a4f90e699226795f4 100644
--- a/frontend/test/lib/utils.unit.spec.js
+++ b/frontend/test/lib/utils.unit.spec.js
@@ -1,39 +1,51 @@
 import MetabaseUtils from "metabase/lib/utils";
+import MetabaseSettings from "metabase/lib/settings";
 
 describe("utils", () => {
   describe("generatePassword", () => {
-    it("defaults to length 14 passwords", () => {
+    it("defaults to at least 14 characters even if password_complexity requirements are lower", () => {
+      MetabaseSettings.set("password_complexity", { total: 10 });
       expect(MetabaseUtils.generatePassword().length).toBe(14);
     });
 
+    it("defaults to complexity requirements if greater than 14", () => {
+      MetabaseSettings.set("password_complexity", { total: 20 });
+      expect(MetabaseUtils.generatePassword().length).toBe(20);
+    });
+
+    it("falls back to length 14 passwords", () => {
+      expect(MetabaseUtils.generatePassword({}).length).toBe(14);
+    });
+
     it("creates passwords for the length we specify", () => {
-      expect(MetabaseUtils.generatePassword(25).length).toBe(25);
+      expect(MetabaseUtils.generatePassword({ total: 25 }).length).toBe(25);
     });
 
     it("can enforce ", () => {
       expect(
-        MetabaseUtils.generatePassword(14, { digit: 2 }).match(/([\d])/g)
+        MetabaseUtils.generatePassword({ total: 14, digit: 2 }).match(/([\d])/g)
           .length >= 2,
       ).toBe(true);
     });
 
     it("can enforce digit requirements", () => {
       expect(
-        MetabaseUtils.generatePassword(14, { digit: 2 }).match(/([\d])/g)
+        MetabaseUtils.generatePassword({ total: 14, digit: 2 }).match(/([\d])/g)
           .length >= 2,
       ).toBe(true);
     });
 
     it("can enforce uppercase requirements", () => {
       expect(
-        MetabaseUtils.generatePassword(14, { uppercase: 2 }).match(/([A-Z])/g)
-          .length >= 2,
+        MetabaseUtils.generatePassword({ total: 14, uppercase: 2 }).match(
+          /([A-Z])/g,
+        ).length >= 2,
       ).toBe(true);
     });
 
     it("can enforce special character requirements", () => {
       expect(
-        MetabaseUtils.generatePassword(14, { special: 2 }).match(
+        MetabaseUtils.generatePassword({ total: 14, special: 2 }).match(
           /([!@#\$%\^\&*\)\(+=._-{}])/g,
         ).length >= 2,
       ).toBe(true);
diff --git a/frontend/test/meta/Card.unit.spec.js b/frontend/test/meta/Card.unit.spec.js
index 3cafc87fae5ae0cce7f9de8229dc2c065c81b75b..7bdd787326e6884227ceda8a40758b0e76828429 100644
--- a/frontend/test/meta/Card.unit.spec.js
+++ b/frontend/test/meta/Card.unit.spec.js
@@ -41,7 +41,7 @@ describe("metabase/meta/Card", () => {
         dataset_query: {
           type: "native",
           native: {
-            template_tags: {
+            "template-tags": {
               baz: { name: "baz", type: "text" },
             },
           },
@@ -83,7 +83,7 @@ describe("metabase/meta/Card", () => {
         dataset_query: {
           type: "query",
           query: {
-            source_table: 1,
+            "source-table": 1,
           },
         },
       };
@@ -131,7 +131,7 @@ describe("metabase/meta/Card", () => {
           card: assocIn(
             dissoc(card, "id"),
             ["dataset_query", "query", "filter"],
-            ["AND", ["=", ["field-id", 1], "bar"]],
+            ["and", ["=", ["field-id", 1], "bar"]],
           ),
         });
       });
@@ -155,7 +155,7 @@ describe("metabase/meta/Card", () => {
           card: assocIn(
             cardWithOnlyOriginalCardId,
             ["dataset_query", "query", "filter"],
-            ["AND", ["=", ["field-id", 1], "bar"]],
+            ["and", ["=", ["field-id", 1], "bar"]],
           ),
         });
       });
@@ -173,7 +173,7 @@ describe("metabase/meta/Card", () => {
           card: assocIn(
             dissoc(card, "id"),
             ["dataset_query", "query", "filter"],
-            ["AND", ["=", ["field-id", 2], 123]],
+            ["and", ["=", ["field-id", 2], 123]],
           ),
         });
       });
@@ -193,7 +193,7 @@ describe("metabase/meta/Card", () => {
             dissoc(card, "id"),
             ["dataset_query", "query", "filter"],
             [
-              "AND",
+              "and",
               ["=", ["datetime-field", ["field-id", 3], "month"], "2017-05-01"],
             ],
           ),
@@ -214,7 +214,7 @@ describe("metabase/meta/Card", () => {
             dissoc(card, "id"),
             ["dataset_query", "query", "filter"],
             [
-              "AND",
+              "and",
               ["=", ["datetime-field", ["fk->", 4, 5], "month"], "2017-05-01"],
             ],
           ),
diff --git a/frontend/test/metabase-lib/Question.integ.spec.js b/frontend/test/metabase-lib/Question.integ.spec.js
index f8e37bfaa85508d764bb6baa960aa2f921cfd1b1..6f65debe65a9800d1fea06c111547aa2a36825da 100644
--- a/frontend/test/metabase-lib/Question.integ.spec.js
+++ b/frontend/test/metabase-lib/Question.integ.spec.js
@@ -28,7 +28,7 @@ describe("Question", () => {
         database: DATABASE_ID,
         native: {
           query: `SELECT SUBTOTAL FROM ORDERS WHERE id = {{${templateTagName}}}`,
-          template_tags: {
+          "template-tags": {
             [templateTagName]: {
               id: templateTagId,
               name: templateTagName,
@@ -61,7 +61,7 @@ describe("Question", () => {
         database: DATABASE_ID,
         native: {
           query: `SELECT SUBTOTAL FROM ORDERS [[WHERE id = {{${templateTagName}}}]]`,
-          template_tags: {
+          "template-tags": {
             [templateTagName]: {
               id: templateTagId,
               name: templateTagName,
diff --git a/frontend/test/metabase-lib/Question.unit.spec.js b/frontend/test/metabase-lib/Question.unit.spec.js
index 1e8c1221a47ee110a962e4aa2e02911bb2ae6c45..21d13f6f1ed1c2fbe21c985eca6e2fef98b0bcaf 100644
--- a/frontend/test/metabase-lib/Question.unit.spec.js
+++ b/frontend/test/metabase-lib/Question.unit.spec.js
@@ -243,7 +243,7 @@ describe("Question", () => {
           type: "query",
           database: DATABASE_ID,
           query: {
-            source_table: ORDERS_TABLE_ID,
+            "source-table": ORDERS_TABLE_ID,
             aggregation: [["count"]],
             breakout: [["field-id", ORDERS_CREATED_DATE_FIELD_ID]],
           },
@@ -251,7 +251,7 @@ describe("Question", () => {
 
         // Make sure we haven't mutated the underlying query
         expect(orders_count_card.dataset_query.query).toEqual({
-          source_table: ORDERS_TABLE_ID,
+          "source-table": ORDERS_TABLE_ID,
           aggregation: [["count"]],
         });
       });
@@ -267,7 +267,7 @@ describe("Question", () => {
           type: "query",
           database: DATABASE_ID,
           query: {
-            source_table: ORDERS_TABLE_ID,
+            "source-table": ORDERS_TABLE_ID,
             aggregation: [["count"]],
             breakout: [["field-id", ORDERS_PK_FIELD_ID]],
           },
@@ -275,7 +275,7 @@ describe("Question", () => {
 
         // Make sure we haven't mutated the underlying query
         expect(orders_count_card.dataset_query.query).toEqual({
-          source_table: ORDERS_TABLE_ID,
+          "source-table": ORDERS_TABLE_ID,
           aggregation: [["count"]],
         });
       });
@@ -295,14 +295,14 @@ describe("Question", () => {
           type: "query",
           database: DATABASE_ID,
           query: {
-            source_table: ORDERS_TABLE_ID,
+            "source-table": ORDERS_TABLE_ID,
             aggregation: [["count"]],
             breakout: ["field-id", ORDERS_CREATED_DATE_FIELD_ID],
           },
         });
         // Make sure we haven't mutated the underlying query
         expect(orders_count_card.dataset_query.query).toEqual({
-          source_table: ORDERS_TABLE_ID,
+          "source-table": ORDERS_TABLE_ID,
           aggregation: [["count"]],
         });
       });
@@ -318,14 +318,14 @@ describe("Question", () => {
           type: "query",
           database: DATABASE_ID,
           query: {
-            source_table: ORDERS_TABLE_ID,
+            "source-table": ORDERS_TABLE_ID,
             aggregation: [["count"]],
             breakout: ["field-id", ORDERS_PK_FIELD_ID],
           },
         });
         // Make sure we haven't mutated the underlying query
         expect(orders_count_card.dataset_query.query).toEqual({
-          source_table: ORDERS_TABLE_ID,
+          "source-table": ORDERS_TABLE_ID,
           aggregation: [["count"]],
         });
       });
@@ -345,7 +345,7 @@ describe("Question", () => {
           type: "query",
           database: DATABASE_ID,
           query: {
-            source_table: ORDERS_TABLE_ID,
+            "source-table": ORDERS_TABLE_ID,
             filter: ["=", ["field-id", ORDERS_PK_FIELD_ID], 1],
           },
         });
@@ -364,7 +364,7 @@ describe("Question", () => {
           type: "query",
           database: DATABASE_ID,
           query: {
-            source_table: ORDERS_TABLE_ID,
+            "source-table": ORDERS_TABLE_ID,
             filter: [
               "=",
               ["fk->", ORDERS_PRODUCT_FK_FIELD_ID, PRODUCT_CATEGORY_FIELD_ID],
@@ -385,7 +385,7 @@ describe("Question", () => {
           type: "query",
           database: DATABASE_ID,
           query: {
-            source_table: ORDERS_TABLE_ID,
+            "source-table": ORDERS_TABLE_ID,
             filter: [
               "=",
               ["field-id", ORDERS_CREATED_DATE_FIELD_ID],
@@ -417,7 +417,7 @@ describe("Question", () => {
           type: "query",
           database: DATABASE_ID,
           query: {
-            source_table: ORDERS_TABLE_ID,
+            "source-table": ORDERS_TABLE_ID,
             filter: ["=", ["field-id", ORDERS_PK_FIELD_ID], 1],
           },
         });
@@ -439,7 +439,7 @@ describe("Question", () => {
 
         // Make sure we haven't mutated the underlying query
         expect(orders_raw_card.dataset_query.query).toEqual({
-          source_table: ORDERS_TABLE_ID,
+          "source-table": ORDERS_TABLE_ID,
         });
       });
       it("returns underlying records correctly for a broken out query", () => {
@@ -453,7 +453,7 @@ describe("Question", () => {
 
         // Make sure we haven't mutated the underlying query
         expect(orders_raw_card.dataset_query.query).toEqual({
-          source_table: ORDERS_TABLE_ID,
+          "source-table": ORDERS_TABLE_ID,
         });
       });
     });
@@ -492,7 +492,7 @@ describe("Question", () => {
           type: "query",
           database: DATABASE_ID,
           query: {
-            source_table: ORDERS_TABLE_ID,
+            "source-table": ORDERS_TABLE_ID,
             filter: ["=", ["field-id", ORDERS_PK_FIELD_ID], 1],
           },
         });
@@ -531,7 +531,7 @@ describe("Question", () => {
       it("returns a question with hash for an unsaved question", () => {
         const question = new Question(metadata, orders_raw_card);
         expect(question.getUrl()).toBe(
-          "/question#eyJuYW1lIjoiUmF3IG9yZGVycyBkYXRhIiwiZGF0YXNldF9xdWVyeSI6eyJ0eXBlIjoicXVlcnkiLCJkYXRhYmFzZSI6MSwicXVlcnkiOnsic291cmNlX3RhYmxlIjoxfX0sImRpc3BsYXkiOiJ0YWJsZSIsInZpc3VhbGl6YXRpb25fc2V0dGluZ3MiOnt9fQ==",
+          "/question#eyJuYW1lIjoiUmF3IG9yZGVycyBkYXRhIiwiZGF0YXNldF9xdWVyeSI6eyJ0eXBlIjoicXVlcnkiLCJkYXRhYmFzZSI6MSwicXVlcnkiOnsic291cmNlLXRhYmxlIjoxfX0sImRpc3BsYXkiOiJ0YWJsZSIsInZpc3VhbGl6YXRpb25fc2V0dGluZ3MiOnt9fQ==",
         );
       });
     });
diff --git a/frontend/test/metabase-lib/queries/NativeQuery.unit.spec.js b/frontend/test/metabase-lib/queries/NativeQuery.unit.spec.js
index e6c89894268cc9481a8d2cfa358100f5793054ee..1d471de5cba450ef608a52220fcf94df12e6f9c7 100644
--- a/frontend/test/metabase-lib/queries/NativeQuery.unit.spec.js
+++ b/frontend/test/metabase-lib/queries/NativeQuery.unit.spec.js
@@ -15,7 +15,7 @@ function makeDatasetQuery(queryText, templateTags, databaseId) {
     database: databaseId,
     native: {
       query: queryText,
-      template_tags: templateTags,
+      "template-tags": templateTags,
     },
   };
 }
diff --git a/frontend/test/metabase-lib/queries/StructuredQuery.unit.spec.js b/frontend/test/metabase-lib/queries/StructuredQuery.unit.spec.js
index 2364743f8f839c5961344f0fb369f9445498791f..9e6e32fcac1cae8d446461b5a2446b48fa310533 100644
--- a/frontend/test/metabase-lib/queries/StructuredQuery.unit.spec.js
+++ b/frontend/test/metabase-lib/queries/StructuredQuery.unit.spec.js
@@ -22,7 +22,7 @@ function makeDatasetQuery(query) {
     type: "query",
     database: DATABASE_ID,
     query: {
-      source_table: ORDERS_TABLE_ID,
+      "source-table": ORDERS_TABLE_ID,
       ...query,
     },
   };
@@ -103,7 +103,7 @@ describe("StructuredQuery unit tests", () => {
     });
     describe("query", () => {
       it("returns the wrapper for the query dictionary", () => {
-        expect(query.query().source_table).toBe(ORDERS_TABLE_ID);
+        expect(query.query()["source-table"]).toBe(ORDERS_TABLE_ID);
       });
     });
     describe("setDatabase", () => {
@@ -237,7 +237,7 @@ describe("StructuredQuery unit tests", () => {
       it("returns a saved metric's name", () => {
         expect(
           makeQueryWithAggregation([
-            "METRIC",
+            "metric",
             MAIN_METRIC_ID,
           ]).aggregationName(),
         ).toBe("Total Order Value");
@@ -286,7 +286,7 @@ describe("StructuredQuery unit tests", () => {
     describe("addAggregation", () => {
       it("adds an aggregation", () => {
         expect(query.addAggregation(["count"]).query()).toEqual({
-          source_table: ORDERS_TABLE_ID,
+          "source-table": ORDERS_TABLE_ID,
           aggregation: [["count"]],
         });
       });
@@ -383,7 +383,7 @@ describe("StructuredQuery unit tests", () => {
 
     describe("segments", () => {
       it("should list any applied segments that are currently active filters", () => {
-        const queryWithSegmentFilter = query.addFilter(["SEGMENT", 1]);
+        const queryWithSegmentFilter = query.addFilter(["segment", 1]);
         // expect there to be segments
         expect(queryWithSegmentFilter.segments().length).toBe(1);
         // and they should actually be segments
@@ -425,9 +425,9 @@ describe("StructuredQuery unit tests", () => {
       it("return an array with the sort clause", () => {
         expect(
           makeQuery({
-            order_by: [["field-id", ORDERS_TOTAL_FIELD_ID], "ascending"],
+            "order-by": ["asc", ["field-id", ORDERS_TOTAL_FIELD_ID]],
           }).sorts(),
-        ).toEqual([["field-id", ORDERS_TOTAL_FIELD_ID], "ascending"]);
+        ).toEqual(["asc", ["field-id", ORDERS_TOTAL_FIELD_ID]]);
       });
     });
 
@@ -438,14 +438,14 @@ describe("StructuredQuery unit tests", () => {
 
       it("excludes the already used sorts", () => {
         const queryWithBreakout = query.addSort([
+          "asc",
           ["field-id", ORDERS_TOTAL_FIELD_ID],
-          "ascending",
         ]);
         expect(queryWithBreakout.sortOptions().dimensions.length).toBe(6);
       });
 
       it("includes an explicitly provided sort although it has already been used", () => {
-        const sort = [["field-id", ORDERS_TOTAL_FIELD_ID], "ascending"];
+        const sort = ["asc", ["field-id", ORDERS_TOTAL_FIELD_ID]];
         const queryWithBreakout = query.addSort(sort);
         expect(queryWithBreakout.sortOptions().dimensions.length).toBe(6);
         expect(queryWithBreakout.sortOptions(sort).dimensions.length).toBe(7);
@@ -579,7 +579,7 @@ describe("StructuredQuery unit tests", () => {
     describe("setDatasetQuery", () => {
       it("replaces the previous dataset query with the provided one", () => {
         const newDatasetQuery = makeDatasetQuery({
-          source_table: ORDERS_TABLE_ID,
+          "source-table": ORDERS_TABLE_ID,
           aggregation: [["count"]],
         });
 
diff --git a/frontend/test/modes/actions/CommonMetricsAction.unit.spec.js b/frontend/test/modes/actions/CommonMetricsAction.unit.spec.js
index e3ba0f3c1ce7e43e58e9f858b866ab54822eff8a..74a65d5e47ae29d3a1e85b45d276545c12c6336a 100644
--- a/frontend/test/modes/actions/CommonMetricsAction.unit.spec.js
+++ b/frontend/test/modes/actions/CommonMetricsAction.unit.spec.js
@@ -46,8 +46,8 @@ describe("CommonMetricsAction", () => {
     expect(actions).toHaveLength(1);
     const newCard = actions[0].question().card();
     expect(newCard.dataset_query.query).toEqual({
-      source_table: ORDERS_TABLE_ID,
-      aggregation: [["METRIC", MAIN_METRIC_ID]],
+      "source-table": ORDERS_TABLE_ID,
+      aggregation: [["metric", MAIN_METRIC_ID]],
     });
     expect(newCard.display).toEqual("scalar");
   });
diff --git a/frontend/test/modes/actions/CompoundQueryAction.unit.spec.js b/frontend/test/modes/actions/CompoundQueryAction.unit.spec.js
index d40c05073bee90f2f393d511d2671f1bd6bbc105..28214d3f62ec91c7e2c41eeba44518194d57a6ff 100644
--- a/frontend/test/modes/actions/CompoundQueryAction.unit.spec.js
+++ b/frontend/test/modes/actions/CompoundQueryAction.unit.spec.js
@@ -23,7 +23,7 @@ describe("CompoundQueryAction", () => {
     expect(actions).toHaveLength(1);
     const newCard = actions[0].question().card();
     expect(newCard.dataset_query.query).toEqual({
-      source_table: "card__2",
+      "source-table": "card__2",
     });
   });
 
@@ -34,7 +34,7 @@ describe("CompoundQueryAction", () => {
     expect(actions).toHaveLength(1);
     const newCard = actions[0].question().card();
     expect(newCard.dataset_query.query).toEqual({
-      source_table: "card__3",
+      "source-table": "card__3",
     });
   });
 });
diff --git a/frontend/test/modes/actions/CountByTimeAction.unit.spec.js b/frontend/test/modes/actions/CountByTimeAction.unit.spec.js
index 0311738206551a1f61c2be3cda32f143d3e02f8d..57bdb5fdea51274ab437ec555430335ee7773631 100644
--- a/frontend/test/modes/actions/CountByTimeAction.unit.spec.js
+++ b/frontend/test/modes/actions/CountByTimeAction.unit.spec.js
@@ -18,7 +18,7 @@ describe("CountByTimeAction", () => {
     expect(actions).toHaveLength(1);
     const newCard = actions[0].question().card();
     expect(newCard.dataset_query.query).toEqual({
-      source_table: ORDERS_TABLE_ID,
+      "source-table": ORDERS_TABLE_ID,
       aggregation: [["count"]],
       breakout: [
         [
diff --git a/frontend/test/modes/drills/ObjectDetailDrill.unit.spec.js b/frontend/test/modes/drills/ObjectDetailDrill.unit.spec.js
index 4cc26e3d8a1cbc69c4b313ab20f8dc17a9efa603..7b6ec2def5549d457d64380a7b784ce089aaa5ad 100644
--- a/frontend/test/modes/drills/ObjectDetailDrill.unit.spec.js
+++ b/frontend/test/modes/drills/ObjectDetailDrill.unit.spec.js
@@ -30,7 +30,7 @@ describe("ObjectDetailDrill", () => {
     expect(actions).toHaveLength(1);
     const newCard = actions[0].question().card();
     expect(newCard.dataset_query.query).toEqual({
-      source_table: ORDERS_TABLE_ID,
+      "source-table": ORDERS_TABLE_ID,
       filter: ["=", ["field-id", ORDERS_PK_FIELD_ID], 42],
     });
   });
@@ -42,7 +42,7 @@ describe("ObjectDetailDrill", () => {
     expect(actions).toHaveLength(1);
     const newCard = actions[0].question().card();
     expect(newCard.dataset_query.query).toEqual({
-      source_table: PRODUCT_TABLE_ID,
+      "source-table": PRODUCT_TABLE_ID,
       filter: ["=", ["field-id", PRODUCT_PK_FIELD_ID], 43],
     });
   });
diff --git a/frontend/test/modes/drills/SummarizeColumnByTimeDrill.unit.spec.js b/frontend/test/modes/drills/SummarizeColumnByTimeDrill.unit.spec.js
index 4b200778554c2098ba86074a3c789e6bae6e4eca..b4ab82fd5090ce8bf5c8ce64c8df90fc1518f056 100644
--- a/frontend/test/modes/drills/SummarizeColumnByTimeDrill.unit.spec.js
+++ b/frontend/test/modes/drills/SummarizeColumnByTimeDrill.unit.spec.js
@@ -31,7 +31,7 @@ describe("SummarizeColumnByTimeDrill", () => {
     expect(actions).toHaveLength(1);
     const newCard = actions[0].question().card();
     expect(newCard.dataset_query.query).toEqual({
-      source_table: ORDERS_TABLE_ID,
+      "source-table": ORDERS_TABLE_ID,
       aggregation: [["sum", ["field-id", ORDERS_TOTAL_FIELD_ID]]],
       breakout: [
         ["datetime-field", ["field-id", ORDERS_CREATED_DATE_FIELD_ID], "day"],
diff --git a/frontend/test/modes/drills/SummarizeColumnDrill.unit.spec.js b/frontend/test/modes/drills/SummarizeColumnDrill.unit.spec.js
index 42979354ae554bbf1c6f10b580e9be277327cfb2..149690d2c848377702edf0c784548546a08cfee4 100644
--- a/frontend/test/modes/drills/SummarizeColumnDrill.unit.spec.js
+++ b/frontend/test/modes/drills/SummarizeColumnDrill.unit.spec.js
@@ -21,7 +21,7 @@ describe("SummarizeColumnDrill", () => {
     expect(actions.length).toEqual(5);
     let newCard = actions[0].question().card();
     expect(newCard.dataset_query.query).toEqual({
-      source_table: ORDERS_TABLE_ID,
+      "source-table": ORDERS_TABLE_ID,
       aggregation: [["sum", ["field-id", ORDERS_TOTAL_FIELD_ID]]],
     });
     expect(newCard.display).toEqual("scalar");
diff --git a/frontend/test/modes/drills/UnderlyingRecordsDrill.unit.spec.js b/frontend/test/modes/drills/UnderlyingRecordsDrill.unit.spec.js
index 11ee419aea2a93b79a28ebb7e28d84e590387aa9..942ed60ec25c013ae9184562c2c05291594a3d56 100644
--- a/frontend/test/modes/drills/UnderlyingRecordsDrill.unit.spec.js
+++ b/frontend/test/modes/drills/UnderlyingRecordsDrill.unit.spec.js
@@ -18,7 +18,7 @@ function getActionPropsForTimeseriesClick(unit, value) {
     question: question
       .query()
       .setQuery({
-        source_table: ORDERS_TABLE_ID,
+        "source-table": ORDERS_TABLE_ID,
         aggregation: [["count"]],
         breakout: [
           [
@@ -54,7 +54,7 @@ describe("UnderlyingRecordsDrill", () => {
     expect(actions).toHaveLength(1);
     const q = actions[0].question();
     expect(q.query().query()).toEqual({
-      source_table: ORDERS_TABLE_ID,
+      "source-table": ORDERS_TABLE_ID,
       filter: [
         "=",
         [
@@ -87,7 +87,7 @@ describe("UnderlyingRecordsDrill", () => {
       null,
     );
     expect(queryWithoutFilterValue).toEqual({
-      source_table: ORDERS_TABLE_ID,
+      "source-table": ORDERS_TABLE_ID,
       filter: [
         "=",
         [
diff --git a/frontend/test/modes/drills/ZoomDrill.unit.spec.js b/frontend/test/modes/drills/ZoomDrill.unit.spec.js
index e8686b435ee144d9f2bdeb634ecfed68b29b63ae..7c61097d287fcd60ea33c7d38e1fa60f4cb44fdf 100644
--- a/frontend/test/modes/drills/ZoomDrill.unit.spec.js
+++ b/frontend/test/modes/drills/ZoomDrill.unit.spec.js
@@ -21,7 +21,7 @@ describe("ZoomDrill", () => {
       question: question
         .query()
         .setQuery({
-          source_table: ORDERS_TABLE_ID,
+          "source-table": ORDERS_TABLE_ID,
           aggregation: [["count"]],
           breakout: [
             [
@@ -45,7 +45,7 @@ describe("ZoomDrill", () => {
     expect(actions).toHaveLength(1);
     const newCard = actions[0].question().card();
     expect(newCard.dataset_query.query).toEqual({
-      source_table: ORDERS_TABLE_ID,
+      "source-table": ORDERS_TABLE_ID,
       aggregation: [["count"]],
       filter: [
         "=",
diff --git a/frontend/test/nav/ProfileLink.unit.spec.js b/frontend/test/nav/ProfileLink.unit.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..d6a70d225a5fe9cc7e731535d0b4a732391b12c8
--- /dev/null
+++ b/frontend/test/nav/ProfileLink.unit.spec.js
@@ -0,0 +1,31 @@
+import React from "react";
+import { shallow } from "enzyme";
+import ProfileLink from "metabase/nav/components/ProfileLink";
+
+jest.mock("metabase/lib/settings", () => ({
+  get: () => ({
+    tag: 1,
+    version: 1,
+  }),
+}));
+
+describe("ProfileLink", () => {
+  describe("options", () => {
+    describe("normal user", () => {
+      it("should show the proper set of items", () => {
+        const normalUser = { is_superuser: false };
+        const wrapper = shallow(<ProfileLink user={normalUser} context={""} />);
+
+        expect(wrapper.instance().generateOptionsForUser().length).toBe(4);
+      });
+    });
+    describe("admin", () => {
+      it("should show the proper set of items", () => {
+        const admin = { is_superuser: true };
+        const wrapper = shallow(<ProfileLink user={admin} context={""} />);
+
+        expect(wrapper.instance().generateOptionsForUser().length).toBe(6);
+      });
+    });
+  });
+});
diff --git a/frontend/test/parameters/parameters.integ.spec.js b/frontend/test/parameters/parameters.integ.spec.js
index e2345ee3162dbbdbdf59cad22faa23bf5a90d3c7..65a9d05431f72abaa6494eea93a98cac234f48be 100644
--- a/frontend/test/parameters/parameters.integ.spec.js
+++ b/frontend/test/parameters/parameters.integ.spec.js
@@ -106,38 +106,38 @@ describe("parameters", () => {
         native: {
           query:
             "SELECT COUNT(*) FROM people WHERE {{id}} AND {{name}} AND {{source}} /* AND {{user_id}} */",
-          template_tags: {
+          "template-tags": {
             id: {
               id: "1",
               name: "id",
-              display_name: "ID",
+              "display-name": "ID",
               type: "dimension",
               dimension: ["field-id", PEOPLE_ID_FIELD_ID],
-              widget_type: "id",
+              "widget-type": "id",
             },
             name: {
               id: "2",
               name: "name",
-              display_name: "Name",
+              "display-name": "Name",
               type: "dimension",
               dimension: ["field-id", PEOPLE_NAME_FIELD_ID],
-              widget_type: "category",
+              "widget-type": "category",
             },
             source: {
               id: "3",
               name: "source",
-              display_name: "Source",
+              "display-name": "Source",
               type: "dimension",
               dimension: ["field-id", PEOPLE_SOURCE_FIELD_ID],
-              widget_type: "category",
+              "widget-type": "category",
             },
             user_id: {
               id: "4",
               name: "user_id",
-              display_name: "User",
+              "display-name": "User",
               type: "dimension",
               dimension: ["field-id", ORDER_USER_ID_FIELD_ID],
-              widget_type: "id",
+              "widget-type": "id",
             },
           },
         },
@@ -314,7 +314,7 @@ describe("parameters", () => {
       app = mount(store.getAppContainer());
 
       await store.waitForActions([FETCH_DASHBOARD]);
-      expect(app.find(".DashboardHeader .Entity h2").text()).toEqual(
+      expect(app.find(".DashboardHeader .Entity .h2").text()).toEqual(
         "Test Dashboard",
       );
 
diff --git a/frontend/test/public/public.integ.spec.js b/frontend/test/public/public.integ.spec.js
index 80df3537a35772216f29bc4b62de07455b3b1e7b..92fe0f1a851e66a6c3c17124d45a44ef44902b50 100644
--- a/frontend/test/public/public.integ.spec.js
+++ b/frontend/test/public/public.integ.spec.js
@@ -67,7 +67,6 @@ import * as Urls from "metabase/lib/urls";
 import QuestionEmbedWidget from "metabase/query_builder/containers/QuestionEmbedWidget";
 import EmbedWidget from "metabase/public/components/widgets/EmbedWidget";
 
-import Collections from "metabase/entities/collections";
 import { CardApi, DashboardApi, SettingsApi } from "metabase/services";
 
 const PEOPLE_TABLE_ID = 2;
@@ -223,7 +222,6 @@ describe("public/embedded", () => {
           .first()
           .find("a"),
       );
-      await store.waitForActions([Collections.actions.fetchList]);
 
       setInputValue(
         app.find(SaveQuestionModal).find("input[name='name']"),
@@ -397,7 +395,7 @@ describe("public/embedded", () => {
           type: "native",
           native: {
             query: "SELECT {{num}} AS num",
-            template_tags: {
+            "template-tags": {
               num: {
                 name: "num",
                 display_name: "Num",
@@ -419,7 +417,7 @@ describe("public/embedded", () => {
           database: 1,
           type: "query",
           query: {
-            source_table: PEOPLE_TABLE_ID,
+            "source-table": PEOPLE_TABLE_ID,
             aggregation: ["count"],
           },
         },
diff --git a/frontend/test/pulse/pulse.integ.spec.js b/frontend/test/pulse/pulse.integ.spec.js
index 9258d367a856a1670b6c2d0d2808a57c285b8d6c..6f807bb4b4b03b5d38204758b50eb5cbf73227d3 100644
--- a/frontend/test/pulse/pulse.integ.spec.js
+++ b/frontend/test/pulse/pulse.integ.spec.js
@@ -11,18 +11,14 @@ import { mount } from "enzyme";
 import { CardApi, PulseApi } from "metabase/services";
 import Question from "metabase-lib/lib/Question";
 
-import PulseListApp from "metabase/pulse/containers/PulseListApp";
 import PulseEditApp from "metabase/pulse/containers/PulseEditApp";
-import PulseListItem from "metabase/pulse/components/PulseListItem";
-import CardPicker from "metabase/pulse/components/CardPicker";
+import QuestionSelect from "metabase/containers/QuestionSelect";
 import PulseCardPreview from "metabase/pulse/components/PulseCardPreview";
 import Toggle from "metabase/components/Toggle";
 
 import {
-  FETCH_PULSES,
   SET_EDITING_PULSE,
   SAVE_EDITING_PULSE,
-  FETCH_CARDS,
   FETCH_PULSE_CARD_PREVIEW,
 } from "metabase/pulse/actions";
 
@@ -83,19 +79,10 @@ describe("Pulse", () => {
     store = await createTestStore();
   });
 
-  it("should load pulses", async () => {
-    store.pushPath("/pulse");
-    const app = mount(store.connectContainer(<PulseListApp />));
-    await store.waitForActions([FETCH_PULSES]);
-
-    const items = app.find(PulseListItem);
-    expect(items.length).toBe(0);
-  });
-
   it("should load create pulse", async () => {
     store.pushPath("/pulse/create");
     const app = mount(store.connectContainer(<PulseEditApp />));
-    await store.waitForActions([SET_EDITING_PULSE, FETCH_CARDS]);
+    await store.waitForActions([SET_EDITING_PULSE]);
 
     // no previews yet
     expect(app.find(PulseCardPreview).length).toBe(0);
@@ -113,7 +100,7 @@ describe("Pulse", () => {
 
     // add count card
     app
-      .find(CardPicker)
+      .find(QuestionSelect)
       .first()
       .props()
       .onChange(questionCount.id());
@@ -121,7 +108,7 @@ describe("Pulse", () => {
 
     // add raw card
     app
-      .find(CardPicker)
+      .find(QuestionSelect)
       .first()
       .props()
       .onChange(questionRaw.id());
@@ -166,13 +153,4 @@ describe("Pulse", () => {
     expect(pulse.channels[0].channel_type).toBe("email");
     expect(pulse.channels[0].enabled).toBe(true);
   });
-
-  it("should load pulses", async () => {
-    store.pushPath("/pulse");
-    const app = mount(store.connectContainer(<PulseListApp />));
-    await store.waitForActions([FETCH_PULSES]);
-
-    const items = app.find(PulseListItem);
-    expect(items.length).toBe(1);
-  });
 });
diff --git a/frontend/test/query_builder/components/VisualizationSettings.integ.spec.js b/frontend/test/query_builder/components/VisualizationSettings.integ.spec.js
index 18076d55c217d35a28c4ad61f26a2b140d4ccc48..b75b248bee1acef873a86b83e74a9b3b668181d5 100644
--- a/frontend/test/query_builder/components/VisualizationSettings.integ.spec.js
+++ b/frontend/test/query_builder/components/VisualizationSettings.integ.spec.js
@@ -16,7 +16,6 @@ import {
 
 import { FETCH_TABLE_METADATA } from "metabase/redux/metadata";
 
-import CheckBox from "metabase/components/CheckBox";
 import RunButton from "metabase/query_builder/components/RunButton";
 
 import VisualizationSettings from "metabase/query_builder/components/VisualizationSettings";
@@ -65,17 +64,14 @@ describe("QueryBuilder", () => {
       const doneButton = settingsModal.find(".Button--primary");
       expect(doneButton.length).toBe(1);
 
-      const fieldsToIncludeCheckboxes = settingsModal.find(CheckBox);
-      expect(fieldsToIncludeCheckboxes.length).toBe(7);
+      const fieldsToIncludeRemoveButtons = settingsModal.find(".Icon-close");
+      expect(fieldsToIncludeRemoveButtons.length).toBe(6);
 
       click(
-        fieldsToIncludeCheckboxes.filterWhere(
-          checkbox =>
-            checkbox
-              .parent()
-              .find("span")
-              .text() === "Created At",
-        ),
+        settingsModal
+          .find("ColumnItem")
+          .findWhere(x => x.text() === "Created At")
+          .find(".Icon-close"),
       );
 
       expect(table.find('div[children="Created At"]').length).toBe(0);
diff --git a/frontend/test/query_builder/qb_drillthrough.integ.spec.js b/frontend/test/query_builder/qb_drillthrough.integ.spec.js
index c018316bc8da7da4168effa0c24f8d7fccc72c34..184371e10108461f02fe38dfc997cd3ba665477a 100644
--- a/frontend/test/query_builder/qb_drillthrough.integ.spec.js
+++ b/frontend/test/query_builder/qb_drillthrough.integ.spec.js
@@ -117,7 +117,7 @@ describe("QueryBuilder", () => {
             database: 1,
             type: "query",
             query: {
-              source_table: 1,
+              "source-table": 1,
               breakout: [["binning-strategy", ["field-id", 6], "num-bins", 50]],
               aggregation: [["count"]],
             },
@@ -169,7 +169,7 @@ describe("QueryBuilder", () => {
             database: 1,
             type: "query",
             query: {
-              source_table: 1,
+              "source-table": 1,
               breakout: [["fk->", 7, 19]],
               aggregation: [["count"]],
             },
@@ -222,7 +222,7 @@ describe("QueryBuilder", () => {
             database: 1,
             type: "query",
             query: {
-              source_table: 1,
+              "source-table": 1,
               breakout: [["binning-strategy", ["fk->", 7, 14], "default"]],
               aggregation: [["count"]],
             },
diff --git a/frontend/test/query_builder/qb_visualizations.integ.spec.js b/frontend/test/query_builder/qb_visualizations.integ.spec.js
index e6cfd422f1aaa4f526ab57da0a155da9bc98357d..d73414368ef1485414936b4e6af12dc6b0b03e06 100644
--- a/frontend/test/query_builder/qb_visualizations.integ.spec.js
+++ b/frontend/test/query_builder/qb_visualizations.integ.spec.js
@@ -26,8 +26,6 @@ import * as Urls from "metabase/lib/urls";
 import VisualizationSettings from "metabase/query_builder/components/VisualizationSettings";
 import Popover from "metabase/components/Popover";
 
-import Collections from "metabase/entities/collections";
-
 const timeBreakoutQuestion = Question.create({
   databaseId: 1,
   tableId: 1,
@@ -78,8 +76,6 @@ describe("Query Builder visualization logic", () => {
         .find("a"),
     );
 
-    await store.waitForActions([Collections.actions.fetchList]);
-
     setInputValue(
       app.find(SaveQuestionModal).find("input[name='name']"),
       "test visualization question",
diff --git a/frontend/test/redux/store.unit.spec.js b/frontend/test/redux/store.unit.spec.js
deleted file mode 100644
index 92d9560936ffac01ce1f69f111418be3f20ca480..0000000000000000000000000000000000000000
--- a/frontend/test/redux/store.unit.spec.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import { trackEvent } from "metabase/store";
-import MetabaseAnalytics from "metabase/lib/analytics";
-
-jest.mock("metabase/lib/analytics", () => ({
-  trackEvent: jest.fn(),
-}));
-
-// fake next for redux
-const next = jest.fn();
-
-describe("store", () => {
-  describe("trackEvent", () => {
-    beforeEach(() => {
-      jest.resetAllMocks();
-    });
-    it("should call MetabaseAnalytics with the proper custom values", () => {
-      const testAction = {
-        type: "metabase/test/ACTION_NAME",
-        meta: {
-          analytics: {
-            category: "cool",
-            action: "action",
-            label: "labeled",
-            value: "value",
-          },
-        },
-      };
-
-      trackEvent({})(next)(testAction);
-      expect(MetabaseAnalytics.trackEvent).toHaveBeenCalledTimes(1);
-      expect(MetabaseAnalytics.trackEvent).toHaveBeenCalledWith(
-        "cool",
-        "action",
-        "labeled",
-        "value",
-      );
-    });
-    it("should ignore actions if ignore is true", () => {
-      const testAction = {
-        type: "metabase/test/ACTION_NAME",
-        meta: {
-          analytics: {
-            ignore: true,
-          },
-        },
-      };
-
-      trackEvent({})(next)(testAction);
-      expect(MetabaseAnalytics.trackEvent).toHaveBeenCalledTimes(0);
-    });
-
-    it("should use the action name if no analytics action is present", () => {
-      const testAction = {
-        type: "metabase/test/ACTION_NAME",
-      };
-
-      trackEvent({})(next)(testAction);
-      expect(MetabaseAnalytics.trackEvent).toHaveBeenCalledWith(
-        "test",
-        "ACTION_NAME",
-      );
-    });
-  });
-});
diff --git a/frontend/test/reference/databases.integ.spec.js b/frontend/test/reference/databases.integ.spec.js
index 5b8d68b81fb6ce0a7b314b2e1beb4f5dacd1c17f..cf78eb34c9e233633749c263b62993616fc424a0 100644
--- a/frontend/test/reference/databases.integ.spec.js
+++ b/frontend/test/reference/databases.integ.spec.js
@@ -41,9 +41,8 @@ describe("The Reference Section", () => {
     display: "scalar",
     dataset_query: {
       database: 1,
-      table_id: 1,
       type: "query",
-      query: { source_table: 1, aggregation: ["count"] },
+      query: { "source-table": 1, aggregation: ["count"] },
     },
     visualization_settings: {},
   };
diff --git a/frontend/test/reference/guide.integ.spec.js b/frontend/test/reference/guide.integ.spec.js
index 29a798baa6bb9452f113c58f84e4da92e031e7a3..daaea07e877ddb9f169366d0f2f1193dad218d05 100644
--- a/frontend/test/reference/guide.integ.spec.js
+++ b/frontend/test/reference/guide.integ.spec.js
@@ -24,7 +24,7 @@ describe("The Reference Section", () => {
     table_id: 1,
     show_in_getting_started: true,
     definition: {
-      source_table: 1,
+      "source-table": 1,
       filter: ["time-interval", ["field-id", 1], -30, "day"],
     },
   };
@@ -35,7 +35,7 @@ describe("The Reference Section", () => {
     table_id: 1,
     show_in_getting_started: true,
     definition: {
-      source_table: 1,
+      "source-table": 1,
       filter: ["time-interval", ["field-id", 1], -30, "day"],
     },
   };
diff --git a/frontend/test/reference/metrics.integ.spec.js b/frontend/test/reference/metrics.integ.spec.js
index 642a1a52a068dc83de5c0f3417e254946261899c..f8bdf19752855b157ef90681c524b389868ebb3d 100644
--- a/frontend/test/reference/metrics.integ.spec.js
+++ b/frontend/test/reference/metrics.integ.spec.js
@@ -5,6 +5,7 @@ import {
 
 import React from "react";
 import { mount } from "enzyme";
+import { assocIn } from "icepick";
 
 import { CardApi, MetricApi } from "metabase/services";
 
@@ -21,6 +22,8 @@ import MetricDetailContainer from "metabase/reference/metrics/MetricDetailContai
 import MetricQuestionsContainer from "metabase/reference/metrics/MetricQuestionsContainer";
 import MetricRevisionsContainer from "metabase/reference/metrics/MetricRevisionsContainer";
 
+// NOTE: database/table_id/source-table are hard-coded, this might be a problem at some point
+
 describe("The Reference Section", () => {
   // Test data
   const metricDef = {
@@ -28,7 +31,7 @@ describe("The Reference Section", () => {
     description: "I did it!",
     table_id: 1,
     show_in_getting_started: true,
-    definition: { database: 1, query: { aggregation: ["count"] } },
+    definition: { aggregation: [["count"]] },
   };
 
   const anotherMetricDef = {
@@ -36,7 +39,7 @@ describe("The Reference Section", () => {
     description: "I did it again!",
     table_id: 1,
     show_in_getting_started: true,
-    definition: { database: 1, query: { aggregation: ["count"] } },
+    definition: { aggregation: [["count"]] },
   };
 
   const metricCardDef = {
@@ -44,9 +47,8 @@ describe("The Reference Section", () => {
     display: "scalar",
     dataset_query: {
       database: 1,
-      table_id: 1,
       type: "query",
-      query: { source_table: 1, aggregation: ["metric", 1] },
+      query: { "source-table": 1, aggregation: [["metric", 1]] },
     },
     visualization_settings: {},
   };
@@ -116,10 +118,16 @@ describe("The Reference Section", () => {
       });
 
       it("Should see a newly asked question in its questions list", async () => {
-        let card = await CardApi.create(metricCardDef);
-        expect(card.name).toBe(metricCardDef.name);
-
+        let card;
         try {
+          const cardDef = assocIn(
+            metricCardDef,
+            ["dataset_query", "query", "aggregation", 0, 1],
+            metricIds[0],
+          );
+          card = await CardApi.create(cardDef);
+          expect(card.name).toBe(metricCardDef.name);
+
           // see that there is a new question on the metric's questions page
           const store = await createTestStore();
 
@@ -127,8 +135,10 @@ describe("The Reference Section", () => {
           mount(store.connectContainer(<MetricQuestionsContainer />));
           await store.waitForActions([FETCH_METRICS, FETCH_METRIC_TABLE]);
         } finally {
-          // even if the code above results in an exception, try to delete the question
-          await CardApi.delete({ cardId: card.id });
+          if (card) {
+            // even if the code above results in an exception, try to delete the question
+            await CardApi.delete({ cardId: card.id });
+          }
         }
       });
     });
diff --git a/frontend/test/reference/segments.integ.spec.js b/frontend/test/reference/segments.integ.spec.js
index c6dea3b2735da859fd670fcfe12fea5dc0170657..7fe95380e460b94fdb4b295eddfa10fb77243748 100644
--- a/frontend/test/reference/segments.integ.spec.js
+++ b/frontend/test/reference/segments.integ.spec.js
@@ -5,6 +5,7 @@ import {
 
 import React from "react";
 import { mount } from "enzyme";
+import { assocIn } from "icepick";
 
 import { CardApi, SegmentApi } from "metabase/services";
 
@@ -24,6 +25,8 @@ import SegmentRevisionsContainer from "metabase/reference/segments/SegmentRevisi
 import SegmentFieldListContainer from "metabase/reference/segments/SegmentFieldListContainer";
 import SegmentFieldDetailContainer from "metabase/reference/segments/SegmentFieldDetailContainer";
 
+// NOTE: database/table_id/source-table are hard-coded, this might be a problem at some point
+
 describe("The Reference Section", () => {
   // Test data
   const segmentDef = {
@@ -32,7 +35,7 @@ describe("The Reference Section", () => {
     table_id: 1,
     show_in_getting_started: true,
     definition: {
-      source_table: 1,
+      "source-table": 1,
       filter: ["time-interval", ["field-id", 1], -30, "day"],
     },
   };
@@ -43,7 +46,7 @@ describe("The Reference Section", () => {
     table_id: 1,
     show_in_getting_started: true,
     definition: {
-      source_table: 1,
+      "source-table": 1,
       filter: ["time-interval", ["field-id", 1], -15, "day"],
     },
   };
@@ -53,10 +56,9 @@ describe("The Reference Section", () => {
     display: "scalar",
     dataset_query: {
       database: 1,
-      table_id: 1,
       type: "query",
       query: {
-        source_table: 1,
+        "source-table": 1,
         aggregation: ["count"],
         filter: ["segment", 1],
       },
@@ -154,7 +156,12 @@ describe("The Reference Section", () => {
       });
 
       it("Should see a newly asked question in its questions list", async () => {
-        let card = await CardApi.create(segmentCardDef);
+        const cardDef = assocIn(
+          segmentCardDef,
+          ["dataset_query", "query", "filter", 1],
+          segmentIds[0],
+        );
+        let card = await CardApi.create(cardDef);
 
         expect(card.name).toBe(segmentCardDef.name);
 
diff --git a/frontend/test/reference/utils.unit.spec.js b/frontend/test/reference/utils.unit.spec.js
index ead5a1460bbccb126de3257d36355a46211e46d1..04911a2a1674f22f7566b06d323f851056295a97 100644
--- a/frontend/test/reference/utils.unit.spec.js
+++ b/frontend/test/reference/utils.unit.spec.js
@@ -106,7 +106,7 @@ describe("Reference utils.js", () => {
           database: database,
           type: "query",
           query: {
-            source_table: table,
+            "source-table": table,
           },
         },
       };
@@ -217,7 +217,7 @@ describe("Reference utils.js", () => {
 
       expect(question).toEqual(
         getNewQuestion({
-          aggregation: ["METRIC", 3],
+          aggregation: ["metric", 3],
         }),
       );
     });
@@ -232,7 +232,7 @@ describe("Reference utils.js", () => {
 
       expect(question).toEqual(
         getNewQuestion({
-          aggregation: ["METRIC", 3],
+          aggregation: ["metric", 3],
           breakout: [4],
         }),
       );
@@ -249,7 +249,7 @@ describe("Reference utils.js", () => {
         getNewQuestion({
           database: 2,
           table: 3,
-          filter: ["AND", ["SEGMENT", 4]],
+          filter: ["and", ["segment", 4]],
         }),
       );
     });
@@ -267,7 +267,7 @@ describe("Reference utils.js", () => {
           database: 2,
           table: 3,
           aggregation: ["count"],
-          filter: ["AND", ["SEGMENT", 4]],
+          filter: ["and", ["segment", 4]],
         }),
       );
     });
diff --git a/frontend/test/services/__snapshots__/MetabaseApi.integ.spec.js.snap b/frontend/test/services/__snapshots__/MetabaseApi.integ.spec.js.snap
index 3362c20ec0071920e516f8466f5664c55675417a..2b07502c1e721d33271a72dadfd8008cde997fb6 100644
--- a/frontend/test/services/__snapshots__/MetabaseApi.integ.spec.js.snap
+++ b/frontend/test/services/__snapshots__/MetabaseApi.integ.spec.js.snap
@@ -429,12 +429,12 @@ Object {
         },
         "type": Object {
           "type/DateTime": Object {
-            "earliest": "2016-04-30T00:00:00.000-07:00",
-            "latest": "2020-04-19T00:00:00.000-07:00",
+            "earliest": "2016-04-30T07:00:00.000Z",
+            "latest": "2020-04-19T07:00:00.000Z",
           },
         },
       },
-      "fingerprint_version": 2,
+      "fingerprint_version": 3,
       "fk_target_field_id": null,
       "has_field_values": "none",
       "id": 1,
@@ -445,7 +445,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": 9,
       "special_type": "type/CreationTimestamp",
       "table_id": 1,
       "target": null,
@@ -537,7 +536,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": null,
       "special_type": "type/Discount",
       "table_id": 1,
       "target": null,
@@ -576,7 +574,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": 10,
       "special_type": "type/PK",
       "table_id": 1,
       "target": null,
@@ -615,7 +612,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": 11,
       "special_type": "type/FK",
       "table_id": 1,
       "target": Object {
@@ -648,7 +644,6 @@ Object {
         "points_of_interest": null,
         "position": 0,
         "preview_display": true,
-        "raw_column_id": 4,
         "special_type": "type/PK",
         "table_id": 3,
         "visibility_type": "normal",
@@ -741,7 +736,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": null,
       "special_type": "type/Quantity",
       "table_id": 1,
       "target": null,
@@ -833,7 +827,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": 12,
       "special_type": null,
       "table_id": 1,
       "target": null,
@@ -925,7 +918,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": 13,
       "special_type": null,
       "table_id": 1,
       "target": null,
@@ -1017,7 +1009,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": 14,
       "special_type": "type/Income",
       "table_id": 1,
       "target": null,
@@ -1056,7 +1047,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": 15,
       "special_type": "type/FK",
       "table_id": 1,
       "target": Object {
@@ -1089,7 +1079,6 @@ Object {
         "points_of_interest": null,
         "position": 0,
         "preview_display": true,
-        "raw_column_id": 21,
         "special_type": "type/PK",
         "table_id": 2,
         "visibility_type": "normal",
@@ -1101,7 +1090,6 @@ Object {
   "metrics": Array [],
   "name": "ORDERS",
   "points_of_interest": null,
-  "raw_table_id": 2,
   "rows": 12805,
   "schema": "PUBLIC",
   "segments": Array [],
@@ -1413,7 +1401,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": 16,
       "special_type": null,
       "table_id": 2,
       "target": null,
@@ -1579,12 +1566,12 @@ Object {
         },
         "type": Object {
           "type/DateTime": Object {
-            "earliest": "1958-04-26T00:00:00.000-08:00",
-            "latest": "2000-04-03T00:00:00.000-07:00",
+            "earliest": "1958-04-26T08:00:00.000Z",
+            "latest": "2000-04-03T07:00:00.000Z",
           },
         },
       },
-      "fingerprint_version": 2,
+      "fingerprint_version": 3,
       "fk_target_field_id": null,
       "has_field_values": "none",
       "id": 9,
@@ -1595,7 +1582,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": 17,
       "special_type": null,
       "table_id": 2,
       "target": null,
@@ -1635,7 +1621,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": 18,
       "special_type": "type/City",
       "table_id": 2,
       "target": null,
@@ -1801,12 +1786,12 @@ Object {
         },
         "type": Object {
           "type/DateTime": Object {
-            "earliest": "2016-04-19T00:00:00.000-07:00",
-            "latest": "2019-04-19T00:00:00.000-07:00",
+            "earliest": "2016-04-19T07:00:00.000Z",
+            "latest": "2019-04-19T07:00:00.000Z",
           },
         },
       },
-      "fingerprint_version": 2,
+      "fingerprint_version": 3,
       "fk_target_field_id": null,
       "has_field_values": "none",
       "id": 11,
@@ -1817,7 +1802,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": 19,
       "special_type": "type/CreationTimestamp",
       "table_id": 2,
       "target": null,
@@ -1857,7 +1841,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": 20,
       "special_type": "type/Email",
       "table_id": 2,
       "target": null,
@@ -1896,7 +1879,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": 21,
       "special_type": "type/PK",
       "table_id": 2,
       "target": null,
@@ -1998,7 +1980,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": 22,
       "special_type": "type/Latitude",
       "table_id": 2,
       "target": null,
@@ -2100,7 +2081,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": 23,
       "special_type": "type/Longitude",
       "table_id": 2,
       "target": null,
@@ -2140,7 +2120,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": 24,
       "special_type": "type/Name",
       "table_id": 2,
       "target": null,
@@ -2180,7 +2159,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": 25,
       "special_type": null,
       "table_id": 2,
       "target": null,
@@ -2220,7 +2198,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": 26,
       "special_type": "type/Source",
       "table_id": 2,
       "target": null,
@@ -2260,7 +2237,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": 27,
       "special_type": "type/State",
       "table_id": 2,
       "target": null,
@@ -2300,7 +2276,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": 28,
       "special_type": "type/ZipCode",
       "table_id": 2,
       "target": null,
@@ -2311,7 +2286,6 @@ Object {
   "metrics": Array [],
   "name": "PEOPLE",
   "points_of_interest": null,
-  "raw_table_id": 3,
   "rows": 2500,
   "schema": "PUBLIC",
   "segments": Array [],
@@ -2623,7 +2597,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": 1,
       "special_type": "type/Category",
       "table_id": 3,
       "target": null,
@@ -2789,12 +2762,12 @@ Object {
         },
         "type": Object {
           "type/DateTime": Object {
-            "earliest": "2016-04-26T00:00:00.000-07:00",
-            "latest": "2019-04-15T00:00:00.000-07:00",
+            "earliest": "2016-04-26T07:00:00.000Z",
+            "latest": "2019-04-15T07:00:00.000Z",
           },
         },
       },
-      "fingerprint_version": 2,
+      "fingerprint_version": 3,
       "fk_target_field_id": null,
       "has_field_values": "none",
       "id": 22,
@@ -2805,7 +2778,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": 2,
       "special_type": "type/CreationTimestamp",
       "table_id": 3,
       "target": null,
@@ -2845,7 +2817,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": 3,
       "special_type": null,
       "table_id": 3,
       "target": null,
@@ -2884,7 +2855,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": 4,
       "special_type": "type/PK",
       "table_id": 3,
       "target": null,
@@ -2976,7 +2946,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": 5,
       "special_type": null,
       "table_id": 3,
       "target": null,
@@ -3068,7 +3037,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": 6,
       "special_type": "type/Score",
       "table_id": 3,
       "target": null,
@@ -3108,7 +3076,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": 7,
       "special_type": "type/Title",
       "table_id": 3,
       "target": null,
@@ -3148,7 +3115,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": 8,
       "special_type": "type/Company",
       "table_id": 3,
       "target": null,
@@ -3159,7 +3125,6 @@ Object {
   "metrics": Array [],
   "name": "PRODUCTS",
   "points_of_interest": null,
-  "raw_table_id": 1,
   "rows": 200,
   "schema": "PUBLIC",
   "segments": Array [],
@@ -3471,7 +3436,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": false,
-      "raw_column_id": 31,
       "special_type": "type/Description",
       "table_id": 4,
       "target": null,
@@ -3637,12 +3601,12 @@ Object {
         },
         "type": Object {
           "type/DateTime": Object {
-            "earliest": "2016-06-03T00:00:00.000-07:00",
-            "latest": "2020-04-19T00:00:00.000-07:00",
+            "earliest": "2016-06-03T07:00:00.000Z",
+            "latest": "2020-04-19T07:00:00.000Z",
           },
         },
       },
-      "fingerprint_version": 2,
+      "fingerprint_version": 3,
       "fk_target_field_id": null,
       "has_field_values": "none",
       "id": 30,
@@ -3653,7 +3617,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": 32,
       "special_type": "type/CreationTimestamp",
       "table_id": 4,
       "target": null,
@@ -3692,7 +3655,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": 33,
       "special_type": "type/PK",
       "table_id": 4,
       "target": null,
@@ -3731,7 +3693,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": 34,
       "special_type": "type/FK",
       "table_id": 4,
       "target": Object {
@@ -3764,7 +3725,6 @@ Object {
         "points_of_interest": null,
         "position": 0,
         "preview_display": true,
-        "raw_column_id": 4,
         "special_type": "type/PK",
         "table_id": 3,
         "visibility_type": "normal",
@@ -3857,7 +3817,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": 35,
       "special_type": "type/Score",
       "table_id": 4,
       "target": null,
@@ -3897,7 +3856,6 @@ Object {
       "points_of_interest": null,
       "position": 0,
       "preview_display": true,
-      "raw_column_id": 36,
       "special_type": null,
       "table_id": 4,
       "target": null,
@@ -3908,7 +3866,6 @@ Object {
   "metrics": Array [],
   "name": "REVIEWS",
   "points_of_interest": null,
-  "raw_table_id": 5,
   "rows": 984,
   "schema": "PUBLIC",
   "segments": Array [],
diff --git a/frontend/test/visualizations/components/ChartSettings.unit.spec.js b/frontend/test/visualizations/components/ChartSettings.unit.spec.js
deleted file mode 100644
index 1729473d62e72e8cead0782338c111f02748057e..0000000000000000000000000000000000000000
--- a/frontend/test/visualizations/components/ChartSettings.unit.spec.js
+++ /dev/null
@@ -1,81 +0,0 @@
-import React from "react";
-
-import ChartSettings from "metabase/visualizations/components/ChartSettings";
-
-import { TableCard } from "../__support__/visualizations";
-
-import { mount } from "enzyme";
-import { click } from "__support__/enzyme_utils";
-
-function renderChartSettings(enabled = true) {
-  const props = {
-    series: [
-      TableCard("Foo", {
-        card: {
-          visualization_settings: {
-            "table.columns": [{ name: "Foo_col0", enabled: enabled }],
-          },
-        },
-      }),
-    ],
-  };
-  return mount(<ChartSettings {...props} onChange={() => {}} />);
-}
-
-// The ExplicitSize component uses the matchMedia DOM API
-// which does not exist in jest's JSDOM
-Object.defineProperty(window, "matchMedia", {
-  value: jest.fn(() => {
-    return {
-      matches: true,
-      addListener: () => {},
-      removeListener: () => {},
-    };
-  }),
-});
-
-// We have to do some mocking here to avoid calls to GA and to Metabase settings
-jest.mock("metabase/lib/settings", () => ({
-  get: () => "v",
-}));
-
-describe("ChartSettings", () => {
-  describe("toggling fields", () => {
-    describe("disabling all fields", () => {
-      it("should show null state", () => {
-        const chartSettings = renderChartSettings();
-
-        expect(chartSettings.find(".toggle-all .Icon-check").length).toEqual(1);
-        expect(chartSettings.find("table").length).toEqual(1);
-
-        click(chartSettings.find(".toggle-all .cursor-pointer"));
-
-        expect(chartSettings.find(".toggle-all .Icon-check").length).toEqual(0);
-        expect(chartSettings.find("table").length).toEqual(0);
-        expect(chartSettings.text()).toContain(
-          "Every field is hidden right now",
-        );
-      });
-    });
-
-    describe("enabling all fields", () => {
-      it("should show all columns", () => {
-        const chartSettings = renderChartSettings(false);
-
-        expect(chartSettings.find(".toggle-all .Icon-check").length).toEqual(0);
-        expect(chartSettings.find("table").length).toEqual(0);
-        expect(chartSettings.text()).toContain(
-          "Every field is hidden right now",
-        );
-
-        click(chartSettings.find(".toggle-all .cursor-pointer"));
-
-        expect(chartSettings.find(".toggle-all .Icon-check").length).toEqual(1);
-        expect(chartSettings.find("table").length).toEqual(1);
-        expect(chartSettings.text()).not.toContain(
-          "Every field is hidden right now",
-        );
-      });
-    });
-  });
-});
diff --git a/frontend/test/visualizations/components/LineAreaBarChart.unit.spec.js b/frontend/test/visualizations/components/LineAreaBarChart.unit.spec.js
index 4f28fd16a705d92d46d09a8175efd5990ddddb6c..d0a763602a58eab5886090adf69ee5c98c329c1c 100644
--- a/frontend/test/visualizations/components/LineAreaBarChart.unit.spec.js
+++ b/frontend/test/visualizations/components/LineAreaBarChart.unit.spec.js
@@ -51,7 +51,7 @@ const millisecondCard = {
       database: 5,
       type: "query",
       query: {
-        source_table: 1784,
+        "source-table": 1784,
         aggregation: [["count"]],
         breakout: [["datetime-field", ["field-id", 8159], "week"]],
       },
@@ -180,7 +180,7 @@ const dateTimeCard = {
       database: 1,
       type: "query",
       query: {
-        source_table: 1,
+        "source-table": 1,
         aggregation: [["sum", ["field-id", 4]]],
         breakout: [["datetime-field", ["field-id", 1], "month"]],
       },
@@ -358,7 +358,7 @@ const numberCard = {
       database: 1,
       type: "query",
       query: {
-        source_table: 4,
+        "source-table": 4,
         aggregation: [["count"]],
         breakout: [["field-id", 33]],
       },
diff --git a/frontend/test/visualizations/components/settings/ChartSettingOrderedColumns.unit.spec.js b/frontend/test/visualizations/components/settings/ChartSettingOrderedColumns.unit.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..b2cb958d61e38712f372c140ea29cb4fe5c45c46
--- /dev/null
+++ b/frontend/test/visualizations/components/settings/ChartSettingOrderedColumns.unit.spec.js
@@ -0,0 +1,84 @@
+import React from "react";
+
+import ChartSettingOrderedColumns from "metabase/visualizations/components/settings/ChartSettingOrderedColumns";
+
+import { mount } from "enzyme";
+
+import { question } from "../../../__support__/sample_dataset_fixture.js";
+
+function renderChartSettingOrderedColumns(props) {
+  return mount(
+    <ChartSettingOrderedColumns
+      onChange={() => {}}
+      columns={[{ name: "Foo" }, { name: "Bar" }]}
+      {...props}
+    />,
+  );
+}
+
+describe("ChartSettingOrderedColumns", () => {
+  it("should have the correct add and remove buttons", () => {
+    const setting = renderChartSettingOrderedColumns({
+      value: [{ name: "Foo", enabled: true }, { name: "Bar", enabled: false }],
+    });
+    expect(setting.find(".Icon-add")).toHaveLength(1);
+    expect(setting.find(".Icon-close")).toHaveLength(1);
+  });
+  it("should add a column", () => {
+    const onChange = jest.fn();
+    const setting = renderChartSettingOrderedColumns({
+      value: [{ name: "Foo", enabled: true }, { name: "Bar", enabled: false }],
+      onChange,
+    });
+    setting.find(".Icon-add").simulate("click");
+    expect(onChange.mock.calls).toEqual([
+      [[{ name: "Foo", enabled: true }, { name: "Bar", enabled: true }]],
+    ]);
+  });
+  it("should remove a column", () => {
+    const onChange = jest.fn();
+    const setting = renderChartSettingOrderedColumns({
+      value: [{ name: "Foo", enabled: true }, { name: "Bar", enabled: false }],
+      onChange,
+    });
+    setting.find(".Icon-close").simulate("click");
+    expect(onChange.mock.calls).toEqual([
+      [[{ name: "Foo", enabled: false }, { name: "Bar", enabled: false }]],
+    ]);
+  });
+  it("should reorder columns", () => {
+    const onChange = jest.fn();
+    const setting = renderChartSettingOrderedColumns({
+      value: [{ name: "Foo", enabled: true }, { name: "Bar", enabled: true }],
+      onChange,
+    });
+    // just call handleSortEnd directly for now as it's difficult to simulate drag and drop
+    setting.instance().handleSortEnd({ oldIndex: 1, newIndex: 0 });
+    expect(onChange.mock.calls).toEqual([
+      [[{ name: "Bar", enabled: true }, { name: "Foo", enabled: true }]],
+    ]);
+  });
+
+  describe("for structured queries", () => {
+    it("should list and add additional columns", () => {
+      const onChange = jest.fn();
+      const addField = jest.fn();
+      const setting = renderChartSettingOrderedColumns({
+        value: [],
+        columns: [],
+        question,
+        onChange,
+        addField,
+      });
+      expect(setting.find(".Icon-add")).toHaveLength(28);
+      setting
+        .find(".Icon-add")
+        .first()
+        .simulate("click");
+      expect(addField.mock.calls).toEqual([[["field-id", 1]]]);
+      expect(onChange.mock.calls).toEqual([
+        [[{ fieldRef: ["field-id", 1], enabled: true }]],
+      ]);
+    });
+  });
+});
diff --git a/frontend/test/visualizations/components/settings/ChartSettingOrderedFields.unit.spec.js b/frontend/test/visualizations/components/settings/ChartSettingOrderedFields.unit.spec.js
deleted file mode 100644
index 84cc84d4e8208c1af635c79955d2cf2d97b63c58..0000000000000000000000000000000000000000
--- a/frontend/test/visualizations/components/settings/ChartSettingOrderedFields.unit.spec.js
+++ /dev/null
@@ -1,79 +0,0 @@
-import React from "react";
-
-import ChartSettingOrderedFields from "metabase/visualizations/components/settings/ChartSettingOrderedFields";
-
-import { mount } from "enzyme";
-
-function renderChartSettingOrderedFields(props) {
-  return mount(<ChartSettingOrderedFields onChange={() => {}} {...props} />);
-}
-
-describe("ChartSettingOrderedFields", () => {
-  describe("isAnySelected", () => {
-    describe("when on or more fields are enabled", () => {
-      it("should be true", () => {
-        const chartSettings = renderChartSettingOrderedFields({
-          columnNames: { id: "ID", text: "Text" },
-          value: [
-            { name: "id", enabled: true },
-            { name: "text", enabled: false },
-          ],
-        });
-        expect(chartSettings.instance().isAnySelected()).toEqual(true);
-      });
-    });
-
-    describe("when no fields are enabled", () => {
-      it("should be false", () => {
-        const chartSettings = renderChartSettingOrderedFields({
-          columnNames: { id: "ID", text: "Text" },
-          value: [
-            { name: "id", enabled: false },
-            { name: "text", enabled: false },
-          ],
-        });
-        expect(chartSettings.instance().isAnySelected()).toEqual(false);
-      });
-    });
-  });
-
-  describe("toggleAll", () => {
-    describe("when passed false", () => {
-      it("should mark all fields as enabled", () => {
-        const onChange = jest.fn();
-        const chartSettings = renderChartSettingOrderedFields({
-          columnNames: { id: "ID", text: "Text" },
-          value: [
-            { name: "id", enabled: false },
-            { name: "text", enabled: false },
-          ],
-          onChange,
-        });
-        chartSettings.instance().handleToggleAll(false);
-        expect(onChange.mock.calls[0][0]).toEqual([
-          { name: "id", enabled: true },
-          { name: "text", enabled: true },
-        ]);
-      });
-    });
-
-    describe("when passed true", () => {
-      it("should mark all fields as disabled", () => {
-        const onChange = jest.fn();
-        const chartSettings = renderChartSettingOrderedFields({
-          columnNames: { id: "ID", text: "Text" },
-          value: [
-            { name: "id", enabled: true },
-            { name: "text", enabled: true },
-          ],
-          onChange,
-        });
-        chartSettings.instance().handleToggleAll(true);
-        expect(onChange.mock.calls[0][0]).toEqual([
-          { name: "id", enabled: false },
-          { name: "text", enabled: false },
-        ]);
-      });
-    });
-  });
-});
diff --git a/frontend/test/visualizations/lib/numeric.unit.spec.js b/frontend/test/visualizations/lib/numeric.unit.spec.js
index 5f8bef35de686994837ba2b1c72ab673502e339a..89cb3d0dc1c302893c5df6014521ff3cbf6c0454 100644
--- a/frontend/test/visualizations/lib/numeric.unit.spec.js
+++ b/frontend/test/visualizations/lib/numeric.unit.spec.js
@@ -1,6 +1,8 @@
 import {
   precision,
   computeNumericDataInverval,
+  isMultipleOf,
+  getModuloScaleFactor,
 } from "metabase/visualizations/lib/numeric";
 
 describe("visualization.lib.numeric", () => {
@@ -44,4 +46,34 @@ describe("visualization.lib.numeric", () => {
       });
     }
   });
+  describe("getModuloScaleFactor", () => {
+    [
+      [0.01, 100],
+      [0.05, 100],
+      [0.1, 10],
+      [1, 1],
+      [2, 1],
+      [10, 1],
+      [10 ** 10, 1],
+    ].map(([value, expected]) =>
+      it(`should return ${expected} for ${value}`, () =>
+        expect(getModuloScaleFactor(value)).toBe(expected)),
+    );
+  });
+  describe("isMultipleOf", () => {
+    [
+      [1, 0.1, true],
+      [1, 1, true],
+      [10, 1, true],
+      [1, 10, false],
+      [3, 1, true],
+      [0.3, 0.1, true],
+      [0.25, 0.1, false],
+      [0.000000001, 0.0000000001, true],
+      [0.0000000001, 0.000000001, false],
+    ].map(([value, base, expected]) =>
+      it(`${value} ${expected ? "is" : "is not"} a multiple of ${base}`, () =>
+        expect(isMultipleOf(value, base)).toBe(expected)),
+    );
+  });
 });
diff --git a/frontend/test/visualizations/lib/settings.unit.spec.js b/frontend/test/visualizations/lib/settings.unit.spec.js
index ede5c52cc1f58d039dc066c98a5f0acb9165ae56..f05ba1cc843b768545e4397542b11ce825fd773f 100644
--- a/frontend/test/visualizations/lib/settings.unit.spec.js
+++ b/frontend/test/visualizations/lib/settings.unit.spec.js
@@ -7,37 +7,78 @@ import { DateTimeColumn, NumberColumn } from "../__support__/visualizations";
 
 describe("visualization_settings", () => {
   describe("getSettings", () => {
-    it("should default to unstacked stacked", () => {
-      const settings = getSettings([
-        {
-          card: {
+    describe("stackable.stack_type", () => {
+      it("should default to unstacked stacked", () => {
+        const settings = getSettings(
+          cardWithTimeseriesBreakout({ unit: "month" }),
+        );
+        expect(settings["stackable.stack_type"]).toBe(null);
+      });
+      it("should default area chart to stacked for 1 dimensions and 2 metrics", () => {
+        const settings = getSettings(
+          cardWithTimeseriesBreakoutAndTwoMetrics({
             display: "area",
-            visualization_settings: {},
-          },
-          data: {
-            cols: [DateTimeColumn({ unit: "month" }), NumberColumn()],
-          },
-        },
-      ]);
-      expect(settings["stackable.stack_type"]).toBe(null);
+            unit: "month",
+          }),
+        );
+        expect(settings["stackable.stack_type"]).toBe("stacked");
+      });
     });
-    it("should default area chart to stacked for 1 dimensions and 2 metrics", () => {
-      const settings = getSettings([
-        {
-          card: {
-            display: "area",
-            visualization_settings: {},
-          },
-          data: {
-            cols: [
-              DateTimeColumn({ unit: "month" }),
-              NumberColumn(),
-              NumberColumn(),
-            ],
-          },
-        },
-      ]);
-      expect(settings["stackable.stack_type"]).toBe("stacked");
+    describe("graph.x_axis._is_histogram", () => {
+      // NOTE: currently datetimes with unit are never considered histograms
+      const HISTOGRAM_UNITS = [];
+      const NON_HISTOGRAM_UNITS = [
+        // definitely not histogram
+        "day-of-week",
+        "month-of-year",
+        "quarter-of-year",
+        // arguably histogram but diabled for now
+        "minute-of-hour",
+        "hour-of-day",
+        "day-of-month",
+        "day-of-year",
+        "week-of-year",
+      ];
+      describe("non-histgram units", () =>
+        NON_HISTOGRAM_UNITS.map(unit =>
+          it(`should default ${unit} to false`, () => {
+            const settings = getSettings(cardWithTimeseriesBreakout({ unit }));
+            expect(settings["graph.x_axis._is_histogram"]).toBe(false);
+          }),
+        ));
+      describe("histgram units", () =>
+        HISTOGRAM_UNITS.map(unit =>
+          it(`should default ${unit} to true`, () => {
+            const settings = getSettings(cardWithTimeseriesBreakout({ unit }));
+            expect(settings["graph.x_axis._is_histogram"]).toBe(true);
+          }),
+        ));
     });
   });
 });
+
+const cardWithTimeseriesBreakout = ({ unit, display = "bar" }) => [
+  {
+    card: {
+      display: display,
+      visualization_settings: {},
+    },
+    data: {
+      cols: [DateTimeColumn({ unit }), NumberColumn()],
+      rows: [[0, 0]],
+    },
+  },
+];
+
+const cardWithTimeseriesBreakoutAndTwoMetrics = ({ unit, display = "bar" }) => [
+  {
+    card: {
+      display: display,
+      visualization_settings: {},
+    },
+    data: {
+      cols: [DateTimeColumn({ unit }), NumberColumn(), NumberColumn()],
+      rows: [[0, 0, 0]],
+    },
+  },
+];
diff --git a/frontend/test/visualizations/lib/utils.unit.spec.js b/frontend/test/visualizations/lib/utils.unit.spec.js
index 4ca063cf1f75b3802fc97d0674fde350332f96dd..adfa4626ae8163fbd9042aa467397efe43522cc7 100644
--- a/frontend/test/visualizations/lib/utils.unit.spec.js
+++ b/frontend/test/visualizations/lib/utils.unit.spec.js
@@ -13,7 +13,7 @@ const baseQuery = {
   database: 1,
   type: "query",
   query: {
-    source_table: 2,
+    "source-table": 2,
     aggregation: [["count"]],
     breakout: [["field-id", 2]],
   },
diff --git a/locales/es.po b/locales/es.po
new file mode 100644
index 0000000000000000000000000000000000000000..fc5caabf1b17b4e8bab03b8ff3d0442f267295f8
--- /dev/null
+++ b/locales/es.po
@@ -0,0 +1,9301 @@
+msgid ""
+msgstr ""
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: POEditor.com\n"
+"Project-Id-Version: Metabase\n"
+"Language: es\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: frontend/src/metabase/admin/databases/components/CreatedDatabaseModal.jsx:19
+msgid "Your database has been added!"
+msgstr "¡Tu base de datos ha sido añadida!"
+
+#: frontend/src/metabase/admin/databases/components/CreatedDatabaseModal.jsx:22
+msgid "We took a look at your data, and we have some automated explorations that we can show you!"
+msgstr "Echamos un vistazo a tus datos, y tenemos algunas exploraciones automatizadas que podemos mostrarte!"
+
+#: frontend/src/metabase/admin/databases/components/CreatedDatabaseModal.jsx:27
+msgid "I'm good thanks"
+msgstr "Está bien, gracias"
+
+#: frontend/src/metabase/admin/databases/components/CreatedDatabaseModal.jsx:32
+msgid "Explore this data"
+msgstr "Explora estos datos"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseEditForms.jsx:42
+msgid "Select a database type"
+msgstr "Selecciona un tipo de base de datos"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseEditForms.jsx:75
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:170
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:435
+#: frontend/src/metabase/admin/permissions/containers/CollectionPermissionsModal.jsx:71
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:182
+#: frontend/src/metabase/components/ActionButton.jsx:51
+#: frontend/src/metabase/components/ButtonWithStatus.jsx:7
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:180
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:197
+#: frontend/src/metabase/reference/components/EditHeader.jsx:54
+#: frontend/src/metabase/reference/components/EditHeader.jsx:69
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:171
+#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:164
+msgid "Save"
+msgstr "Guarda"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:122
+msgid "To do some of its magic, Metabase needs to scan your database. We will also rescan it periodically to keep the metadata up-to-date. You can control when the periodic rescans happen below."
+msgstr "Para hacer algo de su magia, Metabase necesita escanear la base de datos. Lo volveremosa hacer periódicamente para mantener actualizados los metadatos. Puedes controlar cuandose realizan los repasos periódicos a continuación."
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:127
+msgid "Database syncing"
+msgstr "Sincronizando Base de Datos"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:128
+msgid "This is a lightweight process that checks for\n"
+"updates to this database’s schema. In most cases, you should be fine leaving this\n"
+"set to sync hourly."
+msgstr "Este es un proceso rápido que busca \n"
+"actualizaciones al esquema de esta base de datos. En la mayoría de los casos, estará bien dejandoesto \n"
+"configurado para sincronizar cada hora."
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:147
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:184
+msgid "Scan"
+msgstr "Explorar"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:152
+msgid "Scanning for Filter Values"
+msgstr "Buscando Valores de Filtrado"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:153
+msgid "Metabase can scan the values present in each\n"
+"field in this database to enable checkbox filters in dashboards and questions. This\n"
+"can be a somewhat resource-intensive process, particularly if you have a very large\n"
+"database."
+msgstr "Metabase puede escanear los valores presentes en cada \n"
+"campo en esta base de datos para habilitar filtros de casilla de verificación en cuadros de mando ypreguntas. Esto \n"
+"puede ser un proceso que consume muchos recursos, particularmente si tienes una \n"
+"base de datos grande."
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:159
+msgid "When should Metabase automatically scan and cache field values?"
+msgstr "¿Cuándo debería Metabase escanear y almacenar automáticamente los valores de campo?"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:164
+msgid "Regularly, on a schedule"
+msgstr "Regularmente, siguiendo un horario"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:195
+msgid "Only when adding a new filter widget"
+msgstr "Solo cuando se añade un nuevo elemento de filtro"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:199
+msgid "When a user adds a new filter to a dashboard or a SQL question, Metabase will\n"
+"scan the field(s) mapped to that filter in order to show the list of selectable values."
+msgstr "Cuando un usuario añade un nuevo filtro a un cuadro de mando o a una pregunta SQL, Metabaseescaneará\n"
+"los campos asignados a ese filtro para mostrar la lista devalores seleccionables."
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:210
+msgid "Never, I'll do this manually if I need to"
+msgstr "Nunca, lo haré manualmente si lo necesito"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:222
+#: frontend/src/metabase/admin/settings/components/SettingsBatchForm.jsx:27
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:222
+#: frontend/src/metabase/components/ActionButton.jsx:52
+#: frontend/src/metabase/components/ButtonWithStatus.jsx:8
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:429
+msgid "Saving..."
+msgstr "Guardando..."
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:38
+#: frontend/src/metabase/components/form/FormMessage.jsx:4
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:144
+msgid "Server error encountered"
+msgstr "Se ha encontrado un error en el servidor"
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:54
+msgid "Delete this database?"
+msgstr "¿Eliminar esta base de datos?"
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:62
+msgid "All saved questions, metrics, and segments that rely on this database will be lost."
+msgstr "Todas las preguntas, métricas y segmentos guardados que dependen de esta base de datosse perderán."
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:63
+msgid "This cannot be undone."
+msgstr "Esta acción no se puede deshacer."
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:66
+msgid "If you're sure, please type"
+msgstr "Si estás seguro, por favor teclea:"
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:66
+msgid "DELETE"
+msgstr "ELIMINAR"
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:67
+msgid "in this box:"
+msgstr "en esta casilla:"
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:78
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:50
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:87
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:93
+#: frontend/src/metabase/admin/people/components/AddRow.jsx:27
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:250
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:302
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:322
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:343
+#: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:49
+#: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:52
+#: frontend/src/metabase/admin/permissions/containers/CollectionPermissionsModal.jsx:58
+#: frontend/src/metabase/admin/permissions/selectors.js:156
+#: frontend/src/metabase/admin/permissions/selectors.js:166
+#: frontend/src/metabase/admin/permissions/selectors.js:181
+#: frontend/src/metabase/admin/permissions/selectors.js:220
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:355
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:181
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:247
+#: frontend/src/metabase/components/ConfirmContent.jsx:18
+#: frontend/src/metabase/components/DeleteModalWithConfirm.jsx:72
+#: frontend/src/metabase/components/HeaderModal.jsx:49
+#: frontend/src/metabase/components/form/StandardForm.jsx:55
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:196
+#: frontend/src/metabase/dashboard/components/AddSeriesModal.jsx:289
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:162
+#: frontend/src/metabase/dashboard/components/RemoveFromDashboardModal.jsx:42
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:189
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:192
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:352
+#: frontend/src/metabase/query_builder/components/RunButton.jsx:24
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:83
+#: frontend/src/metabase/query_builder/containers/ArchiveQuestionModal.jsx:48
+#: frontend/src/metabase/reference/components/EditHeader.jsx:34
+#: frontend/src/metabase/reference/components/RevisionMessageModal.jsx:52
+#: frontend/src/metabase/visualizations/components/ChartSettings.jsx:209
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:397
+msgid "Cancel"
+msgstr "Cancelar"
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:84
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:123
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:397
+msgid "Delete"
+msgstr "Eliminar"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:128
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:76
+#: frontend/src/metabase/admin/permissions/selectors.js:316
+#: frontend/src/metabase/admin/permissions/selectors.js:323
+#: frontend/src/metabase/admin/permissions/selectors.js:419
+#: frontend/src/metabase/admin/routes.jsx:39
+#: frontend/src/metabase/nav/containers/Navbar.jsx:215
+#: frontend/src/metabase/reference/databases/DatabaseSidebar.jsx:18
+#: frontend/src/metabase/reference/databases/TableSidebar.jsx:18
+msgid "Databases"
+msgstr "Bases de Datos"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:129
+msgid "Add Database"
+msgstr "Añadir Base de Datos"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:60
+msgid "Connection"
+msgstr "Conexión"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:61
+msgid "Scheduling"
+msgstr "Programación"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:170
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:78
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:84
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:253
+#: frontend/src/metabase/admin/settings/components/SettingsBatchForm.jsx:26
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:221
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:182
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:354
+#: frontend/src/metabase/reference/components/RevisionMessageModal.jsx:47
+msgid "Save changes"
+msgstr "Guardar cambios"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:185
+#: frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx:38
+#: frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx:38
+msgid "Actions"
+msgstr "Acciones"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:193
+msgid "Sync database schema now"
+msgstr "Sincronizar el esquema de la base de datos ahora"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:194
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:206
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:788
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:796
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:112
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:120
+msgid "Starting…"
+msgstr "Empezando…"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:195
+msgid "Failed to sync"
+msgstr "No se ha podido sincronizar"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:196
+msgid "Sync triggered!"
+msgstr "Sincronización iniciada!"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:205
+msgid "Re-scan field values now"
+msgstr "Vuelva a escanear los valores de campo ahora"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:207
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:789
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:113
+msgid "Failed to start scan"
+msgstr "No se ha podido iniciar la exploración"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:208
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:790
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:114
+msgid "Scan triggered!"
+msgstr "Exploración iniciada!"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:215
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:399
+msgid "Danger Zone"
+msgstr "Zona Peligrosa"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:221
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:224
+msgid "Discard saved field values"
+msgstr "Descartar valores de campo guardados"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:239
+msgid "Remove this database"
+msgstr "Eliminar esta base de datos"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:75
+msgid "Add database"
+msgstr "Añadir base de datos"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:87
+#: frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx:36
+#: frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx:36
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:468
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:183
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:91
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:402
+#: frontend/src/metabase/containers/EntitySearch.jsx:26
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:218
+#: frontend/src/metabase/entities/collections.js:86
+#: frontend/src/metabase/entities/dashboards.js:96
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:461
+#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:62
+msgid "Name"
+msgstr "Nombre"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:88
+msgid "Engine"
+msgstr "Motor"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:117
+msgid "Deleting..."
+msgstr "Eliminando..."
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:147
+msgid "Loading ..."
+msgstr "Cargando ..."
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:163
+msgid "Bring the sample dataset back"
+msgstr "Recuperar la base de datos de prueba"
+
+#: frontend/src/metabase/admin/databases/database.js:175
+msgid "Couldn't connect to the database. Please check the connection details."
+msgstr "No se ha podido conectar a la base de datos. Por favor, comprueba los detalles de la conexión."
+
+#: frontend/src/metabase/admin/databases/database.js:383
+msgid "Successfully created!"
+msgstr "¡Creado con éxito!"
+
+#: frontend/src/metabase/admin/databases/database.js:393
+msgid "Successfully saved!"
+msgstr "¡Guardado con éxito!"
+
+#. Editar
+#: frontend/src/metabase/admin/datamodel/components/ObjectActionSelect.jsx:44
+#: frontend/src/metabase/dashboard/components/DashCard.jsx:276
+#: frontend/src/metabase/parameters/components/ParameterWidget.jsx:173
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:209
+#: frontend/src/metabase/reference/components/EditButton.jsx:18
+msgid "Edit"
+msgstr "Editar"
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectActionSelect.jsx:59
+msgid "Revision History"
+msgstr "Historial de Revisiones"
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:33
+msgid "Retire this {0}?"
+msgstr "¿Retirar este {0}?"
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:38
+msgid "Saved questions and other things that depend on this {0} will continue to work, but this {1} will no longer be selectable from the query builder."
+msgstr "Las preguntas guardadas y otras cosas que dependen de este {0} continuarán funcionando, pero este {1} ya no se podrá seleccionar desde el generador de consultas."
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:39
+msgid "If you're sure you want to retire this {0}, please write a quick explanation of why it's being retired:"
+msgstr "Si estás seguro de querer retirar este {0}, escribe una explicación de por qué está siendo retirado:"
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:43
+msgid "This will show up in the activity feed and in an email that will be sent to anyone on your team who created something that uses this {0}."
+msgstr "Esto se mostrará en el resumen de actividades y en un correo electrónico que se enviará a cualquier persona de tu equipo que haya creado algo que use este {0}."
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:58
+msgid "Retire"
+msgstr "Retirar"
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:59
+msgid "Retiring…"
+msgstr "Retirando…"
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:60
+msgid "Failed"
+msgstr "Ha fallado"
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:61
+msgid "Success"
+msgstr "Exito"
+
+#: frontend/src/metabase/admin/datamodel/components/PartialQueryBuilder.jsx:118
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:110
+msgid "Preview"
+msgstr "Vista Preliminar"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnItem.jsx:97
+msgid "No column description yet"
+msgstr "No hay una descripción de la columna"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnItem.jsx:133
+msgid "Select a field visibility"
+msgstr "Selecciona una visibilidad de campo"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnItem.jsx:189
+msgid "No special type"
+msgstr "Sin tipo especial"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnItem.jsx:190
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:34
+#: frontend/src/metabase/reference/components/Field.jsx:57
+#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:42
+msgid "Other"
+msgstr "Otro"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnItem.jsx:208
+msgid "Select a special type"
+msgstr "Selección un tipo especial"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnItem.jsx:222
+msgid "Select a target"
+msgstr "Selecciona una referencia"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnsList.jsx:17
+msgid "Columns"
+msgstr "Columnas"
+
+#. Campo
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnsList.jsx:22
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataSchema.jsx:44
+msgid "Column"
+msgstr "Campo"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnsList.jsx:24
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:121
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:203
+msgid "Visibility"
+msgstr "Visibilidad"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnsList.jsx:25
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:214
+msgid "Type"
+msgstr "Tipo"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataHeader.jsx:87
+msgid "Current database:"
+msgstr "Base de Datos actual:"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataHeader.jsx:92
+msgid "Show original schema"
+msgstr "Mostrar Esquema Original"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataSchema.jsx:45
+msgid "Data Type"
+msgstr "Tipo de Dato"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataSchema.jsx:46
+msgid "Additional Info"
+msgstr "Información Adicional"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataSchemaList.jsx:44
+msgid "Find a schema"
+msgstr "Encontrar un esquema"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataSchemaList.jsx:51
+msgid "{0} schema"
+msgid_plural "{0} schemas"
+msgstr[0] "Ver esquema"
+msgstr[1] "Ver esquemas"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:82
+msgid "Why Hide?"
+msgstr "¿Por qué esconderlo?"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:83
+msgid "Technical Data"
+msgstr "Datos Técnicos"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:84
+msgid "Irrelevant/Cruft"
+msgstr "Irrelevante"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:90
+msgid "Queryable"
+msgstr "Consultable"
+
+#. Oculto
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:91
+msgid "Hidden"
+msgstr "Oculto"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:117
+msgid "No table description yet"
+msgstr "No hay una descripción de la tabla"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:124
+msgid "Metadata Strength"
+msgstr "Alcance Metadatos"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTableList.jsx:87
+msgid "{0} Queryable Table"
+msgid_plural "{0} Queryable Tables"
+msgstr[0] "{0] Tabla Consultable"
+msgstr[1] "{0] Tablas Consultables"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTableList.jsx:96
+msgid "{0} Hidden Table"
+msgid_plural "{0} Hidden Tables"
+msgstr[0] "{0] Tabla Escondida"
+msgstr[1] "{0] Tablas Escondidas"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTableList.jsx:113
+msgid "Find a table"
+msgstr "Encontrar una tabla"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTableList.jsx:126
+msgid "Schemas"
+msgstr "Esquemas"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx:24
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:137
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:33
+#: frontend/src/metabase/reference/guide/BaseSidebar.jsx:27
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:189
+#: frontend/src/metabase/reference/metrics/MetricList.jsx:56
+#: frontend/src/metabase/reference/metrics/MetricSidebar.jsx:18
+#: frontend/src/metabase/routes.jsx:231
+msgid "Metrics"
+msgstr "Métricas"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx:30
+msgid "Add a Metric"
+msgstr "Añadir una Métrica"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx:37
+#: frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx:37
+#: frontend/src/metabase/query_builder/components/QueryDefinitionTooltip.jsx:30
+msgid "Definition"
+msgstr "Definición"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx:54
+msgid "Create metrics to add them to the View dropdown in the query builder"
+msgstr "Crea métricas para añadirlas al menú Ver en el generador de consultas"
+
+#: frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx:24
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:922
+#: frontend/src/metabase/reference/guide/BaseSidebar.jsx:33
+#: frontend/src/metabase/reference/segments/SegmentFieldSidebar.jsx:19
+#: frontend/src/metabase/reference/segments/SegmentList.jsx:56
+#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:18
+msgid "Segments"
+msgstr "Segmentos"
+
+#: frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx:30
+msgid "Add a Segment"
+msgstr "Añadir un Segmento"
+
+#: frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx:54
+msgid "Create segments to add them to the Filter dropdown in the query builder"
+msgstr "Crea segmentos para añadirlos al menú Filtro en el generador de consultas"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/Revision.jsx:24
+msgid "created"
+msgstr "creado"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/Revision.jsx:27
+msgid "reverted to a previous version"
+msgstr "revertido a una versión anterior"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/Revision.jsx:33
+msgid "edited the title"
+msgstr "editado el título"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/Revision.jsx:35
+msgid "edited the description"
+msgstr "editado la descripción"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/Revision.jsx:37
+msgid "edited the "
+msgstr "editado el"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/Revision.jsx:40
+msgid "made some changes"
+msgstr "he hecho algunos cambios"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/Revision.jsx:46
+#: frontend/src/metabase/home/components/Activity.jsx:80
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:343
+msgid "You"
+msgstr "Tu"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/RevisionHistory.jsx:37
+msgid "Datamodel"
+msgstr "Modelo de datos"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/RevisionHistory.jsx:43
+msgid " History"
+msgstr "Historia"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/RevisionHistory.jsx:48
+msgid "Revision History for"
+msgstr "Historial de Revisiones para"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:185
+msgid "{0} – Field Settings"
+msgstr "{0} – Configuración de campos"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:204
+msgid "Where this field will appear throughout Metabase"
+msgstr "Donde aparecerá este campo en Metabase"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:226
+msgid "Filtering on this field"
+msgstr "Filtrando sobre este campo"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:227
+msgid "When this field is used in a filter, what should people use to enter the value they want to filter on?"
+msgstr "Cuando este campo se usa en un filtro, ¿qué valores debería aceptar?"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:334
+msgid "No description for this field yet"
+msgstr "No hay descripción para este campo todavía"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:413
+msgid "Original value"
+msgstr "Valor original"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:414
+msgid "Mapped value"
+msgstr "Valor asignado"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:457
+msgid "Enter value"
+msgstr "Introduce un valor"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:480
+msgid "Use original value"
+msgstr "Utiliza valor original"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:481
+msgid "Use foreign key"
+msgstr "Utilizar clave foránea"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:482
+msgid "Custom mapping"
+msgstr "Mapeo personalizado"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:510
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:610
+msgid "Unrecognized mapping type"
+msgstr "Tipo de mapeo no reconocido"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:544
+msgid "Current field isn't a foreign key or FK target table metadata is missing"
+msgstr "El campo actual no es una clave foránea o faltan los metadatos de clave foránea en la tabla de destino"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:641
+msgid "The selected field isn't a foreign key"
+msgstr "El campo seleccionado no es una clave foránea"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:703
+msgid "Display values"
+msgstr "Valores mostrados"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:704
+msgid "Choose to show the original value from the database, or have this field display associated or custom information."
+msgstr "Elige si quieres mostrar el valor original de la base de datos, o mostrar información asociada o personalizada."
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:730
+msgid "Choose a field"
+msgstr "Elige un campo"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:751
+msgid "Please select a column to use for display."
+msgstr "Selecciona una columna para mostrar."
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:771
+msgid "Tip:"
+msgstr "Consejo:"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:772
+msgid "You might want to update the field name to make sure it still makes sense based on your remapping choices."
+msgstr "Es posible que quieras actualizar el nombre del campo para asegurarte de que todavía tenga sentidoen función de las opciones de asignación."
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:781
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:105
+msgid "Cached field values"
+msgstr "Valores de campo en caché"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:782
+msgid "Metabase can scan the values for this field to enable checkbox filters in dashboards and questions."
+msgstr "Metabase puede escanear los valores de este campo para habilitar los filtros de casilla de verificación en cuadros de mando y preguntas."
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:787
+msgid "Re-scan this field"
+msgstr "Vuelve a escanear este campo"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:795
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:119
+msgid "Discard cached field values"
+msgstr "Descartar valores de campo en caché"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:797
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:121
+msgid "Failed to discard values"
+msgstr "Error al descartar valores"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:798
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:122
+msgid "Discard triggered!"
+msgstr "Limpieza iniciada!"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetadataEditorApp.jsx:105
+msgid "Select any table to see its schema and add or edit metadata."
+msgstr "Selecciona cualquier tabla para ver su esquema y añadir o editar metadatos."
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:37
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:34
+#: frontend/src/metabase/entities/collections.js:89
+msgid "Name is required"
+msgstr "Nombre es obligatorio"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:40
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:37
+msgid "Description is required"
+msgstr "Descripción es obligatorio"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:44
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:41
+msgid "Revision message is required"
+msgstr "Mensaje de la revisión es obligatorio"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:50
+msgid "Aggregation is required"
+msgstr "Agregación es obligatoria"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:110
+msgid "Edit Your Metric"
+msgstr "Edita tu Métrica"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:111
+msgid "Create Your Metric"
+msgstr "Crea tu Métrica"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:115
+msgid "Make changes to your metric and leave an explanatory note."
+msgstr "Haz cambios en tu métrica y deja una nota explicativa."
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:116
+msgid "You can create saved metrics to add a named metric option to this table. Saved metrics include the aggregation type, the aggregated field, and optionally any filter you add. As an example, you might use this to create something like the official way of calculating \"Average Price\" for an Orders table."
+msgstr "Puedes crear métricas y guardarlas como campo calculado en esta tabla.Las métricas guardadas incluyen el tipo de agregación, el campo agregado y, opcionalmente, cualquier filtro que añadas.Por ejemplo, puedes usar esto para definir la forma oficial de calcular el \"Precio promedio\" para una tabla de Pedidos."
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:149
+msgid "Result: "
+msgstr "Resultado:"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:157
+msgid "Name Your Metric"
+msgstr "Ponle nombre a tu Métrica"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:158
+msgid "Give your metric a name to help others find it."
+msgstr "Dale un nombre a tu métrica para ayudar a otros a encontrarla."
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:162
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:166
+msgid "Something descriptive but not too long"
+msgstr "Algo descriptivo pero no demasiado largo"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:166
+msgid "Describe Your Metric"
+msgstr "Describe tu Métrica"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:167
+msgid "Give your metric a description to help others understand what it's about."
+msgstr "Dale una descripción a tu métrica para ayudar a otros a entender de qué se trata."
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:171
+msgid "This is a good place to be more specific about less obvious metric rules"
+msgstr "Este es un buen lugar para ser más específico sobre las reglas métricas menos obvias"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:175
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:179
+msgid "Reason For Changes"
+msgstr "Motivo de los cambios"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:177
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:181
+msgid "Leave a note to explain what changes you made and why they were required."
+msgstr "Deja una nota para explicar qué cambios has hecho y por qué fueron necesarios."
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:181
+msgid "This will show up in the revision history for this metric to help everyone remember why things changed"
+msgstr "Esto aparecerá en el historial de revisiones de esta métrica para ayudar a todos a recordar por qué se realizó el cambio"
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:49
+msgid "At least one filter is required"
+msgstr "Se requiere al menos un filtro"
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:116
+msgid "Edit Your Segment"
+msgstr "Edita tu Segmento"
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:117
+msgid "Create Your Segment"
+msgstr "Crea tu Segmento"
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:121
+msgid "Make changes to your segment and leave an explanatory note."
+msgstr "Haz cambios en tu segmento y deja una nota explicativa."
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:122
+msgid "Select and add filters to create your new segment for the {0} table"
+msgstr "Selecciona y añade filtros para crear un nuevo segmento para la tabla {0}"
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:161
+msgid "Name Your Segment"
+msgstr "Ponle nombre a tu segmento"
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:162
+msgid "Give your segment a name to help others find it."
+msgstr "Dale un nombre a tu segmento para ayudar a otros a encontrarla."
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:170
+msgid "Describe Your Segment"
+msgstr "Describe tu Segmento"
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:171
+msgid "Give your segment a description to help others understand what it's about."
+msgstr "Dale una descripción a tu segmento para ayudar a otros a entender de qué se trata."
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:175
+msgid "This is a good place to be more specific about less obvious segment rules"
+msgstr "Este es un buen lugar para ser más específico sobre las reglas de segmentación menos obvias"
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:185
+msgid "This will show up in the revision history for this segment to help everyone remember why things changed"
+msgstr "Esto aparecerá en el historial de revisiones de este segmento para ayudar a todos a recordar por qué se realizó el cambio"
+
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:91
+#: frontend/src/metabase/admin/routes.jsx:79
+#: frontend/src/metabase/admin/settings/containers/SettingsEditorApp.jsx:266
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:96
+#: frontend/src/metabase/nav/containers/Navbar.jsx:200
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorSidebar.jsx:99
+msgid "Settings"
+msgstr "Configuración"
+
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:106
+msgid "Metabase can scan the values in this table to enable checkbox filters in dashboards and questions."
+msgstr "Metabase puede leer los valores en esta tabla para habilitar filtros de casillas de verificación en cuadros de mandos y preguntas."
+
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:111
+msgid "Re-scan this table"
+msgstr "Re-Leer esta tabla"
+
+#: frontend/src/metabase/admin/people/components/AddRow.jsx:34
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:194
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:253
+#: frontend/src/metabase/dashboard/components/DashCard.jsx:276
+msgid "Add"
+msgstr "Añadir"
+
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:80
+#: frontend/src/metabase/setup/components/UserStep.jsx:103
+#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:67
+msgid "Not a valid formatted email address"
+msgstr "Formato de Email incorrecto"
+
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:135
+#: frontend/src/metabase/setup/components/UserStep.jsx:186
+#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:100
+msgid "First name"
+msgstr "Nombre"
+
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:156
+#: frontend/src/metabase/setup/components/UserStep.jsx:203
+#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:117
+msgid "Last name"
+msgstr "Apellidos"
+
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:178
+#: frontend/src/metabase/auth/containers/ForgotPasswordApp.jsx:77
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:158
+#: frontend/src/metabase/components/NewsletterForm.jsx:94
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:470
+#: frontend/src/metabase/setup/components/UserStep.jsx:222
+#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:138
+msgid "Email address"
+msgstr "Dirección de Email"
+
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:202
+msgid "Permission Groups"
+msgstr "Grupos de Permisos"
+
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:238
+msgid "Make this user an admin"
+msgstr "Convertir a este usuario en administrador"
+
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:32
+msgid "All users belong to the {0} group and can't be removed from it. Setting permissions for this group is a great way to\n"
+"make sure you know what new Metabase users will be able to see."
+msgstr "Todos los usuarios pertenecen al grupo {0} y no se pueden eliminar de él. Establecer permisos para este grupo es una excelente manera de asegurarte de saber qué podrán ver los nuevos usuarios de Metabase."
+
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:41
+msgid "This is a special group whose members can see everything in the Metabase instance, and who can access and make changes to the\n"
+"settings in the Admin Panel, including changing permissions! So, add people to this group with care."
+msgstr "Este es un grupo especial cuyos miembros pueden ver todo en la instancia de Metabase, y quienes pueden acceder y realizar cambios en la configuración en el Panel de administración, ¡incluyendo el cambio de permisos!Así que añade personas a este grupo con cuidado."
+
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:45
+msgid "To make sure you don't get locked out of Metabase, there always has to be at least one user in this group."
+msgstr "Para asegurarte de no quedar sin acceso a Metabase, siempre debe haber al menos un usuario en este grupo."
+
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:177
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:219
+msgid "Members"
+msgstr "Miembros"
+
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:177
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:470
+#: frontend/src/metabase/admin/settings/selectors.js:107
+#: frontend/src/metabase/lib/core.js:50
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:293
+msgid "Email"
+msgstr "Email"
+
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:203
+msgid "A group is only as good as its members."
+msgstr "Un grupo solo vale lo que valen sus miembros."
+
+#: frontend/src/metabase/admin/people/components/GroupSummary.jsx:15
+#: frontend/src/metabase/admin/routes.jsx:34
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:50
+msgid "Admin"
+msgstr "Admin"
+
+#: frontend/src/metabase/admin/people/components/GroupSummary.jsx:16
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:237
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:290
+msgid "and"
+msgstr "y"
+
+#: frontend/src/metabase/admin/people/components/GroupSummary.jsx:19
+#: frontend/src/metabase/admin/people/components/GroupSummary.jsx:31
+msgid "{0} other group"
+msgid_plural "{0} other groups"
+msgstr[0] "[0] grupo más"
+msgstr[1] "[0] grupos más"
+
+#: frontend/src/metabase/admin/people/components/GroupSummary.jsx:37
+msgid "Default"
+msgstr "Por defecto"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:40
+msgid "Something like \"Marketing\""
+msgstr "Algo como \"Marketing\""
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:59
+msgid "Remove this group?"
+msgstr "¿Eliminar este grupo?"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:61
+msgid "Are you sure? All members of this group will lose any permissions settings they have based on this group.\n"
+"This can't be undone."
+msgstr "¿Estás seguro? Todos los miembros de este grupo perderán las configuraciones de permisos que tengan basadas en este grupo.\n"
+"Esto no puede deshacerse."
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:72
+#: frontend/src/metabase/components/ConfirmContent.jsx:17
+msgid "Yes"
+msgstr "Sí"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:75
+msgid "No"
+msgstr "No"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:93
+msgid "Edit Name"
+msgstr "Editar Nombre"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:96
+msgid "Remove Group"
+msgstr "Borrar Grupo"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:139
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:225
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:263
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:367
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:385
+#: frontend/src/metabase/components/HeaderModal.jsx:43
+#: frontend/src/metabase/dashboard/components/AddSeriesModal.jsx:282
+#: frontend/src/metabase/parameters/components/widgets/CategoryWidget.jsx:106
+#: frontend/src/metabase/query_builder/components/AggregationPopover.jsx:298
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:194
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:96
+#: frontend/src/metabase/visualizations/components/ChartSettings.jsx:215
+msgid "Done"
+msgstr "Hecho"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:219
+msgid "Group name"
+msgstr "Nombre Grupo"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:399
+#: frontend/src/metabase/admin/people/containers/AdminPeopleApp.jsx:25
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:477
+#: frontend/src/metabase/admin/routes.jsx:72
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:154
+msgid "Groups"
+msgstr "Grupos"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:400
+msgid "Create a group"
+msgstr "Crear un grupo"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:406
+msgid "You can use groups to control your users' access to your data. Put users in groups and then go to the Permissions section to control each group's access. The Administrators and All Users groups are special default groups that can't be removed."
+msgstr "Puedes usar grupos para controlar el acceso de tus usuarios a los datos. Coloca a los usuarios en grupos y después, ves a la sección de Permisos para controlar el acceso de cada grupo. Los grupos Administradores y All Users son grupos predeterminados especiales que no se pueden eliminar."
+
+#: frontend/src/metabase/admin/people/components/UserActionsSelect.jsx:79
+msgid "Edit Details"
+msgstr "Editar Detalles"
+
+#: frontend/src/metabase/admin/people/components/UserActionsSelect.jsx:85
+msgid "Re-send Invite"
+msgstr "Reenviar Invitación"
+
+#: frontend/src/metabase/admin/people/components/UserActionsSelect.jsx:90
+msgid "Reset Password"
+msgstr "Restablecer la contraseña"
+
+#: frontend/src/metabase/admin/people/components/UserActionsSelect.jsx:97
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:304
+msgid "Deactivate"
+msgstr "Desactivar"
+
+#: frontend/src/metabase/admin/people/containers/AdminPeopleApp.jsx:24
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:435
+#: frontend/src/metabase/admin/routes.jsx:70
+#: frontend/src/metabase/nav/containers/Navbar.jsx:205
+msgid "People"
+msgstr "Personas"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:192
+msgid "Who do you want to add?"
+msgstr "¿A quien quieres añadir?"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:207
+msgid "Edit {0}'s details"
+msgstr "Edita los detalles de {0}"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:220
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:258
+msgid "{0} has been added"
+msgstr "Se ha añadido {0}"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:224
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:262
+msgid "Add another person"
+msgstr "Añade otra persona"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:231
+msgid "We couldn’t send them an email invitation,\n"
+"so make sure to tell them to log in using {0}\n"
+"and this password we’ve generated for them:"
+msgstr "No se ha podido enviarles una invitación por correo electrónico, así que asegúraste de indicarles que inicien sesión con {0} y la contraseña que les hemos generado:"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:242
+msgid "If you want to be able to send email invites, just go to the {0} page."
+msgstr "Si quieres poder enviar invitaciones por correo electrónico, simplemente ves a la página {0}."
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:268
+msgid "We’ve sent an invite to {0} with instructions to set their password."
+msgstr "Hemos enviado una invitación a {0} con instrucciones para configurar su contraseña."
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:283
+msgid "We've re-sent {0}'s invite"
+msgstr "Hemos vuelto a enviar la invitación de {0}"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:285
+#: frontend/src/metabase/query_builder/components/SavedQuestionIntroModal.jsx:22
+#: frontend/src/metabase/tutorial/Tutorial.jsx:253
+msgid "Okay"
+msgstr "Vale"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:289
+msgid "Any previous email invites they have will no longer work."
+msgstr "Cualquier invitación de correo electrónico anterior que tengan ya no funcionará."
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:300
+msgid "Deactivate {0}?"
+msgstr "¿Desactivar {0}?"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:309
+msgid "{0} won't be able to log in anymore."
+msgstr "{0} ya no podrá iniciar sesión."
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:320
+msgid "Reactivate {0}'s account?"
+msgstr "¿Activar la cuenta de {0}?"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:326
+msgid "Reactivate"
+msgstr "Activar"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:330
+msgid "They'll be able to log in again, and they'll be placed back into the groups they were in before their account was deactivated."
+msgstr "Podrán volver a iniciar sesión y se les volverá a colocar en los grupos en los que estaban antes de que se desactivara su cuenta."
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:341
+msgid "Reset {0}'s password?"
+msgstr "¿Restablecer la contraseña de {0}?"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:347
+#: frontend/src/metabase/components/form/StandardForm.jsx:71
+msgid "Reset"
+msgstr "Reiniciar"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:351
+#: frontend/src/metabase/components/ConfirmContent.jsx:13
+#: frontend/src/metabase/dashboard/components/ArchiveDashboardModal.jsx:44
+msgid "Are you sure you want to do this?"
+msgstr "¿Seguro que quieres hacer esto?"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:362
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:384
+msgid "{0}'s password has been reset"
+msgstr "La contraseña de {0} ha sido restablecida"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:371
+msgid "Here’s a temporary password they can use to log in and then change their password."
+msgstr "Aquí hay una contraseña temporal que pueden usar para iniciar sesión y luego cambiar su contraseña."
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:388
+msgid "We've sent them an email with instructions for creating a new password."
+msgstr "Les hemos enviado un correo electrónico con instrucciones para crear una nueva contraseña."
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:443
+msgid "Active"
+msgstr "Activo"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:444
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:473
+msgid "Deactivated"
+msgstr "Desactivado"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:459
+msgid "Add someone"
+msgstr "Añadir alguien"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:478
+msgid "Last Login"
+msgstr "Ultimo acceso"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:501
+msgid "Signed up via Google"
+msgstr "Acceso via Google"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:506
+msgid "Signed up via LDAP"
+msgstr "Acceso via LDAP"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:518
+msgid "Reactivate this account"
+msgstr "Activar esta cuenta"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:545
+msgid "Never"
+msgstr "Nunca"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:27
+#: frontend/src/metabase/query_builder/components/dataref/MainPane.jsx:24
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} table"
+msgid_plural "{0} tables"
+msgstr[0] "{0} tabla"
+msgstr[1] "{0} tablas"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:45
+msgid " will be "
+msgstr "será"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:48
+msgid "given access to"
+msgstr "dado el acceso a"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:53
+msgid " and "
+msgstr " y "
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:56
+msgid "denied access to"
+msgstr "denegado el acceso a"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:70
+msgid " will no longer be able to "
+msgstr "ya no podrá"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:71
+msgid " will now be able to "
+msgstr "ahora será capaz de"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:79
+msgid " native queries for "
+msgstr "consultas nativas para"
+
+#: frontend/src/metabase/admin/permissions/routes.jsx:12
+#: frontend/src/metabase/nav/containers/Navbar.jsx:220
+msgid "Permissions"
+msgstr "Permisos"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:32
+msgid "Save permissions?"
+msgstr "¿Guardar permisos?"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:38
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:161
+msgid "Save Changes"
+msgstr "Guardar Cambios"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:44
+msgid "Discard changes?"
+msgstr "¿Descartar los cambios?"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:46
+msgid "No changes to permissions will be made."
+msgstr "No se realizarán cambios en los permisos."
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:65
+msgid "You've made changes to permissions."
+msgstr "Has realizado cambios en los permisos."
+
+#: frontend/src/metabase/admin/permissions/containers/CollectionPermissionsModal.jsx:52
+msgid "Permissions for this collection"
+msgstr "Permisos de esta colección"
+
+#: frontend/src/metabase/admin/permissions/containers/PermissionsApp.jsx:53
+msgid "You have unsaved changes"
+msgstr "Tiene cambios sin guardar"
+
+#: frontend/src/metabase/admin/permissions/containers/PermissionsApp.jsx:54
+msgid "Do you want to leave this page and discard your changes?"
+msgstr "¿Quieres abandonar esta página y descartar tus cambios?"
+
+#: frontend/src/metabase/admin/permissions/permissions.js:137
+msgid "Sorry, an error occurred."
+msgstr "Lo siento, ocurrió un error."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:59
+msgid "Administrators always have the highest level of access to everything in Metabase."
+msgstr "Los administradores siempre tienen el más alto nivel de acceso a todo en Metabase."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:61
+msgid "Every Metabase user belongs to the All Users group. If you want to limit or restrict a group's access to something, make sure the All Users group has an equal or lower level of access."
+msgstr "Todos los usuarios de Metabase pertenecen al grupo All Users. Si quieres limitar o restringir el acceso de un grupo a algo, asegúrate de que el grupo All Users tenga un nivel de acceso igual o inferior."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:63
+msgid "MetaBot is Metabase's Slack bot. You can choose what it has access to here."
+msgstr "MetaBot es el bot de Slack de Metabase. Puedes elegir a qué tiene acceso aquí."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:115
+msgid "The \"{0}\" group may have access to a different set of {1} than this group, which may give this group additional access to some {2}."
+msgstr "El grupo \"{0}\" puede tener acceso a un conjunto diferente de {1} que este grupo, lo que puede darle a este grupo acceso adicional a algún {2}."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:120
+msgid "The \"{0}\" group has a higher level of access than this, which will override this setting. You should limit or revoke the \"{1}\" group's access to this item."
+msgstr "El grupo \"{0}\" tiene un mayor nivel de acceso que este, lo que anulará esta configuración. Debes limitar o revocar el acceso del grupo \"{1}\" a este elemento."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:150
+msgid "Limit"
+msgstr "¿Limitar"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:150
+msgid "Revoke"
+msgstr "¿Revocar"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:152
+msgid "access even though \"{0}\" has greater access?"
+msgstr "el acceso a pesar de que el grupo \"{0}\" tiene mayor acceso?"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:155
+#: frontend/src/metabase/admin/permissions/selectors.js:254
+msgid "Limit access"
+msgstr "Limita el acceso"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:155
+#: frontend/src/metabase/admin/permissions/selectors.js:219
+#: frontend/src/metabase/admin/permissions/selectors.js:262
+msgid "Revoke access"
+msgstr "Revocar el acceso"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:164
+msgid "Change access to this database to limited?"
+msgstr "¿Cambiar el acceso a esta base de datos a limitado?"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:165
+msgid "Change"
+msgstr "Cambiar"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:178
+msgid "Allow Raw Query Writing?"
+msgstr "Permitir escritura de consultas directas"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:179
+msgid "This will also change this group's data access to Unrestricted for this database."
+msgstr "Esto también cambiará el acceso de datos de este grupo a Sin restricciones para esta base de datos."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:180
+msgid "Allow"
+msgstr "Permitir"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:217
+msgid "Revoke access to all tables?"
+msgstr "¿Revoca el acceso a todas las tablas?"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:218
+msgid "This will also revoke this group's access to raw queries for this database."
+msgstr "Esto también revocará el acceso de este grupo a consultas sin formato para esta base de datos."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:247
+msgid "Grant unrestricted access"
+msgstr "Conceder acceso sin restricciones"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:248
+msgid "Unrestricted access"
+msgstr "Acceso no restingido"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:255
+msgid "Limited access"
+msgstr "Acceso limitado"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:263
+msgid "No access"
+msgstr "Sin acceso"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:269
+msgid "Write raw queries"
+msgstr "Escribir consultas directas"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:270
+msgid "Can write raw queries"
+msgstr "Puede escribir consultas directas"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:277
+msgid "Curate collection"
+msgstr "Mima la colección"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:284
+msgid "View collection"
+msgstr "Ver colección"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:327
+#: frontend/src/metabase/admin/permissions/selectors.js:423
+#: frontend/src/metabase/admin/permissions/selectors.js:520
+msgid "Data Access"
+msgstr "Acceso a Datos"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:488
+#: frontend/src/metabase/admin/permissions/selectors.js:645
+#: frontend/src/metabase/admin/permissions/selectors.js:650
+msgid "View tables"
+msgstr "Ver tablas"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:586
+msgid "SQL Queries"
+msgstr "Consultas SQL"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:656
+msgid "View schemas"
+msgstr "Ver esquemas"
+
+#: frontend/src/metabase/admin/routes.jsx:45
+#: frontend/src/metabase/nav/containers/Navbar.jsx:210
+msgid "Data Model"
+msgstr "Modelo de Datos"
+
+#: frontend/src/metabase/admin/settings/components/SettingsAuthenticationOptions.jsx:11
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:118
+msgid "Sign in with Google"
+msgstr "Accede con Google"
+
+#: frontend/src/metabase/admin/settings/components/SettingsAuthenticationOptions.jsx:13
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:120
+msgid "Allows users with existing Metabase accounts to login with a Google account that matches their email address in addition to their Metabase username and password."
+msgstr "Permite a los usuarios con cuentas Metabase existentes iniciar sesión con una cuenta de Google que coincida con su dirección de correo electrónico además de su nombre de usuario y contraseña de Metabase."
+
+#: frontend/src/metabase/admin/settings/components/SettingsAuthenticationOptions.jsx:17
+#: frontend/src/metabase/admin/settings/components/SettingsAuthenticationOptions.jsx:29
+#: frontend/src/metabase/components/ChannelSetupMessage.jsx:32
+msgid "Configure"
+msgstr "Configura"
+
+#: frontend/src/metabase/admin/settings/components/SettingsAuthenticationOptions.jsx:23
+#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:13
+#: frontend/src/metabase/admin/settings/selectors.js:201
+msgid "LDAP"
+msgstr "LDAP"
+
+#: frontend/src/metabase/admin/settings/components/SettingsAuthenticationOptions.jsx:25
+msgid "Allows users within your LDAP directory to log in to Metabase with their LDAP credentials, and allows automatic mapping of LDAP groups to Metabase groups."
+msgstr "Permite a los usuarios de tu directorio LDAP iniciar sesión en la Metabase con sus credenciales LDAP, y permite la asignación automática de grupos LDAP a grupos de metabase."
+
+#: frontend/src/metabase/admin/settings/components/SettingsBatchForm.jsx:17
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:69
+#: frontend/src/metabase/admin/settings/selectors.js:154
+msgid "That's not a valid email address"
+msgstr "Esa no es una dirección de correo electrónico válida"
+
+#: frontend/src/metabase/admin/settings/components/SettingsBatchForm.jsx:21
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:73
+msgid "That's not a valid integer"
+msgstr "Ese no es un entero válido"
+
+#: frontend/src/metabase/admin/settings/components/SettingsBatchForm.jsx:28
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:161
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:223
+msgid "Changes saved!"
+msgstr "¡Cambios guardados!"
+
+#: frontend/src/metabase/admin/settings/components/SettingsBatchForm.jsx:157
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:132
+msgid "Looks like we ran into some problems"
+msgstr "Parece que nos encontramos con algunos problemas"
+
+#: frontend/src/metabase/admin/settings/components/SettingsEmailForm.jsx:12
+msgid "Send test email"
+msgstr "Enviar email de comprobación"
+
+#: frontend/src/metabase/admin/settings/components/SettingsEmailForm.jsx:13
+msgid "Sending..."
+msgstr "Enviando..."
+
+#: frontend/src/metabase/admin/settings/components/SettingsEmailForm.jsx:14
+msgid "Sent!"
+msgstr "Enviado!"
+
+#: frontend/src/metabase/admin/settings/components/SettingsEmailForm.jsx:82
+msgid "Clear"
+msgstr "Limpiar"
+
+#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:12
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:113
+#: frontend/src/metabase/admin/settings/selectors.js:196
+msgid "Authentication"
+msgstr "Autenticación"
+
+#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:18
+msgid "Server Settings"
+msgstr "Configuración Servidor"
+
+#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:29
+msgid "User Schema"
+msgstr "Esquema de Usuario"
+
+#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:33
+msgid "Attributes"
+msgstr "Atributos"
+
+#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:42
+msgid "Group Schema"
+msgstr "Esquema de Grupo"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSetting.jsx:28
+msgid "Using "
+msgstr "Utilizando"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSetupList.jsx:105
+msgid "Getting set up"
+msgstr "Preparándote"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSetupList.jsx:106
+msgid "A few things you can do to get the most out of Metabase."
+msgstr "Algunas cosas que puedes hacer para sacar el máximo provecho de Metabase."
+
+#: frontend/src/metabase/admin/settings/components/SettingsSetupList.jsx:115
+msgid "Recommended next step"
+msgstr "Siguiente paso recomendado"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:114
+msgid "Google Sign-In"
+msgstr "Acceso Google"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:123
+msgid "To allow users to sign in with Google you'll need to give Metabase a Google Developers console application client ID. It only takes a few steps and instructions on how to create a key can be found {0}"
+msgstr "Para permitir que los usuarios inicien sesión con Google, deberás proporcionar a Metabase un ID de cliente de aplicación de la consola de Desarrollo de Google. Solo se necesitan unos pocos pasos y se pueden encontrar instrucciones sobre cómo crear una clave {0}"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:137
+msgid "Your Google client ID"
+msgstr "Tu ID de cliente de Google"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:142
+msgid "Allow users to sign up on their own if their Google account email address is from:"
+msgstr "Permite que los usuarios se registren solos si la dirección de correo electrónico de su cuenta de Google es de:"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:242
+msgid "Answers sent right to your Slack #channels"
+msgstr "Respuestas enviadas directamente a tus #canales Slack"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:251
+msgid "Create a Slack Bot User for MetaBot"
+msgstr "Crear un usuario de Slack Bot para MetaBot"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:261
+msgid "Once you're there, give it a name and click {0}. Then copy and paste the Bot API Token into the field below. Once you are done, create a \"metabase_files\" channel in Slack. Metabase needs this to upload graphs."
+msgstr "Una vez que estés allí, asígnele un nombre y haz clic en {0}. Luego, copia y pega el API Token del Bot en el campo a continuación. En cuanto hayas terminado, crea un canal \"metabase_files\" en Slack. Metabase necesita ese para subir gráficos."
+
+#: frontend/src/metabase/admin/settings/components/SettingsUpdatesForm.jsx:90
+msgid "You're running Metabase {0} which is the latest and greatest!"
+msgstr "¡Estás ejecutando Metabase {0} que es lo último y lo mejor!"
+
+#: frontend/src/metabase/admin/settings/components/SettingsUpdatesForm.jsx:99
+msgid "Metabase {0} is available.  You're running {1}"
+msgstr "Metabase {0} está disponible.  Estás ejecutando {1}"
+
+#: frontend/src/metabase/admin/settings/components/SettingsUpdatesForm.jsx:112
+#: frontend/src/metabase/components/form/StandardForm.jsx:63
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:96
+msgid "Update"
+msgstr "Actualiza"
+
+#: frontend/src/metabase/admin/settings/components/SettingsUpdatesForm.jsx:116
+msgid "What's Changed:"
+msgstr "Qué ha cambiado:"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:131
+msgid "Add a map"
+msgstr "Añade un mapa"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:184
+#: frontend/src/metabase/lib/core.js:100
+msgid "URL"
+msgstr "URL"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:199
+msgid "Delete custom map"
+msgstr "Elimina mapa personalizado"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:201
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:327
+#: frontend/src/metabase/dashboard/components/RemoveFromDashboardModal.jsx:48
+#: frontend/src/metabase/parameters/components/ParameterWidget.jsx:177
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:104
+msgid "Remove"
+msgstr "Borrar"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:226
+#: frontend/src/metabase/dashboard/containers/DashCardCardParameterMapper.jsx:187
+#: frontend/src/metabase/parameters/components/ParameterValueWidget.jsx:241
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:142
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:184
+msgid "Select…"
+msgstr "Selecciona…"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:241
+msgid "Sample values:"
+msgstr "Valores de muestra:"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:279
+msgid "Add a new map"
+msgstr "Añade un nuevo mapa"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:279
+msgid "Edit map"
+msgstr "Editar mapa"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:280
+msgid "What do you want to call this map?"
+msgstr "¿Cómo quieres llamar a este mapa?"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:285
+msgid "e.g. United Kingdom, Brazil, Mars"
+msgstr "e.g. United Kingdom, Brazil, Mars"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:292
+msgid "URL for the GeoJSON file you want to use"
+msgstr "URL para el archivo GeoJSON que quieres usar"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:298
+msgid "Like https://my-mb-server.com/maps/my-map.json"
+msgstr "Como https://my-mb-server.com/maps/my-map.json"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:309
+#: frontend/src/metabase/query_builder/components/RunButton.jsx:33
+msgid "Refresh"
+msgstr "Actualizar"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:309
+msgid "Load"
+msgstr "Carga"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:315
+msgid "Which property specifies the region’s identifier?"
+msgstr "¿Qué propiedad especifica el identificador de la región?"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:324
+msgid "Which property specifies the region’s display name?"
+msgstr "¿Qué propiedad especifica el nombre de la región?"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:345
+msgid "Load a GeoJSON file to see a preview"
+msgstr "Carga un archivo GeoJSON para ver una vista previa"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:363
+msgid "Save map"
+msgstr "Guardar mapa"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:363
+msgid "Add map"
+msgstr "Añadir mapa"
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLegalese.jsx:7
+msgid "Using embedding"
+msgstr "Utilizando embebido"
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLegalese.jsx:9
+msgid "By enabling embedding you're agreeing to the embedding license located at"
+msgstr "Al habilitar el embebido, aceptas la licencia de incorporación ubicada en"
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLegalese.jsx:19
+msgid "In plain English, when you embed charts or dashboards from Metabase in your own application, that application isn't subject to the Affero General Public License that covers the rest of Metabase, provided you keep the Metabase logo and the \"Powered by Metabase\" visible on those embeds. You should, however, read the license text linked above as that is the actual license that you will be agreeing to by enabling this feature."
+msgstr "En lenguaje sencillo, cuando insertas tablas o cuadros de mando de Metabase en tu propia aplicación, esa aplicación no está sujeta a la Licencia AGPL que cubre el resto de Metabase, siempre que mantengas el logotipo de Metabase y el \"Powered by Metabase\" visible en esas incrustaciones. Sin embargo, debes leer el texto de licencia, ya que esa es la licencia real que aceptas al habilitar esta función."
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLegalese.jsx:32
+msgid "Enable"
+msgstr "Habilitar"
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLevel.jsx:24
+msgid "Premium embedding enabled"
+msgstr "Embebido Premium habilitado"
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLevel.jsx:26
+msgid "Enter the token you bought from the Metabase Store"
+msgstr "Ingresa el token que compraste en la Tienda Metabase"
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLevel.jsx:53
+msgid "Premium embedding lets you disable \"Powered by Metabase\" on your embedded dashboards and questions."
+msgstr "La incrustación Premium te permite desactivar \"Powered by Metabase\" en tus cuadros de mandos y preguntas embebidas."
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLevel.jsx:60
+msgid "Buy a token"
+msgstr "Comprar un token"
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLevel.jsx:63
+msgid "Enter a token"
+msgstr "Introducir un token"
+
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:134
+msgid "Edit Mappings"
+msgstr "Editar Asignaciones"
+
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:140
+msgid "Group Mappings"
+msgstr "Asignaciones de Grupo"
+
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:147
+msgid "Create a mapping"
+msgstr "Crear una asignación"
+
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:149
+msgid "Mappings allow Metabase to automatically add and remove users from groups based on the membership information provided by the\n"
+"directory server. Membership to the Admin group can be granted through mappings, but will not be automatically removed as a\n"
+"failsafe measure."
+msgstr "Las asignaciones permiten que Metabase agregue y elimine automáticamente usuarios de grupos según la información de membresía proporcionada por el servidor de directorios. La membresía al grupo de administración se puede otorgar a través de asignaciones, pero no se eliminará automáticamente como una medida a prueba de fallas."
+
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:154
+msgid "Distinguished Name"
+msgstr "Nombre Distinguido"
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:92
+msgid "Public Link"
+msgstr "Enlace Público"
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:93
+msgid "Revoke Link"
+msgstr "Eliminar Enlace"
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:121
+msgid "Disable this link?"
+msgstr "¿Deshabilitar este enlace?"
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:122
+msgid "They won't work anymore, and can't be restored, but you can create new links."
+msgstr "Ya no funcionarán y no se pueden restaurar, pero puedes crear nuevos enlaces."
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:149
+msgid "Public Dashboard Listing"
+msgstr "Listado de Cuadros de Mando Públicos"
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:152
+msgid "No dashboards have been publicly shared yet."
+msgstr "Aún no se ha compartido públicamente ningún cuadro de mando."
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:160
+msgid "Public Card Listing"
+msgstr "Listado de tarjetas públicas"
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:163
+msgid "No questions have been publicly shared yet."
+msgstr "Aún no se ha compartido públicamente ninguna pregunta."
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:172
+msgid "Embedded Dashboard Listing"
+msgstr "Listado de cuadros de mando embebidos"
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:173
+msgid "No dashboards have been embedded yet."
+msgstr "No se han incrustado cuadros de mando todavía."
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:183
+msgid "Embedded Card Listing"
+msgstr "Listado de tarjetas embebidas"
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:184
+msgid "No questions have been embedded yet."
+msgstr "No se han incrustado preguntas todavía."
+
+#: frontend/src/metabase/admin/settings/components/widgets/SecretKeyWidget.jsx:35
+msgid "Regenerate embedding key?"
+msgstr "¿Regenerar la clave de inserción?"
+
+#: frontend/src/metabase/admin/settings/components/widgets/SecretKeyWidget.jsx:36
+msgid "This will cause existing embeds to stop working until they are updated with the new key."
+msgstr "Esto hará que las inserciones existentes dejen de funcionar hasta que se actualicen con la nueva clave."
+
+#: frontend/src/metabase/admin/settings/components/widgets/SecretKeyWidget.jsx:39
+msgid "Regenerate key"
+msgstr "Regenerar clave"
+
+#: frontend/src/metabase/admin/settings/components/widgets/SecretKeyWidget.jsx:47
+msgid "Generate Key"
+msgstr "Generar clave"
+
+#: frontend/src/metabase/admin/settings/components/widgets/SettingToggle.jsx:11
+#: frontend/src/metabase/admin/settings/selectors.js:77
+#: frontend/src/metabase/admin/settings/selectors.js:86
+msgid "Enabled"
+msgstr "Habilitado"
+
+#: frontend/src/metabase/admin/settings/components/widgets/SettingToggle.jsx:11
+#: frontend/src/metabase/admin/settings/selectors.js:82
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:103
+msgid "Disabled"
+msgstr "Deshabilitado"
+
+#: frontend/src/metabase/admin/settings/containers/SettingsEditorApp.jsx:116
+msgid "Unknown setting {0}"
+msgstr "La directiva de configuración {0} es desconocida"
+
+#: frontend/src/metabase/admin/settings/selectors.js:22
+msgid "Setup"
+msgstr "Configura"
+
+#: frontend/src/metabase/admin/settings/selectors.js:27
+msgid "General"
+msgstr "General"
+
+#: frontend/src/metabase/admin/settings/selectors.js:32
+msgid "Site Name"
+msgstr "Nombre Sitio"
+
+#: frontend/src/metabase/admin/settings/selectors.js:37
+msgid "Site URL"
+msgstr "URL Sitio"
+
+#: frontend/src/metabase/admin/settings/selectors.js:42
+msgid "Email Address for Help Requests"
+msgstr "Dirección de correo electrónico para solicitudes de ayuda"
+
+#: frontend/src/metabase/admin/settings/selectors.js:47
+msgid "Report Timezone"
+msgstr "Zona horaria de los Informes"
+
+#: frontend/src/metabase/admin/settings/selectors.js:50
+msgid "Database Default"
+msgstr "La de la base de datos"
+
+#: frontend/src/metabase/admin/settings/selectors.js:53
+msgid "Select a timezone"
+msgstr "Selecciona una zona horaria"
+
+#: frontend/src/metabase/admin/settings/selectors.js:54
+msgid "Not all databases support timezones, in which case this setting won't take effect."
+msgstr "No todas las bases de datos admiten zonas horarias, en cuyo caso esta configuración no tendrá efecto."
+
+#: frontend/src/metabase/admin/settings/selectors.js:59
+msgid "Language"
+msgstr "Idioma"
+
+#: frontend/src/metabase/admin/settings/selectors.js:64
+msgid "Select a language"
+msgstr "Selecciona un idioma"
+
+#: frontend/src/metabase/admin/settings/selectors.js:69
+msgid "Anonymous Tracking"
+msgstr "Seguimiento anónimo"
+
+#: frontend/src/metabase/admin/settings/selectors.js:74
+msgid "Friendly Table and Field Names"
+msgstr "Nombres de tablas y campos amistosos"
+
+#: frontend/src/metabase/admin/settings/selectors.js:80
+msgid "Only replace underscores and dashes with spaces"
+msgstr "Solo reemplaza los guiones bajos y guiones por espacios"
+
+#: frontend/src/metabase/admin/settings/selectors.js:90
+msgid "Enable Nested Queries"
+msgstr "Habilitar consultas anidadas"
+
+#: frontend/src/metabase/admin/settings/selectors.js:96
+msgid "Updates"
+msgstr "Actualizaciones"
+
+#: frontend/src/metabase/admin/settings/selectors.js:101
+msgid "Check for updates"
+msgstr "Buscar actualizaciones"
+
+#: frontend/src/metabase/admin/settings/selectors.js:112
+msgid "SMTP Host"
+msgstr "Servidor SMTP"
+
+#: frontend/src/metabase/admin/settings/selectors.js:120
+msgid "SMTP Port"
+msgstr "Puerto SMTP"
+
+#: frontend/src/metabase/admin/settings/selectors.js:124
+#: frontend/src/metabase/admin/settings/selectors.js:224
+msgid "That's not a valid port number"
+msgstr "Ese no es un número de puerto válido"
+
+#: frontend/src/metabase/admin/settings/selectors.js:128
+msgid "SMTP Security"
+msgstr "Seguridad SMTP"
+
+#: frontend/src/metabase/admin/settings/selectors.js:136
+msgid "SMTP Username"
+msgstr "Usuario SMTP"
+
+#: frontend/src/metabase/admin/settings/selectors.js:143
+msgid "SMTP Password"
+msgstr "Contraseña SMTP"
+
+#: frontend/src/metabase/admin/settings/selectors.js:150
+msgid "From Address"
+msgstr "Email De"
+
+#: frontend/src/metabase/admin/settings/selectors.js:164
+msgid "Slack API Token"
+msgstr "Slack API Token"
+
+#: frontend/src/metabase/admin/settings/selectors.js:166
+msgid "Enter the token you received from Slack"
+msgstr "Introduce el token que has recibido de Slack"
+
+#: frontend/src/metabase/admin/settings/selectors.js:183
+msgid "Single Sign-On"
+msgstr "Inicio de sesión único"
+
+#: frontend/src/metabase/admin/settings/selectors.js:207
+msgid "LDAP Authentication"
+msgstr "Autentificación LDAP"
+
+#: frontend/src/metabase/admin/settings/selectors.js:213
+msgid "LDAP Host"
+msgstr "Servidor LDAP"
+
+#: frontend/src/metabase/admin/settings/selectors.js:221
+msgid "LDAP Port"
+msgstr "Puerto LDAP"
+
+#: frontend/src/metabase/admin/settings/selectors.js:228
+msgid "LDAP Security"
+msgstr "Seguridad LDAP"
+
+#: frontend/src/metabase/admin/settings/selectors.js:236
+msgid "Username or DN"
+msgstr "Nombre de Usuario o DN"
+
+#: frontend/src/metabase/admin/settings/selectors.js:241
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:188
+#: frontend/src/metabase/user/components/UserSettings.jsx:72
+msgid "Password"
+msgstr "Contraseña"
+
+#: frontend/src/metabase/admin/settings/selectors.js:246
+msgid "User search base"
+msgstr "Base de búsqueda de usuario"
+
+#: frontend/src/metabase/admin/settings/selectors.js:252
+msgid "User filter"
+msgstr "Filtro de usuario"
+
+#: frontend/src/metabase/admin/settings/selectors.js:258
+msgid "Check your parentheses"
+msgstr "Verifica los paréntesis"
+
+#: frontend/src/metabase/admin/settings/selectors.js:264
+msgid "Email attribute"
+msgstr "Atributo de Email"
+
+#: frontend/src/metabase/admin/settings/selectors.js:269
+msgid "First name attribute"
+msgstr "Atributo de Nombre"
+
+#: frontend/src/metabase/admin/settings/selectors.js:274
+msgid "Last name attribute"
+msgstr "Atributo de Apellidos"
+
+#: frontend/src/metabase/admin/settings/selectors.js:279
+msgid "Synchronize group memberships"
+msgstr "Sincronizar membresías de grupo"
+
+#: frontend/src/metabase/admin/settings/selectors.js:285
+msgid "Group search base"
+msgstr "Base de búsqueda para Grupo"
+
+#: frontend/src/metabase/admin/settings/selectors.js:294
+msgid "Maps"
+msgstr "Mapas"
+
+#: frontend/src/metabase/admin/settings/selectors.js:299
+msgid "Map tile server URL"
+msgstr "URL del servidor de capas de mapa"
+
+#: frontend/src/metabase/admin/settings/selectors.js:300
+msgid "Metabase uses OpenStreetMaps by default."
+msgstr "Metabase usa OpenStreetMaps por defecto."
+
+#: frontend/src/metabase/admin/settings/selectors.js:305
+msgid "Custom Maps"
+msgstr "Mapas Personalizados"
+
+#: frontend/src/metabase/admin/settings/selectors.js:306
+msgid "Add your own GeoJSON files to enable different region map visualizations"
+msgstr "Añade tus propios archivos GeoJSON para habilitar diferentes visualizaciones de mapas de región"
+
+#: frontend/src/metabase/admin/settings/selectors.js:313
+msgid "Public Sharing"
+msgstr "Uso compartido público"
+
+#: frontend/src/metabase/admin/settings/selectors.js:318
+msgid "Enable Public Sharing"
+msgstr "Habilitar el intercambio público"
+
+#: frontend/src/metabase/admin/settings/selectors.js:323
+msgid "Shared Dashboards"
+msgstr "Cuadros de Mando compartidos"
+
+#: frontend/src/metabase/admin/settings/selectors.js:329
+msgid "Shared Questions"
+msgstr "Preguntas compartidas"
+
+#: frontend/src/metabase/admin/settings/selectors.js:336
+msgid "Embedding in other Applications"
+msgstr "Incrustar en otras aplicaciones"
+
+#: frontend/src/metabase/admin/settings/selectors.js:363
+msgid "Enable Embedding Metabase in other Applications"
+msgstr "Habilitar incrustación de Metabase en otras aplicaciones"
+
+#: frontend/src/metabase/admin/settings/selectors.js:373
+msgid "Embedding secret key"
+msgstr "Clave secreta de incrustación"
+
+#: frontend/src/metabase/admin/settings/selectors.js:379
+msgid "Embedded Dashboards"
+msgstr "Cuadros de Mando embebidos"
+
+#: frontend/src/metabase/admin/settings/selectors.js:385
+msgid "Embedded Questions"
+msgstr "Preguntas embebidas"
+
+#: frontend/src/metabase/admin/settings/selectors.js:392
+msgid "Caching"
+msgstr "Almacenamiento en caché"
+
+#: frontend/src/metabase/admin/settings/selectors.js:397
+msgid "Enable Caching"
+msgstr "Habilitar caché"
+
+#: frontend/src/metabase/admin/settings/selectors.js:402
+msgid "Minimum Query Duration"
+msgstr "Duración mínima de la consulta"
+
+#: frontend/src/metabase/admin/settings/selectors.js:409
+msgid "Cache Time-To-Live (TTL) multiplier"
+msgstr "Multiplicador Time-to-Live (TTL) de caché"
+
+#: frontend/src/metabase/admin/settings/selectors.js:416
+msgid "Max Cache Entry Size"
+msgstr "Tamaño máximo de entrada de caché"
+
+#: frontend/src/metabase/alert/alert.js:60
+msgid "Your alert is all set up."
+msgstr "Tu alerta está configurada."
+
+#: frontend/src/metabase/alert/alert.js:101
+msgid "Your alert was updated."
+msgstr "Tu alerta fue actualizada."
+
+#: frontend/src/metabase/alert/alert.js:149
+msgid "The alert was successfully deleted."
+msgstr "La alerta fue eliminada correctamente."
+
+#: frontend/src/metabase/auth/auth.js:33
+msgid "Please enter a valid formatted email address."
+msgstr "Por favor ingresa una dirección de correo electrónico con formato válido."
+
+#: frontend/src/metabase/auth/auth.js:116
+#: frontend/src/metabase/setup/components/UserStep.jsx:110
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:69
+msgid "Passwords do not match"
+msgstr "Las contraseñas no coinciden"
+
+#: frontend/src/metabase/auth/components/BackToLogin.jsx:6
+msgid "Back to login"
+msgstr "Volver al inicio de sesión"
+
+#: frontend/src/metabase/auth/components/GoogleNoAccount.jsx:15
+msgid "No Metabase account exists for this Google account."
+msgstr "No existe una cuenta de Metabase para esta cuenta de Google."
+
+#: frontend/src/metabase/auth/components/GoogleNoAccount.jsx:17
+msgid "You'll need an administrator to create a Metabase account before you can use Google to log in."
+msgstr "Necesitará un administrador para crear una cuenta Metabase antes de poder usar Google para iniciar sesión."
+
+#: frontend/src/metabase/auth/components/SSOLoginButton.jsx:18
+msgid "Sign in with {0}"
+msgstr "Inicia sesión con {0}"
+
+#: frontend/src/metabase/auth/containers/ForgotPasswordApp.jsx:56
+msgid "Please contact an administrator to have them reset your password"
+msgstr "Por favor, ponte en contacto con un administrador para que restablezca tu contraseña"
+
+#: frontend/src/metabase/auth/containers/ForgotPasswordApp.jsx:69
+msgid "Forgot password"
+msgstr "Olvidé mi contraseña"
+
+#: frontend/src/metabase/auth/containers/ForgotPasswordApp.jsx:84
+msgid "The email you use for your Metabase account"
+msgstr "El correo electrónico que utilizas para tu cuenta Metabase"
+
+#: frontend/src/metabase/auth/containers/ForgotPasswordApp.jsx:99
+msgid "Send password reset email"
+msgstr "Enviar correo electrónico de restablecimiento de contraseña"
+
+#: frontend/src/metabase/auth/containers/ForgotPasswordApp.jsx:110
+msgid "Check your email for instructions on how to reset your password."
+msgstr "Consulta tu correo electrónico para obtener instrucciones sobre cómo restablecer tu contraseña."
+
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:128
+msgid "Sign in to Metabase"
+msgstr "Iniciar sesión en Metabase"
+
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:138
+msgid "OR"
+msgstr "O"
+
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:157
+msgid "Username or email address"
+msgstr "Nombre de usuario o dirección de correo electrónico"
+
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:217
+msgid "Sign in"
+msgstr "Acceder"
+
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:230
+msgid "I seem to have forgotten my password"
+msgstr "Parece que olvidé mi contraseña"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:102
+msgid "request a new reset email"
+msgstr "solicitar un nuevo correo electrónico de reinicio"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:120
+msgid "Whoops, that's an expired link"
+msgstr "Vaya, ese es un enlace caducado"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:122
+msgid "For security reasons, password reset links expire after a little while. If you still need\n"
+"to reset your password, you can {0}."
+msgstr "Por razones de seguridad, los enlaces de restablecimiento de contraseña caducan después de un tiempo.\n"
+"Si aún necesita restablecer tu contraseña, puedes {0}"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:147
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:126
+msgid "New password"
+msgstr "Nueva contraseña"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:149
+msgid "To keep your data secure, passwords {0}"
+msgstr "Para mantener tus datos seguros, las contraseñas {0}"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:163
+msgid "Create a new password"
+msgstr "Crear una nueva contraseña"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:170
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:141
+msgid "Make sure its secure like the instructions above"
+msgstr "Ha de cumplir las instrucciones anteriores"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:184
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:150
+msgid "Confirm new password"
+msgstr "Confirmar nueva contraseña"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:191
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:159
+msgid "Make sure it matches the one you just entered"
+msgstr "Tiene que coincidir con la que acabas de poner"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:216
+msgid "Your password has been reset."
+msgstr "Tu contraseña ha sido restablecida."
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:222
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:227
+msgid "Sign in with your new password"
+msgstr "Inicia sesión con tu nueva contraseña"
+
+#: frontend/src/metabase/components/ActionButton.jsx:53
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:182
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:184
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:251
+msgid "Save failed"
+msgstr "Ha fallado"
+
+#: frontend/src/metabase/components/ActionButton.jsx:54
+#: frontend/src/metabase/components/SaveStatus.jsx:60
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:183
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:124
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:185
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:225
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:252
+msgid "Saved"
+msgstr "Guardado"
+
+#: frontend/src/metabase/components/Alert.jsx:12
+msgid "Ok"
+msgstr "Vale"
+
+#: frontend/src/metabase/components/ArchiveCollectionModal.jsx:40
+msgid "Archive this collection?"
+msgstr "¿Archivar esta colección?"
+
+#: frontend/src/metabase/components/ArchiveCollectionModal.jsx:45
+msgid "The dashboards, collections, and pulses in this collection will also be archived."
+msgstr "Los cuadros de mando, colecciones y pulsos guardados en esta colección también se archivarán."
+
+#: frontend/src/metabase/components/ArchiveCollectionModal.jsx:49
+#: frontend/src/metabase/components/CollectionLanding.jsx:587
+#: frontend/src/metabase/components/EntityItem.jsx:54
+#: frontend/src/metabase/components/EntityMenu.info.js:31
+#: frontend/src/metabase/components/EntityMenu.info.js:87
+#: frontend/src/metabase/home/containers/ArchiveApp.jsx:48
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:195
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:200
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:201
+#: frontend/src/metabase/query_builder/containers/ArchiveQuestionModal.jsx:40
+#: frontend/src/metabase/query_builder/containers/ArchiveQuestionModal.jsx:53
+#: frontend/src/metabase/routes.jsx:199
+msgid "Archive"
+msgstr "Archivar"
+
+#: frontend/src/metabase/containers/ErrorPages.jsx:63
+msgid "This {0} has been archived"
+msgstr "Este {0} ha sido archivado"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:679
+msgid "View the archive"
+msgstr "Ver el archivo"
+
+#: frontend/src/metabase/components/ArchivedItem.jsx:39
+msgid "Unarchive this {0}"
+msgstr "Desarchive este {0}"
+
+#: frontend/src/metabase/components/BrowseApp.jsx:93
+#: frontend/src/metabase/components/BrowseApp.jsx:152
+#: frontend/src/metabase/components/BrowseApp.jsx:243
+#: frontend/src/metabase/containers/Overworld.jsx:222
+msgid "Our data"
+msgstr "Nuestros datos"
+
+#: frontend/src/metabase/components/BrowseApp.jsx:188
+#: frontend/src/metabase/reference/databases/TableSidebar.jsx:51
+msgid "X-ray this table"
+msgstr "Aplica rayos-X a esta tabla"
+
+#: frontend/src/metabase/components/BrowseApp.jsx:201
+#: frontend/src/metabase/containers/Overworld.jsx:249
+msgid "Learn about this table"
+msgstr "Aprende sobre esta tabla"
+
+#: frontend/src/metabase/components/Button.info.js:11
+#: frontend/src/metabase/components/Button.info.js:12
+#: frontend/src/metabase/components/Button.info.js:13
+msgid "Clickity click"
+msgstr "Clic"
+
+#: frontend/src/metabase/components/ButtonWithStatus.jsx:9
+msgid "Saved!"
+msgstr "Guardado!"
+
+#: frontend/src/metabase/components/ButtonWithStatus.jsx:10
+msgid "Saving failed."
+msgstr "No se ha podido guardar."
+
+#: frontend/src/metabase/components/Calendar.jsx:119
+msgid "Su"
+msgstr "D"
+
+#: frontend/src/metabase/components/Calendar.jsx:119
+msgid "Mo"
+msgstr "L"
+
+#: frontend/src/metabase/components/Calendar.jsx:119
+msgid "Tu"
+msgstr "M"
+
+#: frontend/src/metabase/components/Calendar.jsx:119
+msgid "We"
+msgstr "X"
+
+#: frontend/src/metabase/components/Calendar.jsx:119
+msgid "Th"
+msgstr "J"
+
+#: frontend/src/metabase/components/Calendar.jsx:119
+msgid "Fr"
+msgstr "V"
+
+#: frontend/src/metabase/components/Calendar.jsx:119
+msgid "Sa"
+msgstr "S"
+
+#: frontend/src/metabase/components/ChannelSetupMessage.jsx:41
+msgid "Your admin's email address"
+msgstr "La dirección de correo electrónico de tu administrador"
+
+#: frontend/src/metabase/components/ChannelSetupModal.jsx:37
+msgid "To send {0}, you'll need to set up {1} integration."
+msgstr "Para enviar {0}, tendrás que configurar la integración con {1}."
+
+#: frontend/src/metabase/components/ChannelSetupModal.jsx:38
+#: frontend/src/metabase/components/ChannelSetupModal.jsx:41
+msgid " or "
+msgstr " o "
+
+#: frontend/src/metabase/components/ChannelSetupModal.jsx:40
+msgid "To send {0}, an admin needs to set up {1} integration."
+msgstr "Para enviar {0}, un administrador necesita configurar la integración con {1}."
+
+#: frontend/src/metabase/components/CollectionEmptyState.jsx:15
+msgid "This collection is empty, like a blank canvas"
+msgstr "Esta colección está vacía, como una hoja en blanco"
+
+#: frontend/src/metabase/components/CollectionEmptyState.jsx:16
+msgid "You can use collections to organize and group dashboards, questions and pulses for your team or yourself"
+msgstr "Puedes usar colecciones para organizar y agrupar cuadros de mando, preguntas y pulsos para tu equipo o para ti"
+
+#: frontend/src/metabase/components/CollectionEmptyState.jsx:28
+msgid "Create another collection"
+msgstr "Crea otra colección"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:61
+msgid "Dashboards let you collect and share data in one place."
+msgstr "Los cuadros de mando te permiten recopilar y compartir datos en un solo lugar."
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:70
+msgid "Pulses let you send out the latest data to your team on a schedule via email or slack."
+msgstr "Los pulsos te permiten enviar datos de Metabase a correo electrónico o Slack en el horario que deseas."
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:79
+msgid "Questions are a saved look at your data."
+msgstr "Las preguntas son vistas guardadas de tus datos."
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:275
+msgid "Pins"
+msgstr "Pins"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:329
+msgid "Drag something here to pin it to the top"
+msgstr "Arrastra algo aquí para pegarlo arriba"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:733
+#: frontend/src/metabase/components/CollectionLanding.jsx:341
+#: frontend/src/metabase/home/containers/SearchApp.jsx:35
+#: frontend/src/metabase/home/containers/SearchApp.jsx:96
+msgid "Collections"
+msgstr "Colecciones"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:411
+#: frontend/src/metabase/components/CollectionLanding.jsx:434
+msgid "Drag here to un-pin"
+msgstr "Arrastra aquí para despegarlo"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:469
+msgid "{0} item selected"
+msgid_plural "{0} items selected"
+msgstr[0] "{0} elemento seleccionado"
+msgstr[1] "{0} elementos seleccionados"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:487
+msgid "Move {0} items?"
+msgstr "¿Mover {1} elemento(s)?"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:488
+msgid "Move \"{0}\"?"
+msgstr "¿Mover \"{0}\"?"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:594
+#: frontend/src/metabase/components/EntityMenu.info.js:29
+#: frontend/src/metabase/components/EntityMenu.info.js:85
+#: frontend/src/metabase/containers/CollectionMoveModal.jsx:78
+msgid "Move"
+msgstr "Mover"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:656
+msgid "Edit this collection"
+msgstr "Edita esta colección"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:664
+msgid "Archive this collection"
+msgstr "Archivar esta colección"
+
+#: frontend/src/metabase/components/CollectionList.jsx:64
+#: frontend/src/metabase/entities/collections.js:148
+msgid "My personal collection"
+msgstr "Mi colección personal"
+
+#: frontend/src/metabase/components/CollectionList.jsx:106
+#: frontend/src/metabase/containers/CollectionForm.jsx:9
+msgid "New collection"
+msgstr "Nueva colección"
+
+#: frontend/src/metabase/components/CopyButton.jsx:35
+msgid "Copied!"
+msgstr "Copiado!"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:216
+msgid "Use an SSH-tunnel for database connections"
+msgstr "Utiliza un túnel SSH para las conexiones a la base de datos"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:218
+msgid "Some database installations can only be accessed by connecting through an SSH bastion host.\n"
+"This option also provides an extra layer of security when a VPN is not available.\n"
+"Enabling this is usually slower than a direct connection."
+msgstr "Algunas instalaciones de bases de datos solo se pueden acceder conectándose a través de un túnel SSH.\n"
+"Esta opción también proporciona una capa adicional de seguridad cuando no dispones de una VPN.\n"
+"Esto suele ser más lento que una conexión directa."
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:274
+msgid "This is a large database, so let me choose when Metabase syncs and scans"
+msgstr "Esta es una base de datos grande, así que déjame elegir cuándo Metabase sincroniza y escanea"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:276
+msgid "By default, Metabase does a lightweight hourly sync and an intensive daily scan of field values.\n"
+"If you have a large database, we recommend turning this on and reviewing when and how often the field value scans happen."
+msgstr "De forma predeterminada, Metabase realiza una sincronización ligera cada hora y un análisis diario intensivo de los valores de campo.\n"
+"Si tienes una base de datos grande, te recomendamos activarla y revisar cuándo y con qué frecuencia ocurren los escaneos de valores de campo."
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:292
+msgid "{0} to generate a Client ID and Client Secret for your project."
+msgstr "{0} para generar un ID y clave secreta de cliente para tu proyecto."
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:294
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:321
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:356
+msgid "Click here"
+msgstr "Haz clic aquí"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:297
+msgid "Choose \"Other\" as the application type. Name it whatever you'd like."
+msgstr "Elige \"Otro\" como tipo de aplicación. Llámalo como quieras"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:319
+msgid "{0} to get an auth code"
+msgstr "{0} para obtener un código de autenticación"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:331
+msgid "with Google Drive permissions"
+msgstr "con permisos de Google Drive"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:351
+msgid "To use Metabase with this data you must enable API access in the Google Developers Console."
+msgstr "Para usar Metabase con estos datos, debes habilitar el acceso a la API en Google Developers Console."
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:354
+msgid "{0} to go to the console if you haven't already done so."
+msgstr "{0} para ir a la consola si aún no lo has hecho."
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:403
+msgid "How would you like to refer to this database?"
+msgstr "¿Cómo te gustaría llamar esta base de datos?"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:430
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:97
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:240
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:188
+#: frontend/src/metabase/setup/components/DatabaseSchedulingStep.jsx:74
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:116
+#: frontend/src/metabase/setup/components/UserStep.jsx:308
+msgid "Next"
+msgstr "Siguiente"
+
+#: frontend/src/metabase/components/DeleteModalWithConfirm.jsx:80
+msgid "Delete this {0}"
+msgstr "Elimina este {0}"
+
+#: frontend/src/metabase/components/EntityItem.jsx:42
+msgid "Pin this item"
+msgstr "Fija este elemento"
+
+#: frontend/src/metabase/components/EntityItem.jsx:48
+msgid "Move this item"
+msgstr "Mueve este elemento"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:24
+#: frontend/src/metabase/components/EntityMenu.info.js:80
+msgid "Edit this question"
+msgstr "Editar esta pregunta"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:26
+#: frontend/src/metabase/components/EntityMenu.info.js:47
+#: frontend/src/metabase/components/EntityMenu.info.js:82
+#: frontend/src/metabase/components/EntityMenu.info.js:99
+msgid "Action type"
+msgstr "Tipo acción"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:28
+#: frontend/src/metabase/components/EntityMenu.info.js:84
+msgid "View revision history"
+msgstr "Ver el historial de revisiones"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:29
+#: frontend/src/metabase/components/EntityMenu.info.js:85
+msgid "Move action"
+msgstr "Acción de mover"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:33
+#: frontend/src/metabase/components/EntityMenu.info.js:89
+msgid "Archive action"
+msgstr "Acción de archivar"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:45
+#: frontend/src/metabase/components/EntityMenu.info.js:97
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:329
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:342
+msgid "Add to dashboard"
+msgstr "Añadir a Cuadro de Mando"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:49
+#: frontend/src/metabase/components/EntityMenu.info.js:101
+msgid "Download results"
+msgstr "Descarga los resultados"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:51
+#: frontend/src/metabase/components/EntityMenu.info.js:103
+#: frontend/src/metabase/public/components/widgets/EmbedWidget.jsx:52
+msgid "Sharing and embedding"
+msgstr "Compartiendo y Incrustando"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:53
+#: frontend/src/metabase/components/EntityMenu.info.js:105
+msgid "Another action type"
+msgstr "Otro tipo de acción"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:65
+#: frontend/src/metabase/components/EntityMenu.info.js:67
+#: frontend/src/metabase/components/EntityMenu.info.js:113
+#: frontend/src/metabase/components/EntityMenu.info.js:115
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:449
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:454
+msgid "Get alerts about this"
+msgstr "Recibe alertas sobre esto"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:69
+#: frontend/src/metabase/components/EntityMenu.info.js:117
+msgid "View the SQL"
+msgstr "Ver el SQL"
+
+#: frontend/src/metabase/components/EntitySegments.jsx:18
+msgid "Segments for this"
+msgstr "Segmentos para este"
+
+#: frontend/src/metabase/components/ErrorDetails.jsx:20
+msgid "Show error details"
+msgstr "Mostrar detalles del error"
+
+#: frontend/src/metabase/components/ErrorDetails.jsx:26
+msgid "Here's the full error message"
+msgstr "Este es el mensaje de error completo"
+
+#: frontend/src/metabase/components/ExplorePane.jsx:19
+msgid "Hi, Metabot here."
+msgstr "Hola, soy Metabot"
+
+#: frontend/src/metabase/components/ExplorePane.jsx:95
+msgid "Based on the schema"
+msgstr "Basado en el esquema"
+
+#: frontend/src/metabase/components/ExplorePane.jsx:174
+msgid "A look at your"
+msgstr "Una vista a tus"
+
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:234
+msgid "Search the list"
+msgstr "Busca la lista"
+
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:238
+msgid "Search by {0}"
+msgstr "Buscar por {0}"
+
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:240
+msgid " or enter an ID"
+msgstr "o introduce un ID"
+
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:244
+msgid "Enter an ID"
+msgstr "Introduce un ID"
+
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:246
+msgid "Enter a number"
+msgstr "Introduce un número"
+
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:248
+msgid "Enter some text"
+msgstr "Introduce un texto"
+
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:355
+msgid "No matching {0} found."
+msgstr "No se encontraron {0} coincidentes."
+
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:363
+msgid "Including every option in your filter probably won’t do much…"
+msgstr "Incluir cada opción en tu filtro probablemente no hará mucho…"
+
+#: frontend/src/metabase/containers/ErrorPages.jsx:24
+msgid "Something's gone wrong"
+msgstr "Lo siento. Algo salió mal."
+
+#: frontend/src/metabase/containers/ErrorPages.jsx:25
+msgid "We've run into an error. You can try refreshing the page, or just go back."
+msgstr "Nos encontramos con un error. Puedes intentar refrescar la página, o simplemente volver atrás"
+
+#: frontend/src/metabase/components/Header.jsx:97
+#: frontend/src/metabase/components/HeaderBar.jsx:45
+#: frontend/src/metabase/components/ListItem.jsx:37
+#: frontend/src/metabase/reference/components/Detail.jsx:47
+#: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:158
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:213
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:191
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:205
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:209
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:209
+msgid "No description yet"
+msgstr "Sin descripción todavía"
+
+#: frontend/src/metabase/components/Header.jsx:112
+msgid "New {0}"
+msgstr "Nuevo {0}"
+
+#: frontend/src/metabase/components/Header.jsx:123
+msgid "Asked by {0}"
+msgstr "Preguntado por {0}"
+
+#: frontend/src/metabase/components/HistoryModal.jsx:13
+msgid "Today, "
+msgstr "Hoy."
+
+#: frontend/src/metabase/components/HistoryModal.jsx:15
+msgid "Yesterday, "
+msgstr "Ayer."
+
+#: frontend/src/metabase/components/HistoryModal.jsx:68
+msgid "First revision."
+msgstr "Primera revisión."
+
+#: frontend/src/metabase/components/HistoryModal.jsx:70
+msgid "Reverted to an earlier revision and {0}"
+msgstr "Revertido a una revisión anterior y {0}"
+
+#: frontend/src/metabase/components/HistoryModal.jsx:82
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:289
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:379
+#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:54
+msgid "Revision history"
+msgstr "Historial de Revisiones"
+
+#: frontend/src/metabase/components/HistoryModal.jsx:90
+msgid "When"
+msgstr "Cuando"
+
+#: frontend/src/metabase/components/HistoryModal.jsx:91
+msgid "Who"
+msgstr "Quien"
+
+#: frontend/src/metabase/components/HistoryModal.jsx:92
+msgid "What"
+msgstr "Que"
+
+#: frontend/src/metabase/components/HistoryModal.jsx:113
+msgid "Revert"
+msgstr "Revertir"
+
+#: frontend/src/metabase/components/HistoryModal.jsx:114
+msgid "Reverting…"
+msgstr "Revertiendo…"
+
+#: frontend/src/metabase/components/HistoryModal.jsx:115
+msgid "Revert failed"
+msgstr "Falló la reversión"
+
+#: frontend/src/metabase/components/HistoryModal.jsx:116
+msgid "Reverted"
+msgstr "Revertido"
+
+#: frontend/src/metabase/components/ItemTypeFilterBar.jsx:13
+msgid "Everything"
+msgstr "Todo"
+
+#: frontend/src/metabase/components/ItemTypeFilterBar.jsx:18
+#: frontend/src/metabase/home/containers/SearchApp.jsx:73
+msgid "Dashboards"
+msgstr "Cuadros de Mando"
+
+#: frontend/src/metabase/components/ItemTypeFilterBar.jsx:23
+#: frontend/src/metabase/home/containers/SearchApp.jsx:119
+msgid "Questions"
+msgstr "Preguntas"
+
+#: frontend/src/metabase/components/ItemTypeFilterBar.jsx:28
+#: frontend/src/metabase/routes.jsx:320
+msgid "Pulses"
+msgstr "Pulsos"
+
+#: frontend/src/metabase/components/LeftNavPane.jsx:36
+#: frontend/src/metabase/query_builder/components/dataref/DataReference.jsx:86
+msgid "Back"
+msgstr "Atrás"
+
+#: frontend/src/metabase/components/ListSearchField.jsx:18
+msgid "Find..."
+msgstr "Encontrar..."
+
+#: frontend/src/metabase/components/LoadingAndErrorWrapper.jsx:48
+msgid "An error occured"
+msgstr "Ha ocurrido un error"
+
+#: frontend/src/metabase/components/LoadingAndErrorWrapper.jsx:35
+msgid "Loading..."
+msgstr "Cargando..."
+
+#: frontend/src/metabase/components/NewsletterForm.jsx:71
+msgid "Metabase Newsletter"
+msgstr "Boletín Metabase"
+
+#: frontend/src/metabase/components/NewsletterForm.jsx:81
+msgid "Get infrequent emails about new releases and feature updates."
+msgstr "Recibe correos electrónicos infrecuentes sobre nuevos lanzamientos y actualizaciones de funciones."
+
+#: frontend/src/metabase/components/NewsletterForm.jsx:99
+msgid "Subscribe"
+msgstr "Suscribir"
+
+#: frontend/src/metabase/components/NewsletterForm.jsx:106
+msgid "You're subscribed. Thanks for using Metabase!"
+msgstr "Estás suscrito. ¡Gracias por usar Metabase!"
+
+#: frontend/src/metabase/containers/ErrorPages.jsx:44
+msgid "We're a little lost..."
+msgstr "Estamos un poco perdidos..."
+
+#: frontend/src/metabase/components/PasswordReveal.jsx:27
+msgid "Temporary Password"
+msgstr "Contraseña Temporal"
+
+#: frontend/src/metabase/components/PasswordReveal.jsx:68
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:334
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:349
+msgid "Hide"
+msgstr "Esconder"
+
+#: frontend/src/metabase/components/PasswordReveal.jsx:68
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:335
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:350
+msgid "Show"
+msgstr "Mostrar"
+
+#: frontend/src/metabase/components/QuestionSavedModal.jsx:17
+msgid "Saved! Add this to a dashboard?"
+msgstr "¡Guardado! ¿Añadir esto a un cuadro de mando?"
+
+#: frontend/src/metabase/components/QuestionSavedModal.jsx:25
+msgid "Yes please!"
+msgstr "Sí, por favor!"
+
+#: frontend/src/metabase/components/QuestionSavedModal.jsx:29
+msgid "Not now"
+msgstr "Ahora no"
+
+#: frontend/src/metabase/components/SaveStatus.jsx:53
+msgid "Error:"
+msgstr "Error:"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:23
+msgid "Sunday"
+msgstr "Domingo"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:24
+msgid "Monday"
+msgstr "Lunes"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:25
+msgid "Tuesday"
+msgstr "Martes"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:26
+msgid "Wednesday"
+msgstr "Miércoles"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:27
+msgid "Thursday"
+msgstr "Jueves"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:28
+msgid "Friday"
+msgstr "Viernes"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:29
+msgid "Saturday"
+msgstr "Sábado"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:33
+msgid "First"
+msgstr "Primero"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:34
+msgid "Last"
+msgstr "Ultimo"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:35
+msgid "15th (Midpoint)"
+msgstr "15to (punto medio)"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:125
+msgid "Calendar Day"
+msgstr "Día Calendario"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:210
+msgid "your Metabase timezone"
+msgstr "tu zona horaria en Metabase"
+
+#: frontend/src/metabase/components/SearchHeader.jsx:21
+msgid "Filter this list..."
+msgstr "Filtra esta lista..."
+
+#: frontend/src/metabase/components/Select.info.js:8
+msgid "Blue"
+msgstr "Azul"
+
+#: frontend/src/metabase/components/Select.info.js:9
+msgid "Green"
+msgstr "Verde"
+
+#: frontend/src/metabase/components/Select.info.js:10
+msgid "Red"
+msgstr "Rojo"
+
+#: frontend/src/metabase/components/Select.info.js:11
+msgid "Yellow"
+msgstr "Amarillo"
+
+#: frontend/src/metabase/components/Select.info.js:14
+msgid "A component used to make a selection"
+msgstr "Un componente utilizado para hacer una selección"
+
+#: frontend/src/metabase/components/Select.info.js:20
+#: frontend/src/metabase/components/Select.info.js:28
+msgid "Selected"
+msgstr "Seleccionado"
+
+#: frontend/src/metabase/components/Select.jsx:281
+msgid "Nothing to select"
+msgstr "Nada para seleccionar"
+
+#: frontend/src/metabase/containers/ErrorPages.jsx:54
+msgid "Sorry, you don’t have permission to see that."
+msgstr "Lo siento, no tienes permiso para ver eso."
+
+#: frontend/src/metabase/components/form/FormMessage.jsx:5
+msgid "Unknown error encountered"
+msgstr "Error desconocido encontrado"
+
+#: frontend/src/metabase/components/form/StandardForm.jsx:63
+#: frontend/src/metabase/nav/containers/Navbar.jsx:300
+msgid "Create"
+msgstr "Crea"
+
+#: frontend/src/metabase/containers/DashboardForm.jsx:9
+msgid "Create dashboard"
+msgstr "Crea un Cuadro de Mando"
+
+#: frontend/src/metabase/containers/EntitySearch.jsx:35
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:324
+#: frontend/src/metabase/visualizations/visualizations/Table.jsx:44
+msgid "Table"
+msgstr "Tabla"
+
+#: frontend/src/metabase/containers/EntitySearch.jsx:42
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:299
+msgid "Database"
+msgstr "Base de Datos"
+
+#: frontend/src/metabase/containers/EntitySearch.jsx:49
+msgid "Creator"
+msgstr "Creador"
+
+#: frontend/src/metabase/containers/EntitySearch.jsx:238
+msgid "No results found"
+msgstr "No se han encontrado resultados"
+
+#: frontend/src/metabase/containers/EntitySearch.jsx:239
+msgid "Try adjusting your filter to find what you’re looking for."
+msgstr "Intenta ajustar tu filtro para encontrar lo que estás buscando."
+
+#: frontend/src/metabase/containers/EntitySearch.jsx:258
+msgid "View by"
+msgstr "Ver por"
+
+#: frontend/src/metabase/containers/EntitySearch.jsx:494
+#: frontend/src/metabase/query_builder/components/AggregationWidget.jsx:69
+#: frontend/src/metabase/tutorial/TutorialModal.jsx:34
+msgid "of"
+msgstr "de"
+
+#: frontend/src/metabase/containers/Overworld.jsx:78
+msgid "Don't tell anyone, but you're my favorite."
+msgstr "No se lo digas a nadie, pero eres mi favorito."
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:85
+msgid "Once you connect your own data, I can show you some automatic explorations called x-rays. Here are some examples with sample data."
+msgstr "En cuanto conectas tus propios datos, puedo mostrarte algunas exploraciones automáticas llamadas rayos-X. Aquí hay algunos ejemplos con datos de muestra."
+
+#: frontend/src/metabase/containers/Overworld.jsx:131
+#: frontend/src/metabase/containers/Overworld.jsx:302
+#: frontend/src/metabase/reference/components/GuideHeader.jsx:12
+msgid "Start here"
+msgstr "Empieza aquí"
+
+#: frontend/src/metabase/containers/Overworld.jsx:297
+#: frontend/src/metabase/entities/collections.js:140
+#: src/metabase/models/collection.clj
+msgid "Our analytics"
+msgstr "Nuestra Analítica"
+
+#: frontend/src/metabase/containers/Overworld.jsx:206
+msgid "Browse all items"
+msgstr "Ver todos los elementos"
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:165
+msgid "Replace or save as new?"
+msgstr "¿Reemplazar o guardar como nuevo?"
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:173
+msgid "Replace original question, \"{0}\""
+msgstr "Reemplazar la pregunta original, \"{0}\""
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:178
+msgid "Save as new question"
+msgstr "Guardar como nueva pregunta"
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:187
+msgid "First, save your question"
+msgstr "Primero, guarda tu pregunta"
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:188
+msgid "Save question"
+msgstr "Guardar pregunta"
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:224
+msgid "What is the name of your card?"
+msgstr "¿Cuál es el nombre de tu tarjeta?"
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:232
+#: frontend/src/metabase/entities/collections.js:94
+#: frontend/src/metabase/entities/dashboards.js:102
+#: frontend/src/metabase/lib/core.js:45 frontend/src/metabase/lib/core.js:200
+#: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:156
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:211
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:189
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:203
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:207
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:207
+#: frontend/src/metabase/visualizations/lib/settings.js:163
+msgid "Description"
+msgstr "Descripción"
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:238
+#: frontend/src/metabase/entities/dashboards.js:104
+msgid "It's optional but oh, so helpful"
+msgstr "Es opcional pero, tan útil"
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:245
+#: frontend/src/metabase/entities/dashboards.js:108
+msgid "Which collection should this go in?"
+msgstr "¿En qué colección debería ir esto?"
+
+#: frontend/src/metabase/containers/UndoListing.jsx:34
+msgid "modified"
+msgstr "modificado"
+
+#: frontend/src/metabase/containers/UndoListing.jsx:34
+msgid "item"
+msgstr "elemento"
+
+#: frontend/src/metabase/containers/UndoListing.jsx:81
+msgid "Undo"
+msgstr "Deshacer"
+
+#: frontend/src/metabase/dashboard/components/AddSeriesModal.jsx:270
+msgid "Applying Question"
+msgstr "Aplicando Pregunta"
+
+#: frontend/src/metabase/dashboard/components/AddSeriesModal.jsx:274
+msgid "That question isn't compatible"
+msgstr "Esa pregunta no es compatible"
+
+#: frontend/src/metabase/dashboard/components/AddSeriesModal.jsx:310
+msgid "Search for a question"
+msgstr "Busca una pregunta"
+
+#: frontend/src/metabase/dashboard/components/AddSeriesModal.jsx:339
+msgid "We're not sure if this question is compatible"
+msgstr "No estamos seguros si esta pregunta es compatible"
+
+#: frontend/src/metabase/dashboard/components/ArchiveDashboardModal.jsx:43
+msgid "Archive Dashboard"
+msgstr "Archivar Cuadro de Mando"
+
+#: frontend/src/metabase/dashboard/components/DashCardParameterMapper.jsx:20
+msgid "Make sure to make a selection for each series, or the filter won't work on this card."
+msgstr "Asegúrate de hacer una selección para cada serie, o el filtro no funcionará en esta tarjeta."
+
+#: frontend/src/metabase/dashboard/components/Dashboard.jsx:284
+msgid "This dashboard is looking empty."
+msgstr "Este cuadro de mando parece estar vacío."
+
+#: frontend/src/metabase/dashboard/components/Dashboard.jsx:287
+msgid "Add a question to start making it useful!"
+msgstr "¡Añade una pregunta para hacerlo útil!"
+
+#: frontend/src/metabase/dashboard/components/DashboardActions.jsx:36
+msgid "Daytime mode"
+msgstr "Modo Diurno"
+
+#: frontend/src/metabase/dashboard/components/DashboardActions.jsx:36
+msgid "Nighttime mode"
+msgstr "Modo Nocturno"
+
+#: frontend/src/metabase/dashboard/components/DashboardActions.jsx:53
+msgid "Exit fullscreen"
+msgstr "Desactivar Pantalla Completa"
+
+#: frontend/src/metabase/dashboard/components/DashboardActions.jsx:53
+msgid "Enter fullscreen"
+msgstr "Activar Pantalla Completa"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:181
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:183
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:250
+msgid "Saving…"
+msgstr "Guardando…"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:216
+msgid "Add a question"
+msgstr "Añade una pregunta"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:219
+msgid "Add a question to this dashboard"
+msgstr "Añade una pregunta a este cuadro de mando"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:248
+msgid "Add a filter"
+msgstr "Añadir filtro"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:254
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:78
+msgid "Parameters"
+msgstr "Parámetros"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:275
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:279
+msgid "Add a text box"
+msgstr "Añade una caja de texto"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:301
+msgid "Move dashboard"
+msgstr "Mover cuadro de mando"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:313
+msgid "Edit dashboard"
+msgstr "Editar Cuadro de Mando"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:317
+msgid "Edit Dashboard Layout"
+msgstr "Editar Disposición del Cuadro de Mando"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:352
+msgid "You are editing a dashboard"
+msgstr "Estás editando un cuadro de mando"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:357
+msgid "Select the field that should be filtered for each card"
+msgstr "Selecciona el campo que debe filtrarse para cada tarjeta"
+
+#: frontend/src/metabase/dashboard/components/DashboardMoveModal.jsx:28
+msgid "Move dashboard to..."
+msgstr "Mover cuadro de mando a..."
+
+#: frontend/src/metabase/dashboard/components/DashboardMoveModal.jsx:52
+msgid "Dashboard moved to {0}"
+msgstr "Cuadro de Mando movido a {0}"
+
+#: frontend/src/metabase/dashboard/components/ParametersPopover.jsx:82
+msgid "What do you want to filter?"
+msgstr "¿Qué quieres filtrar?"
+
+#: frontend/src/metabase/dashboard/components/ParametersPopover.jsx:115
+msgid "What kind of filter?"
+msgstr "¿Qué tipo de filtro?"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:13
+msgid "Off"
+msgstr "Apagado"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:14
+msgid "1 minute"
+msgstr "1 minuto"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:15
+msgid "5 minutes"
+msgstr "5 minutos"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:16
+msgid "10 minutes"
+msgstr "10 minutos"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:17
+msgid "15 minutes"
+msgstr "15 minutos"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:18
+msgid "30 minutes"
+msgstr "30 minutos"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:19
+msgid "60 minutes"
+msgstr "60 minutos"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:31
+msgid "Auto-refresh"
+msgstr "Auto-Refresco"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:37
+msgid "Refreshing in"
+msgstr "Actualizando en"
+
+#: frontend/src/metabase/dashboard/components/RemoveFromDashboardModal.jsx:37
+msgid "Remove this question?"
+msgstr "¿Eliminar esta pregunta?"
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:70
+msgid "Your dashboard was saved"
+msgstr "Se ha guardado tu cuadro de mando."
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:75
+msgid "See it"
+msgstr "Verlo"
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:132
+msgid "Save this"
+msgstr "Guardalo"
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:165
+msgid "Show more about this"
+msgstr "Mostrar más sobre este/a"
+
+#: frontend/src/metabase/dashboard/containers/DashCardCardParameterMapper.jsx:140
+msgid "This card doesn't have any fields or parameters that can be mapped to this parameter type."
+msgstr "Esta tarjeta no tiene campos ni parámetros que coincidan con este tipo de parámetro."
+
+#: frontend/src/metabase/dashboard/containers/DashCardCardParameterMapper.jsx:142
+msgid "The values in this field don't overlap with the values of any other fields you've chosen."
+msgstr "Los valores en este campo no coinciden con los valores de ningún otro campo que hayas elegido."
+
+#: frontend/src/metabase/dashboard/containers/DashCardCardParameterMapper.jsx:186
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingFieldPicker.jsx:15
+msgid "No valid fields"
+msgstr "No hay campos válidos"
+
+#: frontend/src/metabase/entities/collections.js:90
+msgid "Name must be 100 characters or less"
+msgstr "El nombre debe tener 100 caracteres o menos"
+
+#: frontend/src/metabase/entities/collections.js:104
+msgid "Color is required"
+msgstr "Color es obligatorio"
+
+#: frontend/src/metabase/entities/dashboards.js:97
+msgid "What is the name of your dashboard?"
+msgstr "¿Cuál es el nombre de tu cuadro de mando?"
+
+#: frontend/src/metabase/home/components/Activity.jsx:92
+msgid "did some super awesome stuff that's hard to describe"
+msgstr "he hecho algunas cosas geniales que son difíciles de describir"
+
+#: frontend/src/metabase/home/components/Activity.jsx:101
+#: frontend/src/metabase/home/components/Activity.jsx:116
+msgid "created an alert about - "
+msgstr "creado una alerta sobre - "
+
+#: frontend/src/metabase/home/components/Activity.jsx:126
+#: frontend/src/metabase/home/components/Activity.jsx:141
+msgid "deleted an alert about - "
+msgstr "eliminado una alerta sobre - "
+
+#: frontend/src/metabase/home/components/Activity.jsx:152
+msgid "saved a question about "
+msgstr "guardado una pregunta sobre"
+
+#: frontend/src/metabase/home/components/Activity.jsx:165
+msgid "saved a question"
+msgstr "guardado una pregunta"
+
+#: frontend/src/metabase/home/components/Activity.jsx:169
+msgid "deleted a question"
+msgstr "eliminado una pregunta"
+
+#: frontend/src/metabase/home/components/Activity.jsx:172
+msgid "created a dashboard"
+msgstr "creado un cuadro de mando"
+
+#: frontend/src/metabase/home/components/Activity.jsx:175
+msgid "deleted a dashboard"
+msgstr "eliminado un cuadro de mando"
+
+#: frontend/src/metabase/home/components/Activity.jsx:181
+#: frontend/src/metabase/home/components/Activity.jsx:196
+msgid "added a question to the dashboard - "
+msgstr "añadido una pregunta al cuadro de mando - "
+
+#: frontend/src/metabase/home/components/Activity.jsx:206
+#: frontend/src/metabase/home/components/Activity.jsx:221
+msgid "removed a question from the dashboard - "
+msgstr "eliminado una pregunta del cuadro de mando - "
+
+#: frontend/src/metabase/home/components/Activity.jsx:231
+#: frontend/src/metabase/home/components/Activity.jsx:238
+msgid "received the latest data from"
+msgstr "recibido los últimos datos de"
+
+#: frontend/src/metabase/home/components/Activity.jsx:244
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:273
+msgid "Unknown"
+msgstr "Desconocido"
+
+#: frontend/src/metabase/home/components/Activity.jsx:251
+msgid "Hello World!"
+msgstr "Hola Mundo!"
+
+#: frontend/src/metabase/home/components/Activity.jsx:252
+msgid "Metabase is up and running."
+msgstr "Metabase está funcionando."
+
+#: frontend/src/metabase/home/components/Activity.jsx:258
+#: frontend/src/metabase/home/components/Activity.jsx:288
+msgid "added the metric "
+msgstr "añadido la métrica"
+
+#: frontend/src/metabase/home/components/Activity.jsx:272
+#: frontend/src/metabase/home/components/Activity.jsx:362
+msgid " to the "
+msgstr "a la"
+
+#: frontend/src/metabase/home/components/Activity.jsx:282
+#: frontend/src/metabase/home/components/Activity.jsx:322
+#: frontend/src/metabase/home/components/Activity.jsx:372
+#: frontend/src/metabase/home/components/Activity.jsx:413
+msgid " table"
+msgstr "tabla"
+
+#: frontend/src/metabase/home/components/Activity.jsx:298
+#: frontend/src/metabase/home/components/Activity.jsx:328
+msgid "made changes to the metric "
+msgstr "ha hecho cambios a la métrica"
+
+#: frontend/src/metabase/home/components/Activity.jsx:312
+#: frontend/src/metabase/home/components/Activity.jsx:403
+msgid " in the "
+msgstr "en el"
+
+#: frontend/src/metabase/home/components/Activity.jsx:335
+msgid "removed the metric "
+msgstr "eliminado la métrica"
+
+#: frontend/src/metabase/home/components/Activity.jsx:338
+msgid "created a pulse"
+msgstr "pulso creado"
+
+#: frontend/src/metabase/home/components/Activity.jsx:341
+msgid "deleted a pulse"
+msgstr "pulso eliminado"
+
+#: frontend/src/metabase/home/components/Activity.jsx:347
+#: frontend/src/metabase/home/components/Activity.jsx:378
+msgid "added the filter"
+msgstr "filtro añadido"
+
+#: frontend/src/metabase/home/components/Activity.jsx:388
+#: frontend/src/metabase/home/components/Activity.jsx:419
+msgid "made changes to the filter"
+msgstr "ha hecho cambios al filtro"
+
+#: frontend/src/metabase/home/components/Activity.jsx:426
+msgid "removed the filter {0}"
+msgstr "eliminado el filtro {0}"
+
+#: frontend/src/metabase/home/components/Activity.jsx:429
+msgid "joined!"
+msgstr "se ha unido!"
+
+#: frontend/src/metabase/home/components/Activity.jsx:529
+msgid "Hmmm, looks like nothing has happened yet."
+msgstr "Hmmm, parece que nada ha pasado aún."
+
+#: frontend/src/metabase/home/components/Activity.jsx:532
+msgid "Save a question and get this baby going!"
+msgstr "¡Guarda una pregunta y haz que esto funcione!"
+
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:19
+msgid "Ask questions and explore"
+msgstr "Haz preguntas y explora"
+
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:20
+msgid "Click on charts or tables to explore, or ask a new question using the easy interface or the powerful SQL editor."
+msgstr "Haz clic en cuadros o tablas para explorar, o haz una nueva pregunta usando el interfaz sencillo o el potente editor SQL."
+
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:30
+msgid "Make your own charts"
+msgstr "Crea tus propios gráficos"
+
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:31
+msgid "Create line charts, scatter plots, maps, and more."
+msgstr "Crea gráficos de líneas, gráficos de dispersión, mapas y más."
+
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:41
+msgid "Share what you find"
+msgstr "Comparte lo que encuentres"
+
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:42
+msgid "Create powerful and flexible dashboards, and send regular updates via email or Slack."
+msgstr "Crea cuadros de mando potentes y flexibles, y envía actualizaciones periódicas por correo electrónico o Slack."
+
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:97
+msgid "Let's go"
+msgstr "Vamos allá"
+
+#: frontend/src/metabase/home/components/NextStep.jsx:34
+msgid "Setup Tip"
+msgstr "Consejo de configuración"
+
+#: frontend/src/metabase/home/components/NextStep.jsx:40
+msgid "View all"
+msgstr "Ver todos"
+
+#: frontend/src/metabase/home/components/RecentViews.jsx:40
+msgid "Recently Viewed"
+msgstr "Visto Recientemente"
+
+#: frontend/src/metabase/home/components/RecentViews.jsx:75
+msgid "You haven't looked at any dashboards or questions recently"
+msgstr "No has consultado ningún cuadro de mando o pregunta recientemente"
+
+#: frontend/src/metabase/home/containers/ArchiveApp.jsx:82
+msgid "{0} items selected"
+msgstr "{0} elemento(s) seleccionado(s)"
+
+#: frontend/src/metabase/home/containers/ArchiveApp.jsx:102
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:172
+msgid "Unarchive"
+msgstr "Desarchivar"
+
+#: frontend/src/metabase/home/containers/HomepageApp.jsx:74
+#: frontend/src/metabase/nav/containers/Navbar.jsx:327
+msgid "Activity"
+msgstr "Actividad"
+
+#: frontend/src/metabase/home/containers/SearchApp.jsx:28
+msgid "Results for \"{0}\""
+msgstr "Resultados de \"{0}\""
+
+#: frontend/src/metabase/home/containers/SearchApp.jsx:142
+msgid "Pulse"
+msgstr "Pulso"
+
+#: frontend/src/metabase/lib/core.js:7
+msgid "Entity Key"
+msgstr "Clave Entidad"
+
+#: frontend/src/metabase/lib/core.js:8 frontend/src/metabase/lib/core.js:14
+#: frontend/src/metabase/lib/core.js:20
+msgid "Overall Row"
+msgstr "Fila General"
+
+#: frontend/src/metabase/lib/core.js:9
+msgid "The primary key for this table."
+msgstr "La clave principal para esta tabla."
+
+#: frontend/src/metabase/lib/core.js:13
+msgid "Entity Name"
+msgstr "Nombre Entidad"
+
+#: frontend/src/metabase/lib/core.js:15
+msgid "The \"name\" of each record. Usually a column called \"name\", \"title\", etc."
+msgstr "El \"nombre\" de cada registro. Por lo general, una columna llamada \"nombre\", \"título\", etc."
+
+#: frontend/src/metabase/lib/core.js:19
+msgid "Foreign Key"
+msgstr "Clave Foránea"
+
+#: frontend/src/metabase/lib/core.js:21
+msgid "Points to another table to make a connection."
+msgstr "Referencia otra tabla para establecer una conexión."
+
+#: frontend/src/metabase/lib/core.js:25
+msgid "Avatar Image URL"
+msgstr "URL Avatar"
+
+#: frontend/src/metabase/lib/core.js:26 frontend/src/metabase/lib/core.js:31
+#: frontend/src/metabase/lib/core.js:36 frontend/src/metabase/lib/core.js:41
+#: frontend/src/metabase/lib/core.js:46 frontend/src/metabase/lib/core.js:51
+#: frontend/src/metabase/lib/core.js:56 frontend/src/metabase/lib/core.js:61
+#: frontend/src/metabase/lib/core.js:66 frontend/src/metabase/lib/core.js:71
+#: frontend/src/metabase/lib/core.js:76 frontend/src/metabase/lib/core.js:81
+#: frontend/src/metabase/lib/core.js:86 frontend/src/metabase/lib/core.js:91
+#: frontend/src/metabase/lib/core.js:96 frontend/src/metabase/lib/core.js:101
+#: frontend/src/metabase/lib/core.js:106 frontend/src/metabase/lib/core.js:111
+#: frontend/src/metabase/lib/core.js:116 frontend/src/metabase/lib/core.js:121
+#: frontend/src/metabase/lib/core.js:126 frontend/src/metabase/lib/core.js:131
+#: frontend/src/metabase/lib/core.js:136 frontend/src/metabase/lib/core.js:141
+#: frontend/src/metabase/lib/core.js:146 frontend/src/metabase/lib/core.js:151
+#: frontend/src/metabase/lib/core.js:156 frontend/src/metabase/lib/core.js:161
+#: frontend/src/metabase/lib/core.js:166 frontend/src/metabase/lib/core.js:171
+#: frontend/src/metabase/lib/core.js:176 frontend/src/metabase/lib/core.js:181
+#: frontend/src/metabase/lib/core.js:186 frontend/src/metabase/lib/core.js:191
+#: frontend/src/metabase/lib/core.js:196 frontend/src/metabase/lib/core.js:201
+#: frontend/src/metabase/lib/core.js:206 frontend/src/metabase/lib/core.js:211
+#: frontend/src/metabase/lib/core.js:216 frontend/src/metabase/lib/core.js:221
+#: frontend/src/metabase/lib/core.js:226
+msgid "Common"
+msgstr "Común"
+
+#: frontend/src/metabase/lib/core.js:30
+#: frontend/src/metabase/meta/Dashboard.js:82
+#: frontend/src/metabase/qb/components/actions/PivotByCategoryAction.jsx:9
+msgid "Category"
+msgstr "Categoría"
+
+#: frontend/src/metabase/lib/core.js:35
+#: frontend/src/metabase/meta/Dashboard.js:62
+msgid "City"
+msgstr "Ciudad"
+
+#: frontend/src/metabase/lib/core.js:40
+#: frontend/src/metabase/meta/Dashboard.js:74
+msgid "Country"
+msgstr "País"
+
+#: frontend/src/metabase/lib/core.js:55
+msgid "Enum"
+msgstr "Enum"
+
+#: frontend/src/metabase/lib/core.js:60
+msgid "Image URL"
+msgstr "URL Imagen"
+
+#: frontend/src/metabase/lib/core.js:65
+msgid "Field containing JSON"
+msgstr "Campo que contiene JSON"
+
+#: frontend/src/metabase/lib/core.js:70
+msgid "Latitude"
+msgstr "Latitud"
+
+#: frontend/src/metabase/lib/core.js:75
+msgid "Longitude"
+msgstr "Longitud"
+
+#: frontend/src/metabase/lib/core.js:80
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:146
+#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:22
+msgid "Number"
+msgstr "Número"
+
+#: frontend/src/metabase/lib/core.js:85
+#: frontend/src/metabase/meta/Dashboard.js:66
+msgid "State"
+msgstr "Provincia"
+
+#: frontend/src/metabase/lib/core.js:90
+msgid "UNIX Timestamp (Seconds)"
+msgstr "Marca de tiempo UNIX (Segundos)"
+
+#: frontend/src/metabase/lib/core.js:95
+msgid "UNIX Timestamp (Milliseconds)"
+msgstr "Marca de tiempo UNIX (Milisegundos)"
+
+#: frontend/src/metabase/lib/core.js:105
+msgid "Zip Code"
+msgstr "Código Postal"
+
+#: frontend/src/metabase/lib/core.js:110
+msgid "Quantity"
+msgstr "Cantidad"
+
+#: frontend/src/metabase/lib/core.js:115
+msgid "Income"
+msgstr "Ingreso"
+
+#: frontend/src/metabase/lib/core.js:120
+msgid "Discount"
+msgstr "Descuento"
+
+#: frontend/src/metabase/lib/core.js:125
+msgid "Creation timestamp"
+msgstr "FechaHora de Creación"
+
+#: frontend/src/metabase/lib/core.js:130
+msgid "Creation time"
+msgstr "Hora de Creación"
+
+#: frontend/src/metabase/lib/core.js:135
+msgid "Creation date"
+msgstr "Fecha de Creación"
+
+#: frontend/src/metabase/lib/core.js:140
+msgid "Product"
+msgstr "Producto"
+
+#: frontend/src/metabase/lib/core.js:145
+msgid "User"
+msgstr "Usuario"
+
+#: frontend/src/metabase/lib/core.js:150
+msgid "Source"
+msgstr "Fuente"
+
+#: frontend/src/metabase/lib/core.js:155
+msgid "Price"
+msgstr "Precio"
+
+#: frontend/src/metabase/lib/core.js:160
+msgid "Join timestamp"
+msgstr "Unir por Tiempo"
+
+#: frontend/src/metabase/lib/core.js:165
+msgid "Join time"
+msgstr "Unir por Tiempo"
+
+#: frontend/src/metabase/lib/core.js:170
+msgid "Join date"
+msgstr "Unir por Fecha"
+
+#: frontend/src/metabase/lib/core.js:175
+msgid "Share"
+msgstr "Compartir"
+
+#: frontend/src/metabase/lib/core.js:180
+msgid "Owner"
+msgstr "Propietario"
+
+#: frontend/src/metabase/lib/core.js:185
+msgid "Company"
+msgstr "Empresa"
+
+#: frontend/src/metabase/lib/core.js:190
+msgid "Subscription"
+msgstr "Suscripción"
+
+#: frontend/src/metabase/lib/core.js:195
+msgid "Score"
+msgstr "Puntuación"
+
+#: frontend/src/metabase/lib/core.js:205
+#: frontend/src/metabase/public/components/widgets/DisplayOptionsPane.jsx:49
+#: frontend/src/metabase/visualizations/lib/settings.js:156
+msgid "Title"
+msgstr "Título"
+
+#: frontend/src/metabase/lib/core.js:210
+msgid "Comment"
+msgstr "Comentario"
+
+#: frontend/src/metabase/lib/core.js:215
+msgid "Cost"
+msgstr "Coste"
+
+#: frontend/src/metabase/lib/core.js:220
+msgid "Gross margin"
+msgstr "Margen Bruto"
+
+#: frontend/src/metabase/lib/core.js:225
+msgid "Birthday"
+msgstr "Cumpleaños"
+
+#: frontend/src/metabase/lib/core.js:236
+msgid "Search box"
+msgstr "Caja de Búsqueda"
+
+#: frontend/src/metabase/lib/core.js:237
+msgid "A list of all values"
+msgstr "Una lista con todos los valores"
+
+#: frontend/src/metabase/lib/core.js:238
+msgid "Plain input box"
+msgstr "Caja de Texto"
+
+#: frontend/src/metabase/lib/core.js:244
+msgid "Everywhere"
+msgstr "En todas partes"
+
+#: frontend/src/metabase/lib/core.js:245
+msgid "The default setting. This field will be displayed normally in tables and charts."
+msgstr "La configuración predeterminada. Este campo se mostrará normalmente en tablas y gráficos."
+
+#: frontend/src/metabase/lib/core.js:249
+msgid "Only in Detail Views"
+msgstr "Solo en vistas detalladas"
+
+#: frontend/src/metabase/lib/core.js:250
+msgid "This field will only be displayed when viewing the details of a single record. Use this for information that's lengthy or that isn't useful in a table or chart."
+msgstr "Este campo solo se mostrará cuando veas los detalles de un solo registro. Úsalo para obtener información larga o que no sea útil en una tabla o gráfico."
+
+#: frontend/src/metabase/lib/core.js:254
+msgid "Do Not Include"
+msgstr "No Incluir"
+
+#: frontend/src/metabase/lib/core.js:255
+msgid "Metabase will never retrieve this field. Use this for sensitive or irrelevant information."
+msgstr "Metabase nunca recuperará este campo. Úsalo para información sensible o irrelevante."
+
+#: frontend/src/metabase/lib/expressions/config.js:7
+#: frontend/src/metabase/lib/query.js:615
+#: frontend/src/metabase/visualizations/lib/utils.js:113
+msgid "Count"
+msgstr "Contar"
+
+#: frontend/src/metabase/lib/expressions/config.js:8
+msgid "CumulativeCount"
+msgstr "RecuentoAcumulativo"
+
+#: frontend/src/metabase/lib/expressions/config.js:9
+#: frontend/src/metabase/qb/components/drill/SummarizeColumnDrill.js:17
+#: frontend/src/metabase/visualizations/lib/utils.js:114
+msgid "Sum"
+msgstr "Suma"
+
+#: frontend/src/metabase/lib/expressions/config.js:10
+msgid "CumulativeSum"
+msgstr "Suma acumulativa"
+
+#: frontend/src/metabase/lib/expressions/config.js:11
+#: frontend/src/metabase/visualizations/lib/utils.js:115
+msgid "Distinct"
+msgstr "Distinto"
+
+#: frontend/src/metabase/lib/expressions/config.js:12
+msgid "StandardDeviation"
+msgstr "Desviación estándar"
+
+#: frontend/src/metabase/lib/expressions/config.js:13
+#: frontend/src/metabase/visualizations/lib/utils.js:112
+msgid "Average"
+msgstr "Media"
+
+#: frontend/src/metabase/lib/expressions/config.js:14
+#: frontend/src/metabase/qb/components/drill/SummarizeColumnDrill.js:25
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:363
+msgid "Min"
+msgstr "Mín"
+
+#: frontend/src/metabase/lib/expressions/config.js:15
+#: frontend/src/metabase/qb/components/drill/SummarizeColumnDrill.js:29
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:371
+msgid "Max"
+msgstr "Máx"
+
+#: frontend/src/metabase/lib/expressions/parser.js:384
+msgid "sad sad panda, lexing errors detected"
+msgstr "triste panda triste, errores léxicos detectados"
+
+#: frontend/src/metabase/lib/formatting.js:447
+msgid "{0} second"
+msgid_plural "{0} seconds"
+msgstr[0] "{0} segundo"
+msgstr[1] "{0} segundos"
+
+#: frontend/src/metabase/lib/formatting.js:450
+msgid "{0} minute"
+msgid_plural "{0} minutes"
+msgstr[0] "{0} minuto"
+msgstr[1] "{0} minutos"
+
+#: frontend/src/metabase/lib/greeting.js:4
+msgid "Hey there"
+msgstr "Hola"
+
+#: frontend/src/metabase/lib/greeting.js:5
+#: frontend/src/metabase/lib/greeting.js:29
+msgid "How's it going"
+msgstr "¿Cómo te va"
+
+#: frontend/src/metabase/lib/greeting.js:6
+msgid "Howdy"
+msgstr "Hola"
+
+#: frontend/src/metabase/lib/greeting.js:7
+msgid "Greetings"
+msgstr "Saludos"
+
+#: frontend/src/metabase/lib/greeting.js:8
+msgid "Good to see you"
+msgstr "Que bueno verte"
+
+#: frontend/src/metabase/lib/greeting.js:12
+msgid "What do you want to know?"
+msgstr "¿Que quieres saber?"
+
+#: frontend/src/metabase/lib/greeting.js:13
+msgid "What's on your mind?"
+msgstr "¿Qué tienes en mente?"
+
+#: frontend/src/metabase/lib/greeting.js:14
+msgid "What do you want to find out?"
+msgstr "¿Qué quieres saber?"
+
+#: frontend/src/metabase/lib/query.js:613
+#: frontend/src/metabase/lib/schema_metadata.js:441
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:246
+msgid "Raw data"
+msgstr "Datos brutos"
+
+#: frontend/src/metabase/lib/query.js:617
+msgid "Cumulative count"
+msgstr "Recuento acumulativo"
+
+#: frontend/src/metabase/lib/query.js:620
+msgid "Average of "
+msgstr "Media de"
+
+#: frontend/src/metabase/lib/query.js:625
+msgid "Distinct values of "
+msgstr "Valores distintos de"
+
+#: frontend/src/metabase/lib/query.js:630
+msgid "Standard deviation of "
+msgstr "Desviación estándar de"
+
+#: frontend/src/metabase/lib/query.js:635
+msgid "Sum of "
+msgstr "Suma de"
+
+#: frontend/src/metabase/lib/query.js:640
+msgid "Cumulative sum of "
+msgstr "Suma acumulativa de"
+
+#: frontend/src/metabase/lib/query.js:645
+msgid "Maximum of "
+msgstr "Máximo de"
+
+#: frontend/src/metabase/lib/query.js:650
+msgid "Minimum of "
+msgstr "Mínimo de"
+
+#: frontend/src/metabase/lib/query.js:664
+msgid "Grouped by "
+msgstr "Agrupado por"
+
+#: frontend/src/metabase/lib/query.js:678
+msgid "Filtered by "
+msgstr "Filtrado por"
+
+#: frontend/src/metabase/lib/query.js:706
+msgid "Sorted by "
+msgstr "Ordenado por"
+
+#: frontend/src/metabase/lib/schema_metadata.js:211
+msgid "True"
+msgstr "Verdadero"
+
+#: frontend/src/metabase/lib/schema_metadata.js:211
+msgid "False"
+msgstr "Falso"
+
+#: frontend/src/metabase/lib/schema_metadata.js:295
+msgid "Select longitude field"
+msgstr "Selecciona el campo de longitud"
+
+#: frontend/src/metabase/lib/schema_metadata.js:296
+msgid "Enter upper latitude"
+msgstr "Latitud Superior"
+
+#: frontend/src/metabase/lib/schema_metadata.js:297
+msgid "Enter left longitude"
+msgstr "Longitud Izquierda"
+
+#: frontend/src/metabase/lib/schema_metadata.js:298
+msgid "Enter lower latitude"
+msgstr "Latitud Inferior"
+
+#: frontend/src/metabase/lib/schema_metadata.js:299
+msgid "Enter right longitude"
+msgstr "Longitud Derecha"
+
+#: frontend/src/metabase/lib/schema_metadata.js:335
+#: frontend/src/metabase/lib/schema_metadata.js:355
+#: frontend/src/metabase/lib/schema_metadata.js:365
+#: frontend/src/metabase/lib/schema_metadata.js:371
+#: frontend/src/metabase/lib/schema_metadata.js:379
+#: frontend/src/metabase/lib/schema_metadata.js:385
+#: frontend/src/metabase/lib/schema_metadata.js:390
+msgid "Is"
+msgstr "Es"
+
+#: frontend/src/metabase/lib/schema_metadata.js:336
+#: frontend/src/metabase/lib/schema_metadata.js:356
+#: frontend/src/metabase/lib/schema_metadata.js:366
+#: frontend/src/metabase/lib/schema_metadata.js:380
+#: frontend/src/metabase/lib/schema_metadata.js:386
+msgid "Is not"
+msgstr "No es"
+
+#: frontend/src/metabase/lib/schema_metadata.js:337
+#: frontend/src/metabase/lib/schema_metadata.js:351
+#: frontend/src/metabase/lib/schema_metadata.js:359
+#: frontend/src/metabase/lib/schema_metadata.js:367
+#: frontend/src/metabase/lib/schema_metadata.js:375
+#: frontend/src/metabase/lib/schema_metadata.js:381
+#: frontend/src/metabase/lib/schema_metadata.js:391
+msgid "Is empty"
+msgstr "Vacío"
+
+#: frontend/src/metabase/lib/schema_metadata.js:338
+#: frontend/src/metabase/lib/schema_metadata.js:352
+#: frontend/src/metabase/lib/schema_metadata.js:360
+#: frontend/src/metabase/lib/schema_metadata.js:368
+#: frontend/src/metabase/lib/schema_metadata.js:376
+#: frontend/src/metabase/lib/schema_metadata.js:382
+#: frontend/src/metabase/lib/schema_metadata.js:392
+msgid "Not empty"
+msgstr "No Vacío"
+
+#: frontend/src/metabase/lib/schema_metadata.js:344
+msgid "Equal to"
+msgstr "Igual a"
+
+#: frontend/src/metabase/lib/schema_metadata.js:345
+msgid "Not equal to"
+msgstr "Distinto a"
+
+#: frontend/src/metabase/lib/schema_metadata.js:346
+msgid "Greater than"
+msgstr "Mayor que"
+
+#: frontend/src/metabase/lib/schema_metadata.js:347
+msgid "Less than"
+msgstr "Menor que"
+
+#: frontend/src/metabase/lib/schema_metadata.js:348
+#: frontend/src/metabase/lib/schema_metadata.js:374
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:289
+#: frontend/src/metabase/query_builder/components/filters/pickers/TimePicker.jsx:96
+msgid "Between"
+msgstr "Entre"
+
+#: frontend/src/metabase/lib/schema_metadata.js:349
+msgid "Greater than or equal to"
+msgstr "Mayor o igual a"
+
+#: frontend/src/metabase/lib/schema_metadata.js:350
+msgid "Less than or equal to"
+msgstr "Menos o igual a"
+
+#: frontend/src/metabase/lib/schema_metadata.js:357
+msgid "Contains"
+msgstr "Contiene"
+
+#: frontend/src/metabase/lib/schema_metadata.js:358
+msgid "Does not contain"
+msgstr "No contiene"
+
+#: frontend/src/metabase/lib/schema_metadata.js:361
+msgid "Starts with"
+msgstr "Empieza con"
+
+#: frontend/src/metabase/lib/schema_metadata.js:362
+msgid "Ends with"
+msgstr "Termina en"
+
+#: frontend/src/metabase/lib/schema_metadata.js:372
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:268
+#: frontend/src/metabase/query_builder/components/filters/pickers/TimePicker.jsx:74
+msgid "Before"
+msgstr "Antes"
+
+#: frontend/src/metabase/lib/schema_metadata.js:373
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:275
+#: frontend/src/metabase/query_builder/components/filters/pickers/TimePicker.jsx:85
+msgid "After"
+msgstr "Después"
+
+#: frontend/src/metabase/lib/schema_metadata.js:387
+msgid "Inside"
+msgstr "Dentro"
+
+#: frontend/src/metabase/lib/schema_metadata.js:443
+msgid "Just a table with the rows in the answer, no additional operations."
+msgstr "Solo una tabla con las filas en la respuesta, sin operaciones adicionales."
+
+#: frontend/src/metabase/lib/schema_metadata.js:449
+msgid "Count of rows"
+msgstr "Número de filas"
+
+#: frontend/src/metabase/lib/schema_metadata.js:451
+msgid "Total number of rows in the answer."
+msgstr "Número total de filas en la respuesta."
+
+#: frontend/src/metabase/lib/schema_metadata.js:457
+msgid "Sum of ..."
+msgstr "Suma de ..."
+
+#: frontend/src/metabase/lib/schema_metadata.js:459
+msgid "Sum of all the values of a column."
+msgstr "Suma de todos los valores de una columna."
+
+#: frontend/src/metabase/lib/schema_metadata.js:465
+msgid "Average of ..."
+msgstr "Media de ..."
+
+#: frontend/src/metabase/lib/schema_metadata.js:467
+msgid "Average of all the values of a column"
+msgstr "Promedio de todos los valores de una columna"
+
+#: frontend/src/metabase/lib/schema_metadata.js:473
+msgid "Number of distinct values of ..."
+msgstr "Número de valores distintos de ..."
+
+#: frontend/src/metabase/lib/schema_metadata.js:475
+msgid "Number of unique values of a column among all the rows in the answer."
+msgstr "Número de valores únicos de una columna entre todas las filas en la respuesta."
+
+#: frontend/src/metabase/lib/schema_metadata.js:481
+msgid "Cumulative sum of ..."
+msgstr "Suma acumulada de ..."
+
+#: frontend/src/metabase/lib/schema_metadata.js:483
+msgid "Additive sum of all the values of a column.\\ne.x. total revenue over time."
+msgstr "Suma aditiva de todos los valores de una columna.\n"
+" e.g. los ingresos totales a lo largo del tiempo"
+
+#: frontend/src/metabase/lib/schema_metadata.js:489
+msgid "Cumulative count of rows"
+msgstr "Recuento acumulado de filas"
+
+#: frontend/src/metabase/lib/schema_metadata.js:491
+msgid "Additive count of the number of rows.\\ne.x. total number of sales over time."
+msgstr "Recuento aditiva del número de filas.\n"
+" e.g. número total de ventas en el tiempo"
+
+#: frontend/src/metabase/lib/schema_metadata.js:497
+msgid "Standard deviation of ..."
+msgstr "Desviación estándar de ..."
+
+#: frontend/src/metabase/lib/schema_metadata.js:499
+msgid "Number which expresses how much the values of a column vary among all rows in the answer."
+msgstr "Número que expresa cuánto varían los valores de una columna entre todas las filas en la respuesta."
+
+#: frontend/src/metabase/lib/schema_metadata.js:505
+msgid "Minimum of ..."
+msgstr "Mínimo de ..."
+
+#: frontend/src/metabase/lib/schema_metadata.js:507
+msgid "Minimum value of a column"
+msgstr "Valor mínimo de una columna"
+
+#: frontend/src/metabase/lib/schema_metadata.js:513
+msgid "Maximum of ..."
+msgstr "Máximo de ..."
+
+#: frontend/src/metabase/lib/schema_metadata.js:515
+msgid "Maximum value of a column"
+msgstr "Valor máximo de una columna"
+
+#: frontend/src/metabase/lib/schema_metadata.js:523
+msgid "Break out by dimension"
+msgstr "Desglosar por dimensión"
+
+#: frontend/src/metabase/lib/settings.js:93
+msgid "lower case letter"
+msgstr "letra en minúsculas"
+
+#: frontend/src/metabase/lib/settings.js:95
+msgid "upper case letter"
+msgstr "letra en mayúsculas"
+
+#: frontend/src/metabase/lib/settings.js:97
+#: src/metabase/automagic_dashboards/core.clj
+msgid "number"
+msgstr "número"
+
+#: frontend/src/metabase/lib/settings.js:99
+msgid "special character"
+msgstr "carácter especial"
+
+#: frontend/src/metabase/lib/settings.js:105
+msgid "must be"
+msgstr "debe ser"
+
+#: frontend/src/metabase/lib/settings.js:105
+#: frontend/src/metabase/lib/settings.js:106
+msgid "characters long"
+msgstr "caracteres"
+
+#: frontend/src/metabase/lib/settings.js:106
+msgid "Must be"
+msgstr "Debe ser"
+
+#: frontend/src/metabase/lib/settings.js:122
+msgid "and include"
+msgstr "e incluir"
+
+#: frontend/src/metabase/lib/utils.js:92
+msgid "zero"
+msgstr "cero"
+
+#: frontend/src/metabase/lib/utils.js:93
+msgid "one"
+msgstr "uno"
+
+#: frontend/src/metabase/lib/utils.js:94
+msgid "two"
+msgstr "dos"
+
+#: frontend/src/metabase/lib/utils.js:95
+msgid "three"
+msgstr "tres"
+
+#: frontend/src/metabase/lib/utils.js:96
+msgid "four"
+msgstr "cuatro"
+
+#: frontend/src/metabase/lib/utils.js:97
+msgid "five"
+msgstr "cinco"
+
+#: frontend/src/metabase/lib/utils.js:98
+msgid "six"
+msgstr "seis"
+
+#: frontend/src/metabase/lib/utils.js:99
+msgid "seven"
+msgstr "siete"
+
+#: frontend/src/metabase/lib/utils.js:100
+msgid "eight"
+msgstr "ocho"
+
+#: frontend/src/metabase/lib/utils.js:101
+msgid "nine"
+msgstr "nueve"
+
+#: frontend/src/metabase/meta/Dashboard.js:31
+msgid "Month and Year"
+msgstr "Mes y Año"
+
+#: frontend/src/metabase/meta/Dashboard.js:32
+msgid "Like January, 2016"
+msgstr "Como Enero 2016"
+
+#: frontend/src/metabase/meta/Dashboard.js:36
+msgid "Quarter and Year"
+msgstr "Trimestre y Año"
+
+#: frontend/src/metabase/meta/Dashboard.js:37
+msgid "Like Q1, 2016"
+msgstr "Como T1, 2016"
+
+#: frontend/src/metabase/meta/Dashboard.js:41
+msgid "Single Date"
+msgstr "Fecha Unica"
+
+#: frontend/src/metabase/meta/Dashboard.js:42
+msgid "Like January 31, 2016"
+msgstr "Como 31 de Enero, 2016"
+
+#: frontend/src/metabase/meta/Dashboard.js:46
+msgid "Date Range"
+msgstr "Rango de Fechas"
+
+#: frontend/src/metabase/meta/Dashboard.js:47
+msgid "Like December 25, 2015 - February 14, 2016"
+msgstr "Como 25 de Diciembre, 2015 - 14 de Febrero, 2016"
+
+#: frontend/src/metabase/meta/Dashboard.js:51
+msgid "Relative Date"
+msgstr "Fecha Relativa"
+
+#: frontend/src/metabase/meta/Dashboard.js:52
+msgid "Like \"the last 7 days\" or \"this month\""
+msgstr "Como \"los últimos 7 días\" o \"este mes\""
+
+#: frontend/src/metabase/meta/Dashboard.js:56
+msgid "Date Filter"
+msgstr "Filtro de Fecha"
+
+#: frontend/src/metabase/meta/Dashboard.js:57
+msgid "All Options"
+msgstr "Todas las Opciones"
+
+#: frontend/src/metabase/meta/Dashboard.js:58
+msgid "Contains all of the above"
+msgstr "Contiene todo lo anterior"
+
+#: frontend/src/metabase/meta/Dashboard.js:70
+msgid "ZIP or Postal Code"
+msgstr "Código Postal"
+
+#: frontend/src/metabase/meta/Dashboard.js:78
+#: frontend/src/metabase/meta/Dashboard.js:108
+msgid "ID"
+msgstr "ID"
+
+#: frontend/src/metabase/meta/Dashboard.js:96
+#: frontend/src/metabase/qb/components/actions/PivotByTimeAction.jsx:8
+msgid "Time"
+msgstr "Tiempo"
+
+#: frontend/src/metabase/meta/Dashboard.js:97
+msgid "Date range, relative date, time of day, etc."
+msgstr "Rango de fechas, fecha relativa, hora del día, etc."
+
+#: frontend/src/metabase/meta/Dashboard.js:102
+#: frontend/src/metabase/qb/components/actions/PivotByLocationAction.jsx:8
+msgid "Location"
+msgstr "Ubicación"
+
+#: frontend/src/metabase/meta/Dashboard.js:103
+msgid "City, State, Country, ZIP code."
+msgstr "Ciudad, Provincia, País, Código Postal."
+
+#: frontend/src/metabase/meta/Dashboard.js:109
+msgid "User ID, product ID, event ID, etc."
+msgstr "ID Usuario, ID Productio, ID Evento, etc."
+
+#: frontend/src/metabase/meta/Dashboard.js:114
+msgid "Other Categories"
+msgstr "Otras Categorías"
+
+#: frontend/src/metabase/meta/Dashboard.js:115
+msgid "Category, Type, Model, Rating, etc."
+msgstr "Categoría, Tipo, Modelo, Clasificación, etc."
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:43
+#: frontend/src/metabase/user/components/UserSettings.jsx:54
+msgid "Account settings"
+msgstr "Configuración de la cuenta"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:50
+msgid "Exit admin"
+msgstr "Salir de Configuración"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:60
+msgid "Logs"
+msgstr "Logs"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:67
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorSidebar.jsx:105
+msgid "Help"
+msgstr "Ayuda"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:76
+msgid "About Metabase"
+msgstr "Sobre Metabase"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:82
+msgid "Sign out"
+msgstr "Salir"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:107
+msgid "Thanks for using"
+msgstr "Gracias por utilizar"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:111
+msgid "You're on version"
+msgstr "Estás en la versión"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:114
+msgid "Built on"
+msgstr "Construida el"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:133
+msgid "is a Trademark of"
+msgstr "es una marca registrada de"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:135
+msgid "and is built with care in San Francisco, CA"
+msgstr "y está construido con cariño en San Francisco, CA"
+
+#: frontend/src/metabase/nav/containers/Navbar.jsx:195
+msgid "Metabase Admin"
+msgstr "Configuración Metabase"
+
+#: frontend/src/metabase/nav/containers/Navbar.jsx:296
+#: frontend/src/metabase/reference/databases/TableQuestions.jsx:36
+#: frontend/src/metabase/reference/metrics/MetricQuestions.jsx:37
+#: frontend/src/metabase/reference/segments/SegmentQuestions.jsx:37
+msgid "Ask a question"
+msgstr "Haz una pregunta"
+
+#: frontend/src/metabase/nav/containers/Navbar.jsx:305
+msgid "New dashboard"
+msgstr "Añadir nuevo Cuadro de Mando"
+
+#: frontend/src/metabase/nav/containers/Navbar.jsx:311
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:127
+msgid "New pulse"
+msgstr "Nuevo pulso"
+
+#: frontend/src/metabase/nav/containers/Navbar.jsx:319
+msgid "Reference"
+msgstr "Referencia"
+
+#: frontend/src/metabase/new_query/containers/MetricSearch.jsx:83
+msgid "Which metric?"
+msgstr "¿Qué Métrica?"
+
+#: frontend/src/metabase/new_query/containers/MetricSearch.jsx:110
+#: frontend/src/metabase/reference/metrics/MetricList.jsx:24
+msgid "Defining common metrics for your team makes it even easier to ask questions"
+msgstr "La definición de métricas comunes para tu equipo hace que sea aún más fácil hacer preguntas"
+
+#: frontend/src/metabase/new_query/containers/MetricSearch.jsx:113
+msgid "How to create metrics"
+msgstr "Cómo crear métricas"
+
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:138
+msgid "See data over time, as a map, or pivoted to help you understand trends or changes."
+msgstr "Consulta los datos a lo largo del tiempo, como un mapa, o gírelos para ayudarte a comprender las tendencias o los cambios."
+
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:149
+msgid "Custom"
+msgstr "Personalizado"
+
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:150
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:517
+msgid "New question"
+msgstr "Nueva pregunta"
+
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:152
+msgid "Use the simple question builder to see trends, lists of things, or to create your own metrics."
+msgstr "Utiliza el generador de preguntas para ver tendencias, listas de cosas o para crear tus propias métricas."
+
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:161
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Native query"
+msgstr "Consulta nativa"
+
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:162
+msgid "For more complicated questions, you can write your own SQL or native query."
+msgstr "Para preguntas más complicadas, puedes escribir tu propia consulta SQL o nativa."
+
+#: frontend/src/metabase/parameters/components/ParameterValueWidget.jsx:240
+msgid "Select a default value…"
+msgstr "Selecciona un valor por defecto…"
+
+#: frontend/src/metabase/parameters/components/widgets/DateAllOptionsWidget.jsx:149
+#: frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx:390
+msgid "Update filter"
+msgstr "Actualizar filtro"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:9
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:144
+msgid "Today"
+msgstr "Hoy"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:14
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:148
+msgid "Yesterday"
+msgstr "Ayer"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:18
+msgid "Past 7 days"
+msgstr "Ultimos 7 días"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:19
+msgid "Past 30 days"
+msgstr "Ultimos 30 días"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:24
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:29
+#: src/metabase/api/table.clj
+msgid "Week"
+msgstr "Semana"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:25
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:30
+#: src/metabase/api/table.clj
+msgid "Month"
+msgstr "Mes"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:26
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:31
+#: src/metabase/api/table.clj
+msgid "Year"
+msgstr "Año"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:152
+msgid "Past 7 Days"
+msgstr "Ultimos 7 Días"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:156
+msgid "Past 30 Days"
+msgstr "Ultimos 30 Días"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:160
+msgid "Last Week"
+msgstr "Semana Pasada"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:164
+msgid "Last Month"
+msgstr "Mes Pasado"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:168
+msgid "Last Year"
+msgstr "Año Pasado"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:172
+msgid "This Week"
+msgstr "Esta Semana"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:176
+msgid "This Month"
+msgstr "Este Mes"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:180
+msgid "This Year"
+msgstr "Este Año"
+
+#: frontend/src/metabase/parameters/components/widgets/ParameterFieldWidget.jsx:88
+#: frontend/src/metabase/parameters/components/widgets/TextWidget.jsx:54
+msgid "Enter a value..."
+msgstr "Introduce un valor..."
+
+#: frontend/src/metabase/parameters/components/widgets/TextWidget.jsx:90
+msgid "Enter a default value..."
+msgstr "Introduce un valor predeterminado..."
+
+#: frontend/src/metabase/public/components/PublicError.jsx:18
+msgid "An error occurred"
+msgstr "Ha ocurrido un error"
+
+#: frontend/src/metabase/public/components/PublicNotFound.jsx:11
+msgid "Not found"
+msgstr "No encontrado"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:82
+msgid "You’ve made changes that need to be published before they will be reflected in your application embed."
+msgstr "Ha realizado cambios que deben publicarse antes de que se vean reflejados en la inserción de la aplicación."
+
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:83
+msgid "You will need to publish this {0} before you can embed it in another application."
+msgstr "Tendrás que publicar este {0} antes de poder incrustarlo en otra aplicación."
+
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:92
+msgid "Discard Changes"
+msgstr "Descartar Cambios"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:99
+msgid "Updating..."
+msgstr "Actualizando..."
+
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:100
+msgid "Updated"
+msgstr "Actualizado"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:101
+msgid "Failed!"
+msgstr "¡Ha fallado!"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:102
+msgid "Publish"
+msgstr "Publicar"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:111
+msgid "Code"
+msgstr "Código"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:70
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:157
+msgid "Style"
+msgstr "Estilo"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:80
+msgid "Which parameters can users of this embed use?"
+msgstr "¿Qué parámetros pueden usar los usuarios de este embebido?"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:83
+msgid "This {0} doesn't have any parameters to configure yet."
+msgstr "Este {0} aún no tiene parámetros para configurar."
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:104
+msgid "Editable"
+msgstr "Editable"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:105
+msgid "Locked"
+msgstr "Bloqueado"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:113
+msgid "Preview Locked Parameters"
+msgstr "Vista previa de los parámetros bloqueados"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:115
+msgid "Try passing some values to your locked parameters here. Your server will have to provide the actual values in the signed token when using this for real."
+msgstr "Intenta pasar algunos valores a tus parámetros bloqueados aquí. Tu servidor tendrá que proporcionar los valores reales en el token firmado al usar esto de manera real."
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:126
+msgid "Danger zone"
+msgstr "Zona peligrosa"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:127
+msgid "This will disable embedding for this {0}."
+msgstr "Esto deshabilitará la incrustación para este {0}."
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:128
+msgid "Unpublish"
+msgstr "No publicar"
+
+#: frontend/src/metabase/public/components/widgets/DisplayOptionsPane.jsx:17
+msgid "Light"
+msgstr "Claro"
+
+#: frontend/src/metabase/public/components/widgets/DisplayOptionsPane.jsx:18
+msgid "Dark"
+msgstr "Oscuro"
+
+#: frontend/src/metabase/public/components/widgets/DisplayOptionsPane.jsx:37
+msgid "Border"
+msgstr "Borde"
+
+#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:62
+msgid "To embed this {0} in your application:"
+msgstr "Para insertar este {0} en tu aplicación"
+
+#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:64
+msgid "Insert this code snippet in your server code to generate the signed embedding URL "
+msgstr "Inserta este fragmento de código en tu código de servidor para generar la URL de inserción firmada"
+
+#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:87
+msgid "Then insert this code snippet in your HTML template or single page app."
+msgstr "A continuación, inserta este fragmento de código en tu plantilla HTML o aplicación de una sola página."
+
+#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:94
+msgid "Embed code snippet for your HTML or Frontend Application"
+msgstr "Incrusta este fragmento de código en tu aplicación"
+
+#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:101
+msgid "More {0}"
+msgstr "Más {0}"
+
+#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:103
+msgid "examples on GitHub"
+msgstr "ejemplos en GitHub"
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:72
+msgid "Enable sharing"
+msgstr "Habilitar compartir"
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:76
+msgid "Disable this public link?"
+msgstr "¿Deshabilitar este enlace público?"
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:77
+msgid "This will cause the existing link to stop working. You can re-enable it, but when you do it will be a different link."
+msgstr "Esto hará que el enlace existente deje de funcionar. Puedes volver a habilitarlo, pero cuando lo hagas será un enlace diferente."
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:117
+msgid "Public link"
+msgstr "Enlace público"
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:118
+msgid "Share this {0} with people who don't have a Metabase account using the URL below:"
+msgstr "Comparte este {0} con personas que no tienen una cuenta de Metabase usando la siguiente URL:"
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:158
+msgid "Public embed"
+msgstr "Incrustación pública"
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:159
+msgid "Embed this {0} in blog posts or web pages by copying and pasting this snippet:"
+msgstr "Incrusta este {0} en publicaciones de blogs o páginas web al copiar y pegar este fragmento:"
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:176
+msgid "Embed this {0} in an application"
+msgstr "Incrustar este {0} en una aplicación"
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:177
+msgid "By integrating with your application server code, you can provide a secure stats {0} limited to a specific user, customer, organization, etc."
+msgstr "Al integrar con tu servidor de aplicaciones, puedes proporcionar un/a {0} segura limitada a un usuario específico, cliente, organización, etc."
+
+#: frontend/src/metabase/pulse/components/PulseCardPreview.jsx:94
+msgid "Remove attachment"
+msgstr "Eliminar adjunto"
+
+#: frontend/src/metabase/pulse/components/PulseCardPreview.jsx:95
+msgid "Attach file with results"
+msgstr "Adjuntar archivo con resultados"
+
+#: frontend/src/metabase/pulse/components/PulseCardPreview.jsx:127
+msgid "This question will be added as a file attachment"
+msgstr "Esta pregunta se añadirá como un archivo adjunto"
+
+#: frontend/src/metabase/pulse/components/PulseCardPreview.jsx:128
+msgid "This question won't be included in your Pulse"
+msgstr "Esta pregunta no estará incluida en tu Pulso"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:92
+msgid "This pulse will no longer be emailed to {0} {1}"
+msgstr "Este pulso ya no se enviará por correo electrónico a la dirección {1}"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:94
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:374
+msgid "{0} address"
+msgid_plural "{0} addresses"
+msgstr[0] "{0} dirección"
+msgstr[1] "{0} direcciones"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:102
+msgid "Slack channel {0} will no longer get this pulse {1}"
+msgstr "El canal Slack {0} ya no recibirá este pulso {1}"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:110
+msgid "Channel {0} will no longer receive this pulse {1}"
+msgstr "El canal {0} ya no recibirá este pulso {1}"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:127
+msgid "Edit pulse"
+msgstr "Edita un pulso"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:131
+msgid "What's a Pulse?"
+msgstr "¿Qué es un Pulso?"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:141
+msgid "Got it"
+msgstr "Lo tengo"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:157
+msgid "Where should this data go?"
+msgstr "¿A dónde deberían ir estos datos?"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:173
+msgid "Unarchiving…"
+msgstr "Desarchivando…"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:174
+msgid "Unarchive failed"
+msgstr "Ha fallado la recuperación"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:175
+msgid "Unarchived"
+msgstr "Desarchivado"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:182
+msgid "Create pulse"
+msgstr "Crea un pulso"
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:90
+msgid "Attachment"
+msgstr "Adjunto"
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:104
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:111
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:671
+msgid "Heads up"
+msgstr "Aviso"
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:105
+msgid "We'll show the first 10 columns and 20 rows of this table in your Pulse. If you email this, we'll add a file attachment with all columns and up to 2,000 rows."
+msgstr "Mostraremos las primeras 10 columnas y 20 filas de esta tabla en tu Pulso. Si lo envías por correo electrónico, añadiremos un archivo adjunto con todas las columnas y hasta 2.000 filas."
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:112
+msgid "Raw data questions can only be included as email attachments"
+msgstr "Las preguntas de datos brutos solo se pueden incluir como archivos adjuntos de correo electrónico"
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:119
+msgid "Looks like this pulse is getting big"
+msgstr "Parece que este pulso está creciendo mucho"
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:120
+msgid "We recommend keeping pulses small and focused to help keep them digestible and useful to the whole team."
+msgstr "Recomendamos mantener los pulsos pequeños y enfocados para ayudar a mantenerlos digeribles y útiles para todo el equipo."
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:160
+msgid "Pick your data"
+msgstr "Elige tus datos"
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:162
+msgid "Choose questions you'd like to send in this pulse"
+msgstr "Elige las preguntas que te gustaría enviar en este pulso"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:29
+msgid "Emails"
+msgstr "Emails"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:30
+msgid "Slack messages"
+msgstr "Mensajes Slack"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:218
+msgid "Sent"
+msgstr "Enviado"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:219
+msgid "{0} will be sent at"
+msgstr "{0} se enviará a las"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:221
+msgid "Messages"
+msgstr "Mensajes"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:232
+msgid "Send email now"
+msgstr "Enviar email ahora"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:233
+msgid "Send to {0} now"
+msgstr "Enviar a {0} ahora"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:235
+msgid "Sending…"
+msgstr "Enviando…"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:236
+msgid "Sending failed"
+msgstr "Ha fallado el envío"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:239
+msgid "Didn’t send because the pulse has no results."
+msgstr "No se envió porque el pulso no tiene resultados."
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:240
+msgid "Pulse sent"
+msgstr "Pulso enviado"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:279
+msgid "{0} needs to be set up by an administrator."
+msgstr "{0} debe ser configurado por un administrador."
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:294
+msgid "Slack"
+msgstr "Slack"
+
+#: frontend/src/metabase/pulse/components/PulseEditCollection.jsx:12
+msgid "Which collection should this pulse live in?"
+msgstr "¿En qué colección debería estar este pulso?"
+
+#: frontend/src/metabase/pulse/components/PulseEditName.jsx:35
+msgid "Name your pulse"
+msgstr "Nombra tu pulso"
+
+#: frontend/src/metabase/pulse/components/PulseEditName.jsx:37
+msgid "Give your pulse a name to help others understand what it's about"
+msgstr "Dale un nombre a tu pulso para ayudar a otros a entender de qué se trata"
+
+#: frontend/src/metabase/pulse/components/PulseEditName.jsx:49
+msgid "Important metrics"
+msgstr "Métricas importantes"
+
+#: frontend/src/metabase/pulse/components/PulseEditSkip.jsx:22
+msgid "Skip if no results"
+msgstr "Omitir si no hay resultados"
+
+#: frontend/src/metabase/pulse/components/PulseEditSkip.jsx:24
+msgid "Skip a scheduled Pulse if none of its questions have any results"
+msgstr "Omitir un pulso programado si ninguna de sus preguntas tiene resultado"
+
+#: frontend/src/metabase/pulse/components/RecipientPicker.jsx:65
+msgid "Enter email addresses you'd like this data to go to"
+msgstr "Ingresa las direcciones de correo electrónico a las que quieres que vayan estos datos"
+
+#: frontend/src/metabase/pulse/components/WhatsAPulse.jsx:16
+msgid "Help everyone on your team stay in sync with your data."
+msgstr "Ayuda a todos los de tu equipo a mantenerse sincronizados con tus datos."
+
+#: frontend/src/metabase/pulse/components/WhatsAPulse.jsx:30
+msgid "Pulses let you send data from Metabase to email or Slack on the schedule of your choice."
+msgstr "Los pulsos te permiten enviar datos de Metabase a correo electrónico o Slack en el horario que desees."
+
+#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:100
+msgid "After {0}"
+msgstr "Después de {0}"
+
+#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:102
+msgid "Before {0}"
+msgstr "Antes de {0}"
+
+#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:104
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:299
+msgid "Is Empty"
+msgstr "Vacío"
+
+#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:106
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:305
+msgid "Not Empty"
+msgstr "No Vacío"
+
+#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:109
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:216
+msgid "All Time"
+msgstr "Todo el Tiempo"
+
+#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:154
+msgid "Apply"
+msgstr "Aplicar"
+
+#: frontend/src/metabase/qb/components/actions/CommonMetricsAction.jsx:21
+msgid "View {0}"
+msgstr "Ver {0}"
+
+#: frontend/src/metabase/qb/components/actions/CompareWithTable.jsx:29
+msgid "Compare this with all rows in the table"
+msgstr "Compara esto con todas las filas en la tabla"
+
+#: frontend/src/metabase/qb/components/actions/CompoundQueryAction.jsx:14
+msgid "Analyze the results of this Query"
+msgstr "Analiza los resultados de esta consulta"
+
+#: frontend/src/metabase/qb/components/actions/CountByTimeAction.jsx:29
+msgid "Count of rows by time"
+msgstr "Número de filas por tiempo"
+
+#: frontend/src/metabase/qb/components/actions/PivotByAction.jsx:55
+msgid "Break out by {0}"
+msgstr "Distribuir por {0}"
+
+#: frontend/src/metabase/qb/components/actions/SummarizeBySegmentMetricAction.jsx:34
+msgid "Summarize this segment"
+msgstr "Resume este segmento"
+
+#: frontend/src/metabase/qb/components/actions/UnderlyingDataAction.jsx:14
+msgid "View this as a table"
+msgstr "Ver esto como una tabla"
+
+#: frontend/src/metabase/qb/components/actions/UnderlyingRecordsAction.jsx:22
+msgid "View the underlying {0} records"
+msgstr "Ver los registros subyacentes de {0}"
+
+#: frontend/src/metabase/qb/components/actions/XRayCard.jsx:15
+msgid "X-Ray this question"
+msgstr "Aplica rayos-X a esta pregunta"
+
+#: frontend/src/metabase/qb/components/drill/AutomaticDashboardDrill.jsx:32
+msgid "X-ray {0} {1}"
+msgstr "Rayos-X {0} {1}"
+
+#: frontend/src/metabase/qb/components/drill/AutomaticDashboardDrill.jsx:32
+#: frontend/src/metabase/qb/components/drill/CompareToRestDrill.js:32
+msgid "these"
+msgstr "estos"
+
+#: frontend/src/metabase/qb/components/drill/AutomaticDashboardDrill.jsx:32
+#: frontend/src/metabase/qb/components/drill/CompareToRestDrill.js:32
+msgid "this"
+msgstr "este"
+
+#: frontend/src/metabase/qb/components/drill/CompareToRestDrill.js:32
+msgid "Compare {0} {1} to the rest"
+msgstr "Compara {0} {1} con el resto"
+
+#: frontend/src/metabase/qb/components/drill/DistributionDrill.jsx:35
+msgid "Distribution"
+msgstr "Distribución"
+
+#: frontend/src/metabase/qb/components/drill/ObjectDetailDrill.jsx:38
+msgid "View details"
+msgstr "Ver detalles"
+
+#: frontend/src/metabase/qb/components/drill/QuickFilterDrill.jsx:54
+msgid "View this {0}'s {1}"
+msgstr "Ver los {1} de {0}"
+
+#: frontend/src/metabase/qb/components/drill/SortAction.jsx:45
+msgid "Ascending"
+msgstr "Ascendente"
+
+#: frontend/src/metabase/qb/components/drill/SortAction.jsx:57
+msgid "Descending"
+msgstr "Descendente"
+
+#: frontend/src/metabase/qb/components/drill/SummarizeColumnByTimeDrill.js:47
+msgid "over time"
+msgstr "a través del tiempo."
+
+#: frontend/src/metabase/qb/components/drill/SummarizeColumnDrill.js:21
+msgid "Avg"
+msgstr "Media"
+
+#: frontend/src/metabase/qb/components/drill/SummarizeColumnDrill.js:33
+msgid "Distincts"
+msgstr "Distintos"
+
+#: frontend/src/metabase/qb/components/drill/UnderlyingRecordsDrill.jsx:32
+msgid "View this {0}"
+msgid_plural "View these {0}"
+msgstr[0] "Ver el {0}"
+msgstr[1] "Ver estos {0}"
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:220
+#: frontend/src/metabase/qb/components/drill/ZoomDrill.jsx:26
+msgid "Zoom in"
+msgstr "Ampliar"
+
+#: frontend/src/metabase/query_builder/components/AggregationPopover.jsx:19
+msgid "Custom Expression"
+msgstr "Expresión Personalizada"
+
+#: frontend/src/metabase/query_builder/components/AggregationPopover.jsx:20
+msgid "Common Metrics"
+msgstr "Métricas Comunes"
+
+#: frontend/src/metabase/query_builder/components/AggregationPopover.jsx:182
+msgid "Metabasics"
+msgstr "Metabasics"
+
+#: frontend/src/metabase/query_builder/components/AggregationPopover.jsx:290
+msgid "Name (optional)"
+msgstr "Nombre (opcional)"
+
+#: frontend/src/metabase/query_builder/components/AggregationWidget.jsx:153
+msgid "Choose an aggregation"
+msgstr "Elige una agregación"
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:115
+msgid "Set up your own alert"
+msgstr "Configura tu propia alerta"
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:155
+msgid "Unsubscribing..."
+msgstr "Dando de baja..."
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:160
+msgid "Failed to unsubscribe"
+msgstr "Error al darse de baja"
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:216
+msgid "Unsubscribe"
+msgstr "Darse de baja"
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:247
+msgid "No channel"
+msgstr "Sin canal"
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:274
+msgid "Okay, you're unsubscribed"
+msgstr "De acuerdo, has sido dado de baja"
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:346
+msgid "You're receiving {0}'s alerts"
+msgstr "Estás recibiendo las alertas de {0}"
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:347
+msgid "{0} set up an alert"
+msgstr "{0} has configurado una alerta"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:161
+msgid "alerts"
+msgstr "alertas"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:184
+msgid "Let's set up your alert"
+msgstr "Vamos a configurar tu alerta"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:215
+msgid "The wide world of alerts"
+msgstr "El amplio mundo de las alertas"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:216
+msgid "There are a few different kinds of alerts you can get"
+msgstr "Hay algunos tipos diferentes de alertas que puedes obtener"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:229
+msgid "When a raw data question {0}"
+msgstr "Cuando una pregunta de datos {0}"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:230
+msgid "returns any results"
+msgstr "arroja resultados"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:240
+msgid "When a line or bar {0}"
+msgstr "Cuando una línea o barra {0}"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:241
+msgid "crosses a goal line"
+msgstr "supera un objetivo"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:251
+msgid "When a progress bar {0}"
+msgstr "Cuando una barra de progreso {0}"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:252
+msgid "reaches its goal"
+msgstr "alcanza su objetivo"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:260
+msgid "Set up an alert"
+msgstr "Crea una alerta"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:329
+msgid "Edit your alert"
+msgstr "Edita tu alerta"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:329
+msgid "Edit alert"
+msgstr "Editar alerta"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:372
+msgid "This alert will no longer be emailed to {0}."
+msgstr "Esta alerta ya no se enviará por correo electrónico a {0}."
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:380
+msgid "Slack channel {0} will no longer get this alert."
+msgstr "El canal Slack {0} ya no recibirá esta alerta."
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:384
+msgid "Channel {0} will no longer receive this alert."
+msgstr "El canal {0} ya no recibirá esta alerta."
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:401
+msgid "Delete this alert"
+msgstr "Elimina esta alerta"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:403
+msgid "Stop delivery and delete this alert. There's no undo, so be careful."
+msgstr "Detener el envío y elimina esta alerta. No se puede deshacer, así que ten cuidado."
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:411
+msgid "Delete this alert?"
+msgstr "¿Eliminar esta alerta?"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:495
+msgid "Alert me when the line…"
+msgstr "Avísame cuando la línea…"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:496
+msgid "Alert me when the progress bar…"
+msgstr "Avísame cuando la barra de progreso…"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:499
+msgid "Goes above the goal line"
+msgstr "Va por encima de la línea de objetivo"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:499
+msgid "Reaches the goal"
+msgstr "Llega al objetivo"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:502
+msgid "Goes below the goal line"
+msgstr "Va por debajo de la línea de objetivo"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:502
+msgid "Goes below the goal"
+msgstr "Va por debajo del objetivo"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:510
+msgid "The first time it crosses, or every time?"
+msgstr "¿La primera vez que cruza, o cada vez?"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:511
+msgid "The first time it reaches the goal, or every time?"
+msgstr "¿La primera vez que alcanza el objetivo, o cada vez?"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:513
+msgid "The first time"
+msgstr "La primera vez"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:514
+msgid "Every time"
+msgstr "Cada vez"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:617
+msgid "Where do you want to send these alerts?"
+msgstr "¿A dónde quieres enviar estas alertas?"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:628
+msgid "Email alerts to:"
+msgstr "Envía emails de alerta a:"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:670
+msgid "{0} Goal-based alerts aren't yet supported for charts with more than one line, so this alert will be sent whenever the chart has {1}."
+msgstr "{0} Las alertas basadas en objetivos aún no son compatibles para gráficos con más de una línea, por lo que esta alerta se enviará siempre que el gráfico tenga {1}."
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:673
+msgid "results"
+msgstr "resultados"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:677
+msgid "{0} This kind of alert is most useful when your saved question doesn’t {1} return any results, but you want to know when it does."
+msgstr "{0} Este tipo de alerta es más útil cuando tu pregunta {1} no arroja ningún resultado, pero quieres saber cuándo lo hace."
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:678
+msgid "Tip"
+msgstr "Consejo:"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:680
+msgid "usually"
+msgstr "generalmente"
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:57
+msgid "Pick a segment or table"
+msgstr "Elige un segmento o tabla"
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:73
+msgid "Select a database"
+msgstr "Selecciona una base de datos"
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:88
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:87
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:187
+#: frontend/src/metabase/reference/components/MetricImportantFieldsDetail.jsx:35
+msgid "Select..."
+msgstr "Seleccionar..."
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:128
+msgid "Select a table"
+msgstr "Selecciona una tabla"
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:785
+msgid "No tables found in this database."
+msgstr "No se encontraron tablas en esta base de datos."
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:822
+msgid "Is a question missing?"
+msgstr "¿Falta una pregunta?"
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:826
+msgid "Learn more about nested queries"
+msgstr "Obtén más información sobre consultas anidadas"
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:860
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:30
+msgid "Fields"
+msgstr "Campos"
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:938
+msgid "No segments were found."
+msgstr "No se encontraron segmentos"
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:961
+msgid "Find a segment"
+msgstr "Encuentra un segmento"
+
+#: frontend/src/metabase/query_builder/components/ExpandableString.jsx:46
+msgid "View less"
+msgstr "Ver menos"
+
+#: frontend/src/metabase/query_builder/components/ExpandableString.jsx:56
+msgid "View more"
+msgstr "Ver más"
+
+#: frontend/src/metabase/query_builder/components/ExtendedOptions.jsx:111
+msgid "Pick a field to sort by"
+msgstr "Elige un campo para ordenar por"
+
+#: frontend/src/metabase/query_builder/components/ExtendedOptions.jsx:124
+msgid "Sort"
+msgstr "Ordenar"
+
+#: frontend/src/metabase/query_builder/components/ExtendedOptions.jsx:193
+msgid "Row limit"
+msgstr "Límite de filas"
+
+#: frontend/src/metabase/query_builder/components/FieldName.jsx:76
+msgid "Unknown Field"
+msgstr "Campo Desconocido"
+
+#: frontend/src/metabase/query_builder/components/FieldName.jsx:79
+msgid "field"
+msgstr "campo"
+
+#: frontend/src/metabase/query_builder/components/Filter.jsx:113
+msgid "Matches"
+msgstr "Conincidencias"
+
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:152
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:160
+msgid "Add filters to narrow your answer"
+msgstr "Añade filtros para limitar tu respuesta"
+
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:284
+msgid "Add a grouping"
+msgstr "Añadir una agrupación"
+
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:322
+#: frontend/src/metabase/visualizations/components/LineAreaBarChart.jsx:102
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:55
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:92
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:131
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:58
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:66
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:73
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:54
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:61
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:66
+#: frontend/src/metabase/visualizations/visualizations/Table.jsx:60
+#: frontend/src/metabase/visualizations/visualizations/Table.jsx:72
+msgid "Data"
+msgstr "Datos"
+
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:352
+msgid "Filtered by"
+msgstr "Filtrado por"
+
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:369
+msgid "View"
+msgstr "Ver"
+
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:386
+msgid "Grouped By"
+msgstr "Agrupado por"
+
+#: frontend/src/metabase/query_builder/components/LimitWidget.jsx:27
+msgid "None"
+msgstr "Ninguno"
+
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:338
+msgid "This question is written in {0}."
+msgstr "Esta pregunta está escrita en {0}"
+
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:346
+msgid "Hide Editor"
+msgstr "Esconder Editor"
+
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:347
+msgid "Hide Query"
+msgstr "Esconder Consulta"
+
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:352
+msgid "Open Editor"
+msgstr "Abrir Editor"
+
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:353
+msgid "Show Query"
+msgstr "Mostrar Consulta"
+
+#: frontend/src/metabase/query_builder/components/QueryDefinitionTooltip.jsx:25
+msgid "This metric has been retired.  It's no longer available for use."
+msgstr "Esta métrica ha sido retirada. Ya no está disponible para su uso."
+
+#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:34
+#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:46
+msgid "Download full results"
+msgstr "Descarga los resultados completos"
+
+#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:35
+msgid "Download this data"
+msgstr "Descargar esta información"
+
+#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:46
+msgid "Warning"
+msgstr "Aviso"
+
+#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:52
+msgid "Your answer has a large number of rows so it could take a while to download."
+msgstr "Tu respuesta tiene una gran cantidad de filas, por lo que podría tardar un tiempo en descargarse."
+
+#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:53
+msgid "The maximum download size is 1 million rows."
+msgstr "El tamaño máximo de descarga es de 1 millón de filas."
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:232
+msgid "Edit question"
+msgstr "Editar pregunta"
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:249
+msgid "SAVE CHANGES"
+msgstr "GUARDAR CAMBIOS"
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:263
+msgid "CANCEL"
+msgstr "CANCELAR"
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:276
+msgid "Move question"
+msgstr "Mover pregunta"
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:283
+msgid "Which collection should this be in?"
+msgstr "¿En qué colección debería estar esto?"
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:313
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:110
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorSidebar.jsx:83
+msgid "Variables"
+msgstr "Variables"
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:432
+msgid "Learn about your data"
+msgstr "Conoce tus datos"
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:460
+msgid "Alerts are on"
+msgstr "Las alertas están activadas"
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:522
+msgid "started from"
+msgstr "iniciado desde"
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:48
+msgid "SQL"
+msgstr "SQL"
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:48
+msgid "native query"
+msgstr "consulta nativa"
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:52
+msgid "Not Supported"
+msgstr "No Soportado"
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:58
+msgid "View the {0}"
+msgstr "Ver el {0}"
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:59
+msgid "Switch to {0}"
+msgstr "Cambiar a {0}"
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:62
+msgid "Switch to Builder"
+msgstr "Cambiar al Editor"
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:87
+msgid "{0} for this question"
+msgstr "{0} de esta pregunta"
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:111
+msgid "Convert this question to {0}"
+msgstr "Convertir esta pregunta en {0}"
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:122
+msgid "This question will take approximately {0} to refresh"
+msgstr "Esta pregunta se actualizará en aproximadamente {0}"
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:131
+msgid "Updated {0}"
+msgstr "Actualizado hace {0}"
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:141
+msgid "row"
+msgid_plural "rows"
+msgstr[0] "fila"
+msgstr[1] "filas"
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:148
+msgid "Showing first {0} {1}"
+msgstr "Mostrando primeras {0} {1}"
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:151
+msgid "Showing {0} {1}"
+msgstr "Mostrando {0} {1}"
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:281
+msgid "Doing science"
+msgstr "Haciendo ciencia"
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:294
+msgid "If you give me some data I can show you something cool. Run a Query!"
+msgstr "Si me das algunos datos, puedo mostrarte algo genial. ¡Ejecuta una consulta!"
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:299
+msgid "How do I use this thing?"
+msgstr "¿Cómo uso esta cosa?"
+
+#: frontend/src/metabase/query_builder/components/RunButton.jsx:28
+msgid "Get Answer"
+msgstr "Obtener Respuesta"
+
+#: frontend/src/metabase/query_builder/components/SavedQuestionIntroModal.jsx:12
+msgid "It's okay to play around with saved questions"
+msgstr "Puedes jugar con preguntas guardadas"
+
+#: frontend/src/metabase/query_builder/components/SavedQuestionIntroModal.jsx:14
+msgid "You won't make any permanent changes to a saved question unless you click the edit icon in the top-right."
+msgstr "No harás ningún cambio permanente en una pregunta guardada a menos que hagas clic en el icono de edición en la esquina superior derecha."
+
+#: frontend/src/metabase/query_builder/components/SearchBar.jsx:28
+msgid "Search for"
+msgstr "Buscar"
+
+#: frontend/src/metabase/query_builder/components/SelectionModule.jsx:158
+msgid "Advanced..."
+msgstr "Avanzado..."
+
+#: frontend/src/metabase/query_builder/components/SelectionModule.jsx:167
+msgid "Sorry. Something went wrong."
+msgstr "Lo siento. Algo salió mal."
+
+#: frontend/src/metabase/query_builder/components/TimeGroupingPopover.jsx:40
+msgid "Group time by"
+msgstr "Agrupar tiempo por"
+
+#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:46
+msgid "Your question took too long"
+msgstr "Tu pregunta tardó demasiado tiempo"
+
+#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:47
+msgid "We didn't get an answer back from your database in time, so we had to stop. You can try again in a minute, or if the problem persists, you can email an admin to let them know."
+msgstr "No obtuvimos una respuesta de tu base de datos a tiempo, así que tuvimos que parar.Puedes volver a intentarlo en un minuto, o si el problema persiste, puedes enviar un correo electrónico a un administrador para avisarle."
+
+#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:55
+msgid "We're experiencing server issues"
+msgstr "Estamos experimentando problemas con el servidor"
+
+#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:56
+msgid "Try refreshing the page after waiting a minute or two. If the problem persists we'd recommend you contact an admin."
+msgstr "Intenta actualizar la página después de esperar uno o dos minutos. Si el problema persiste, te recomendamos que te pongas en contacto con un administrador."
+
+#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:88
+msgid "There was a problem with your question"
+msgstr "Hubo un problema con tu pregunta"
+
+#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:89
+msgid "Most of the time this is caused by an invalid selection or bad input value. Double check your inputs and retry your query."
+msgstr "La mayoría de las veces esto es causado por una selección no válida o un valor de entrada incorrecto.Revisa tus entradas y vuelva a intentar la consulta."
+
+#: frontend/src/metabase/query_builder/components/VisualizationResult.jsx:60
+msgid "This may be the answer you’re looking for. If not, try removing or changing your filters to make them less specific."
+msgstr "Esta puede ser la respuesta que estás buscando. De lo contrario, intenta eliminar o cambiar tus filtros para que sean menos específicos."
+
+#: frontend/src/metabase/query_builder/components/VisualizationResult.jsx:66
+msgid "You can also {0} when there are some results."
+msgstr "También puedes {0} cuando hay algunos resultados."
+
+#: frontend/src/metabase/query_builder/components/VisualizationResult.jsx:68
+msgid "get an alert"
+msgstr "recibir una alerta"
+
+#: frontend/src/metabase/query_builder/components/VisualizationResult.jsx:77
+msgid "Back to last run"
+msgstr "Volver a la última ejecución"
+
+#: frontend/src/metabase/query_builder/components/VisualizationSettings.jsx:53
+msgid "Visualization"
+msgstr "Visualización"
+
+#: frontend/src/metabase/query_builder/components/dataref/DetailPane.jsx:17
+#: frontend/src/metabase/query_builder/components/dataref/TablePane.jsx:151
+msgid "No description set."
+msgstr "Sin descripción"
+
+#: frontend/src/metabase/query_builder/components/dataref/DetailPane.jsx:21
+msgid "Use for current question"
+msgstr "Usar para la pregunta actual"
+
+#: frontend/src/metabase/query_builder/components/dataref/DetailPane.jsx:33
+#: frontend/src/metabase/reference/components/UsefulQuestions.jsx:16
+msgid "Potentially useful questions"
+msgstr "Preguntas potencialmente útiles"
+
+#: frontend/src/metabase/query_builder/components/dataref/FieldPane.jsx:156
+msgid "Group by {0}"
+msgstr "Agrupar por {0}"
+
+#: frontend/src/metabase/query_builder/components/dataref/FieldPane.jsx:165
+msgid "Sum of all values of {0}"
+msgstr "Suma de todos los valores de {0}"
+
+#: frontend/src/metabase/query_builder/components/dataref/FieldPane.jsx:173
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:63
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:51
+msgid "All distinct values of {0}"
+msgstr "Todos los valores distintos de {0}"
+
+#: frontend/src/metabase/query_builder/components/dataref/FieldPane.jsx:177
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:39
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:51
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:39
+msgid "Number of {0} grouped by {1}"
+msgstr "Número de {0} agrupadas por {1}"
+
+#: frontend/src/metabase/query_builder/components/dataref/MainPane.jsx:11
+#: frontend/src/metabase/reference/databases/DatabaseSidebar.jsx:20
+#: frontend/src/metabase/reference/databases/FieldSidebar.jsx:27
+#: frontend/src/metabase/reference/databases/TableSidebar.jsx:23
+#: frontend/src/metabase/reference/guide/BaseSidebar.jsx:17
+#: frontend/src/metabase/reference/guide/BaseSidebar.jsx:19
+#: frontend/src/metabase/reference/metrics/MetricSidebar.jsx:20
+#: frontend/src/metabase/reference/segments/SegmentFieldSidebar.jsx:24
+#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:20
+msgid "Data Reference"
+msgstr "Referencia de Datos"
+
+#: frontend/src/metabase/query_builder/components/dataref/MainPane.jsx:13
+msgid "Learn more about your data structure to ask more useful questions"
+msgstr "Aprende más sobre tu estructura de datos para hacer preguntas más útiles"
+
+#: frontend/src/metabase/query_builder/components/dataref/MetricPane.jsx:58
+#: frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx:84
+msgid "Could not find the table metadata prior to creating a new question"
+msgstr "No se pudieron encontrar los metadatos de la tabla antes de crear una nueva pregunta"
+
+#: frontend/src/metabase/query_builder/components/dataref/MetricPane.jsx:80
+msgid "See {0}"
+msgstr "Ver {0}"
+
+#: frontend/src/metabase/query_builder/components/dataref/MetricPane.jsx:94
+msgid "Metric Definition"
+msgstr "Definición de Métrica"
+
+#: frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx:118
+msgid "Filter by {0}"
+msgstr "Filtrar por {0}"
+
+#: frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx:127
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:36
+msgid "Number of {0}"
+msgstr "Número de {0}"
+
+#: frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx:134
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:46
+msgid "See all {0}"
+msgstr "Ver todos los {0}"
+
+#: frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx:148
+msgid "Segment Definition"
+msgstr "Definición de Segmento"
+
+#: frontend/src/metabase/query_builder/components/dataref/TablePane.jsx:50
+msgid "An error occurred loading the table"
+msgstr "Se produjo un error al cargar la tabla"
+
+#: frontend/src/metabase/query_builder/components/dataref/TablePane.jsx:74
+msgid "See the raw data for {0}"
+msgstr "Ver los datos de {0}"
+
+#: frontend/src/metabase/query_builder/components/dataref/TablePane.jsx:205
+msgid "More"
+msgstr "Más"
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:200
+msgid "Invalid expression"
+msgstr "Expresión inválida"
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:275
+msgid "unknown error"
+msgstr "error no controlado"
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:46
+msgid "Field formula"
+msgstr "Fórmula de campo"
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:57
+msgid "Think of this as being kind of like writing a formula in a spreadsheet program: you can use numbers, fields in this table, mathematical symbols like +, and some functions. So you could type something like Subtotal - Cost."
+msgstr "Piensa en esto como algo parecido a escribir una fórmula en un programa de hoja de cálculo: puedes usar números, campos de esta tabla, símbolos matemáticos como + y algunas funciones. Así puedes escribir algo como Subtotal - Cost."
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:62
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:126
+msgid "Learn more"
+msgstr "Aprende más"
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:66
+msgid "Give it a name"
+msgstr "Dale un nombre"
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:72
+msgid "Something nice and descriptive"
+msgstr "Algo agradable y descriptivo"
+
+#: frontend/src/metabase/query_builder/components/expressions/Expressions.jsx:60
+msgid "Add a custom field"
+msgstr "Añadir un campo personalizado"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:17
+msgid "Include {0}"
+msgstr "Incluir {0}"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:19
+msgid "Case sensitive"
+msgstr "Distingue mayúsculas y minúsculas"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:23
+msgid "today"
+msgstr "hoy"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:24
+msgid "this week"
+msgstr "esta semana"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:25
+msgid "this month"
+msgstr "este mes"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:26
+msgid "this year"
+msgstr "este año"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:27
+msgid "this minute"
+msgstr "este minuto"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:28
+msgid "this hour"
+msgstr "esta hora"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx:285
+msgid "not implemented {0}"
+msgstr "{0} no está implementado"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx:286
+msgid "true"
+msgstr "verdadero"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx:286
+msgid "false"
+msgstr "falso"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx:390
+msgid "Add filter"
+msgstr "Añadir filtro"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterWidgetList.jsx:64
+msgid "Item"
+msgstr "Elemento"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:224
+msgid "Previous"
+msgstr "Anterior"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:255
+msgid "Current"
+msgstr "Actual"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:282
+msgid "On"
+msgstr "Activado"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/NumberPicker.jsx:47
+msgid "Enter desired number"
+msgstr "Ingresa el número deseado"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/SelectPicker.jsx:83
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:100
+msgid "Empty"
+msgstr "Vacío"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/SelectPicker.jsx:116
+msgid "Find a value"
+msgstr "Encuentra un valor"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/SpecificDatePicker.jsx:113
+msgid "Hide calendar"
+msgstr "Esconder calendario"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/SpecificDatePicker.jsx:113
+msgid "Show calendar"
+msgstr "Mostrar calendario"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/TextPicker.jsx:97
+msgid "You can enter multiple values separated by commas"
+msgstr "Puedes ingresar varios valores separados por comas"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/TextPicker.jsx:38
+msgid "Enter desired text"
+msgstr "Ingresa el texto deseado"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:83
+msgid "Try it"
+msgstr "Pruébalo"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:105
+msgid "What's this for?"
+msgstr "¿Para qué sirve esto?"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:107
+msgid "Variables in native queries let you dynamically replace values in your queries using filter widgets or through the URL."
+msgstr "Las variables en las consultas nativas te permiten reemplazar dinámicamente los valores en tus consultas utilizando elementos de filtro o a través de la URL."
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:112
+msgid "{0} creates a variable in this SQL template called \"variable_name\". Variables can be given types in the side panel, which changes their behavior. All variable types other than \"Field Filter\" will automatically cause a filter widget to be placed on this question; with Field Filters, this is optional. When this filter widget is filled in, that value replaces the variable in the SQL template."
+msgstr "{0} crea una variable en esta plantilla SQL llamada \"nombre_variable\". Puedes asignar tipos a las variables en el panel lateral, lo que cambia su comportamiento. Todos los tipos de variables que no sean \"Filtro de Campo\" provocarán automáticamente que se coloque un elemento de filtro en esta pregunta; con Filtros de Campo, esto es opcional. Cuando se rellena este elemento de filtro, ese valor reemplaza a la variable en la plantilla SQL."
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:121
+msgid "Field Filters"
+msgstr "Filtros de Campo"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:123
+msgid "Giving a variable the \"Field Filter\" type allows you to link SQL cards to dashboard filter widgets or use more types of filter widgets on your SQL question. A Field Filter variable inserts SQL similar to that generated by the GUI query builder when adding filters on existing columns."
+msgstr "Darle a una variable el tipo \"Filtro de campo\" te permite vincular las tarjetas SQL con los elementos de filtro en los cuadros de mando o usar más tipos de elementos de filtro en tu pregunta de SQL. Una variable de filtro de campo inserta SQL similar a la generada por el generador de consultas GUI cuando se añaden filtros en columnas existentes."
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:126
+msgid "When adding a Field Filter variable, you'll need to map it to a specific field. You can then choose to display a filter widget on your question, but even if you don't, you can now map your Field Filter variable to a dashboard filter when adding this question to a dashboard. Field Filters should be used inside of a \"WHERE\" clause."
+msgstr "Al añadir una variable de Filtro de campo, deberás asignarla a un campo específico. A continuación, puedes optar por mostrar un elemento de filtro en tu pregunta, pero incluso si no lo haces, ahora puedes asignar tu variable de Filtro de campo a un filtro de cuadro de mando al agregar esta pregunta al mismo. Los filtros de campo deben usarse dentro de una cláusula \"WHERE\"."
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:130
+msgid "Optional Clauses"
+msgstr "Cláusulas opcionales"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:132
+msgid "brackets around a {0} create an optional clause in the template. If \"variable\" is set, then the entire clause is placed into the template. If not, then the entire clause is ignored."
+msgstr "los corchetes alrededor de un {0} crean una cláusula opcional en la plantilla. Si se establece la \"variable\", entonces la cláusula completa se coloca en la plantilla. Si no, entonces se ignora toda la cláusula."
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:142
+msgid "To use multiple optional clauses you can include at least one non-optional WHERE clause followed by optional clauses starting with \"AND\"."
+msgstr "Para utilizar varias cláusulas opcionales, puedes incluir al menos una cláusula WHERE no opcional seguida de cláusulas opcionales que comiencen con \"AND\"."
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:154
+msgid "Read the full documentation"
+msgstr "Lee la documentación completa"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:124
+msgid "Filter label"
+msgstr "Filtro de Etiqueta"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:136
+msgid "Variable type"
+msgstr "Tipo de Variable"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:145
+msgid "Text"
+msgstr "Texto"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:147
+msgid "Date"
+msgstr "Fecha"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:148
+msgid "Field Filter"
+msgstr "Filtro de Campo"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:154
+msgid "Field to map to"
+msgstr "Campo para mapear a"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:176
+msgid "Filter widget type"
+msgstr "Tipo de filtro"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:199
+msgid "Required?"
+msgstr "¿Obligatorio?"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:210
+msgid "Default filter widget value"
+msgstr "Valor por defecto del filtro"
+
+#: frontend/src/metabase/query_builder/containers/ArchiveQuestionModal.jsx:46
+msgid "Archive this question?"
+msgstr "¿Archivar esta pregunta?"
+
+#: frontend/src/metabase/query_builder/containers/ArchiveQuestionModal.jsx:57
+msgid "This question will be removed from any dashboards or pulses using it."
+msgstr "Esta pregunta se eliminará de cualquier cuadto de mando o pulso que lo usen."
+
+#: frontend/src/metabase/query_builder/containers/QueryBuilder.jsx:136
+msgid "Question"
+msgstr "Pregunta"
+
+#: frontend/src/metabase/questions/containers/AddToDashboard.jsx:11
+msgid "Pick a question to add"
+msgstr "Elige una pregunta para añadir"
+
+#: frontend/src/metabase/reference/components/EditHeader.jsx:19
+msgid "You are editing this page"
+msgstr "Estás editando esta página"
+
+#: frontend/src/metabase/reference/components/EditableReferenceHeader.jsx:101
+#: frontend/src/metabase/reference/components/ReferenceHeader.jsx:63
+msgid "See this {0}"
+msgstr "Ver este {0}"
+
+#: frontend/src/metabase/reference/components/EditableReferenceHeader.jsx:120
+msgid "A subset of"
+msgstr "Un subconjunto de"
+
+#: frontend/src/metabase/reference/components/Field.jsx:47
+#: frontend/src/metabase/reference/components/Field.jsx:86
+#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:32
+#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:68
+msgid "Select a field type"
+msgstr "Selecciona un tipo de campo"
+
+#: frontend/src/metabase/reference/components/Field.jsx:56
+#: frontend/src/metabase/reference/components/Field.jsx:71
+#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:41
+#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:57
+msgid "No field type"
+msgstr "Sin tipo de campo"
+
+#: frontend/src/metabase/reference/components/FieldToGroupBy.jsx:22
+msgid "by"
+msgstr "por"
+
+#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:25
+#: frontend/src/metabase/reference/databases/FieldList.jsx:152
+#: frontend/src/metabase/reference/segments/SegmentFieldList.jsx:153
+msgid "Field type"
+msgstr "Tipo campo"
+
+#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:72
+msgid "Select a Foreign Key"
+msgstr "Selecciona una Clabe foránea"
+
+#: frontend/src/metabase/reference/components/Formula.jsx:53
+msgid "View the {0} formula"
+msgstr "Ver la fórmula de {0}"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:80
+msgid "Why this {0} is important"
+msgstr "Por qué este {0} es importante"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:81
+msgid "Why this {0} is interesting"
+msgstr "Por qué este {0} es interesante"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:87
+msgid "Nothing important yet"
+msgstr "Nada importante todavía"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:88
+#: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:168
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:233
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:211
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:215
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:219
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:229
+msgid "Nothing interesting yet"
+msgstr "Nada interesante todavía"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:93
+msgid "Things to be aware of about this {0}"
+msgstr "Cosas a tener en cuenta acerca de este {0}"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:97
+#: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:178
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:243
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:221
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:225
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:229
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:239
+msgid "Nothing to be aware of yet"
+msgstr "Nada de lo que estar enterado todavía"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:103
+msgid "Explore this metric"
+msgstr "Explora esta métrica"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:105
+msgid "View this metric"
+msgstr "Ver esta métrica"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:112
+msgid "By {0}"
+msgstr "Por {0}"
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:146
+msgid "Remove item"
+msgstr "Eliminar elemento"
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:155
+msgid "Why is this dashboard the most important?"
+msgstr "¿Por qué este cuadro de mando es el más importante?"
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:156
+msgid "What is useful or interesting about this {0}?"
+msgstr "¿Qué es útil o interesante sobre este {0}?"
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:160
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:174
+msgid "Write something helpful here"
+msgstr "Escribe algo útil aquí"
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:169
+msgid "Is there anything users of this dashboard should be aware of?"
+msgstr "¿Hay algo que los usuarios de este cuadro de mando deben tener en cuenta?"
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:170
+msgid "Anything users should be aware of about this {0}?"
+msgstr "¿Hay alguna cosa que los usuarios deben saber sobre este {0}?"
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:182
+#: frontend/src/metabase/reference/components/MetricImportantFieldsDetail.jsx:26
+msgid "Which 2-3 fields do you usually group this metric by?"
+msgstr "¿Con qué 2-3 campos se agrupa normalmente esta métrica?"
+
+#: frontend/src/metabase/reference/components/GuideHeader.jsx:23
+msgid "This is the perfect place to start if you’re new to your company’s data, or if you just want to check in on what’s going on."
+msgstr "Este es el lugar perfecto para comenzar si eres nuevo en los datos de tu empresa, o si solo deseas verificar lo que está sucediendo."
+
+#: frontend/src/metabase/reference/components/MetricImportantFieldsDetail.jsx:65
+msgid "Most useful fields to group this metric by"
+msgstr "Campos más útiles para agrupar esta métrica"
+
+#: frontend/src/metabase/reference/components/RevisionMessageModal.jsx:32
+msgid "Reason for changes"
+msgstr "Motivo del cambio"
+
+#: frontend/src/metabase/reference/components/RevisionMessageModal.jsx:36
+msgid "Leave a note to explain what changes you made and why they were required"
+msgstr "Deja una nota para explicar qué cambios ha realizado y por qué fueron necesarios"
+
+#: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:166
+msgid "Why this database is interesting"
+msgstr "Por qué esta base de datos es interesante"
+
+#: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:176
+msgid "Things to be aware of about this database"
+msgstr "Cosas a tener en cuenta acerca de esta base de datos"
+
+#: frontend/src/metabase/reference/databases/DatabaseList.jsx:46
+#: frontend/src/metabase/reference/guide/BaseSidebar.jsx:39
+msgid "Databases and tables"
+msgstr "Bases de datos y tablas"
+
+#: frontend/src/metabase/reference/databases/DatabaseSidebar.jsx:27
+#: frontend/src/metabase/reference/databases/FieldSidebar.jsx:38
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:170
+#: frontend/src/metabase/reference/databases/TableSidebar.jsx:31
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:184
+#: frontend/src/metabase/reference/metrics/MetricSidebar.jsx:27
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:188
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:187
+#: frontend/src/metabase/reference/segments/SegmentFieldSidebar.jsx:31
+#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:27
+msgid "Details"
+msgstr "Detalles"
+
+#: frontend/src/metabase/reference/databases/DatabaseSidebar.jsx:33
+#: frontend/src/metabase/reference/databases/TableList.jsx:111
+msgid "Tables in {0}"
+msgstr "Tablas en {0}"
+
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:222
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:200
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:218
+msgid "Actual name in database"
+msgstr "Nombre real en la base de datos"
+
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:231
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:227
+msgid "Why this field is interesting"
+msgstr "Por qué este campo es interesante"
+
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:241
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:237
+msgid "Things to be aware of about this field"
+msgstr "Cosas a tener en cuenta sobre este campo"
+
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:253
+#: frontend/src/metabase/reference/databases/FieldList.jsx:155
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:249
+#: frontend/src/metabase/reference/segments/SegmentFieldList.jsx:156
+msgid "Data type"
+msgstr "Tipo de dato"
+
+#: frontend/src/metabase/reference/databases/FieldList.jsx:39
+#: frontend/src/metabase/reference/segments/SegmentFieldList.jsx:39
+msgid "Fields in this table will appear here as they're added"
+msgstr "Los campos en esta tabla aparecerán aquí a medida que se añadan"
+
+#: frontend/src/metabase/reference/databases/FieldList.jsx:134
+#: frontend/src/metabase/reference/segments/SegmentFieldList.jsx:135
+msgid "Fields in {0}"
+msgstr "Campos en {0}"
+
+#: frontend/src/metabase/reference/databases/FieldList.jsx:149
+#: frontend/src/metabase/reference/segments/SegmentFieldList.jsx:150
+msgid "Field name"
+msgstr "Nombre de campo"
+
+#: frontend/src/metabase/reference/databases/FieldSidebar.jsx:46
+msgid "X-ray this field"
+msgstr "Aplica rayos-X a este campo"
+
+#: frontend/src/metabase/reference/databases/NoDatabasesEmptyState.jsx:8
+msgid "Metabase is no fun without any data"
+msgstr "Metabase no es divertido sin datos"
+
+#: frontend/src/metabase/reference/databases/NoDatabasesEmptyState.jsx:9
+msgid "Your databases will appear here once you connect one"
+msgstr "Tus bases de datos aparecerán aquí una vez que conectes una"
+
+#: frontend/src/metabase/reference/databases/NoDatabasesEmptyState.jsx:10
+msgid "Databases will appear here once your admins have added some"
+msgstr "Las bases de datos aparecerán aquí una vez que los administradores hayan añadido algunas"
+
+#: frontend/src/metabase/reference/databases/NoDatabasesEmptyState.jsx:12
+msgid "Connect a database"
+msgstr "Conecta una base de datos"
+
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:38
+msgid "Count of {0}"
+msgstr "Número de {0}"
+
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:47
+msgid "See raw data for {0}"
+msgstr "Ver datos de {0}"
+
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:209
+msgid "Why this table is interesting"
+msgstr "Por qué esta tabla es interesante"
+
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:219
+msgid "Things to be aware of about this table"
+msgstr "Cosas a tener en cuenta sobre esta tabla"
+
+#: frontend/src/metabase/reference/databases/TableList.jsx:30
+msgid "Tables in this database will appear here as they're added"
+msgstr "Las tablas en esta base de datos aparecerán aquí cuando se añadan"
+
+#: frontend/src/metabase/reference/databases/TableQuestions.jsx:34
+msgid "Questions about this table will appear here as they're added"
+msgstr "Las preguntas sobre esta tabla aparecerán aquí cuando se añadan"
+
+#: frontend/src/metabase/reference/databases/TableQuestions.jsx:71
+#: frontend/src/metabase/reference/metrics/MetricQuestions.jsx:75
+#: frontend/src/metabase/reference/metrics/MetricSidebar.jsx:33
+#: frontend/src/metabase/reference/segments/SegmentQuestions.jsx:74
+msgid "Questions about {0}"
+msgstr "Preguntas sobre {0}"
+
+#: frontend/src/metabase/reference/databases/TableQuestions.jsx:95
+#: frontend/src/metabase/reference/metrics/MetricQuestions.jsx:99
+#: frontend/src/metabase/reference/segments/SegmentQuestions.jsx:98
+msgid "Created {0} by {1}"
+msgstr "Creado hace {0} por {1}"
+
+#: frontend/src/metabase/reference/databases/TableSidebar.jsx:37
+msgid "Fields in this table"
+msgstr "Campos en esta tabla"
+
+#: frontend/src/metabase/reference/databases/TableSidebar.jsx:45
+msgid "Questions about this table"
+msgstr "Preguntas sobre esta tabla"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:157
+msgid "Help your team get started with your data."
+msgstr "Ayuda a tu equipo a empezar con tus datos."
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:159
+msgid "Show your team what’s most important by choosing your top dashboard, metrics, and segments."
+msgstr "Muestra a tu equipo lo que es más importante al elegir tu cuadro de mando, tus métricas y tus segmentos principales."
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:165
+msgid "Get started"
+msgstr "Empieza"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:173
+msgid "Our most important dashboard"
+msgstr "Nuestro cuadro de mando más importante"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:188
+msgid "Numbers that we pay attention to"
+msgstr "Números a los que prestamos atención"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:213
+msgid "Metrics are important numbers your company cares about. They often represent a core indicator of how the business is performing."
+msgstr "Las métricas son números destacados que le importan a tu empresa. A menudo representan un indicador central de cómo está funcionando el negocio."
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:221
+msgid "See all metrics"
+msgstr "Ver todas las métricas"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:235
+msgid "Segments and tables"
+msgstr "Segmentos y tablas"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:236
+msgid "Tables"
+msgstr "Tablas"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:262
+msgid "Segments and tables are the building blocks of your company's data. Tables are collections of the raw information while segments are specific slices with specific meanings, like {0}"
+msgstr "Los segmentos y tablas son los componentes básicos de los datos de tu empresa. Las tablas son colecciones de información sin formato mientras que los segmentos son trozos acotados con significados específico, como {0}"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:267
+msgid "Tables are the building blocks of your company's data."
+msgstr "Las tablas son los componentes básicos de los datos de tu empresa."
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:277
+msgid "See all segments"
+msgstr "Ver todos los segmentos"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:293
+msgid "See all tables"
+msgstr "Ver todas las tablas"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:301
+msgid "Other things to know about our data"
+msgstr "Otras cosas que debes saber sobre nuestros datos"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:302
+msgid "Find out more"
+msgstr "Descubre más"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:307
+msgid "A good way to get to know your data is by spending a bit of time exploring the different tables and other info available to you. It may take a while, but you'll start to recognize names and meanings over time."
+msgstr "Una buena manera de conocer tus datos es dedicarle un poco de tiempo a explorar las diferentes tablas y otra información disponible. Puedes tardar un poco, pero comenzarás a reconocer nombres y significados con el tiempo."
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:313
+msgid "Explore our data"
+msgstr "Explora nuestros datos"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:321
+msgid "Have questions?"
+msgstr "¿Tienes una pregunta?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:326
+msgid "Contact {0}"
+msgstr "Habla con {0}"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:248
+msgid "Help new Metabase users find their way around."
+msgstr "Ayuda a los nuevos usuarios de Metabase a encontrar su camino."
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:251
+msgid "The Getting Started guide highlights the dashboard, metrics, segments, and tables that matter most, and informs your users of important things they should know before digging into the data."
+msgstr "La guía de introducción destaca el cuadro de mando, las métricas, los segmentos y las tablas que más importan, e informa a los usuarios sobre cosas importantes que deberían saber antes de profundizar en los datos."
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:258
+msgid "Is there an important dashboard for your team?"
+msgstr "¿Hay un cuadro de mando importante para tu equipo?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:260
+msgid "Create a dashboard now"
+msgstr "Crea un cuadro de mando ahora"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:266
+msgid "What is your most important dashboard?"
+msgstr "¿Cuál es tu cuadro de mando más importante?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:285
+msgid "Do you have any commonly referenced metrics?"
+msgstr "¿Tienes alguna métrica comúnmente referenciada?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:287
+msgid "Learn how to define a metric"
+msgstr "Aprende a definir una métrica"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:300
+msgid "What are your 3-5 most commonly referenced metrics?"
+msgstr "¿Cuáles son las 3-5 métricas más comúnmente referenciadas?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:344
+msgid "Add another metric"
+msgstr "Añade otra métrica"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:357
+msgid "Do you have any commonly referenced segments or tables?"
+msgstr "¿Tienes algún segmento o tabla comúnmente referenciado?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:359
+msgid "Learn how to create a segment"
+msgstr "Aprenda a crear un segmento"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:372
+msgid "What are 3-5 commonly referenced segments or tables that would be useful for this audience?"
+msgstr "¿Cuáles son los 3-5 segmentos o tablas comúnmente referenciados que serían útiles paraesta audiencia?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:418
+msgid "Add another segment or table"
+msgstr "Añade otro segmento o tabla"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:427
+msgid "Is there anything your users should understand or know before they start accessing the data?"
+msgstr "¿Hay algo que tus usuarios deberían entender o saber antes de que comiencen a acceder a los datos?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:433
+msgid "What should a user of this data know before they start accessing it?"
+msgstr "¿Qué debe saber un usuario de estos datos antes de que comiencen a acceder a ellos?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:437
+msgid "E.g., expectations around data privacy and use, common pitfalls or misunderstandings, information about data warehouse performance, legal notices, etc."
+msgstr "Por ejemplo, las expectativas sobre la privacidad y el uso de datos, peligros comunes o malentendidos, información sobre el rendimiento del almacén de datos, avisos legales, etc."
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:448
+msgid "Is there someone your users could contact for help if they're confused about this guide?"
+msgstr "¿Hay alguien con quien tus usuarios puedan contactar si necesitan ayuda si están confundidos acerca de esta guía?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:457
+msgid "Who should users contact for help if they're confused about this data?"
+msgstr "¿A quién deben contactar los usuarios para obtener ayuda si están confundidos acerca de esta información?"
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:75
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:95
+msgid "Please enter a revision message"
+msgstr "Por favor añade un mensaje de revisión"
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:213
+msgid "Why this Metric is interesting"
+msgstr "Por qué esta métrica es interesante"
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:223
+msgid "Things to be aware of about this Metric"
+msgstr "Cosas a tener en cuenta acerca de esta métrica"
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:233
+msgid "How this Metric is calculated"
+msgstr "Cómo se calcula esta métrica"
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:235
+msgid "Nothing on how it's calculated yet"
+msgstr "Nada sobre cómo se calculó aún"
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:293
+msgid "Other fields you can group this metric by"
+msgstr "Otros campos por los que puedes agrupar esta métrica"
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:294
+msgid "Fields you can group this metric by"
+msgstr "Campos por los que puedes agrupar esta métrica"
+
+#: frontend/src/metabase/reference/metrics/MetricList.jsx:23
+msgid "Metrics are the official numbers that your team cares about"
+msgstr "Las métricas son los números oficiales de los que tu equipo se preocupa"
+
+#: frontend/src/metabase/reference/metrics/MetricList.jsx:25
+msgid "Metrics will appear here once your admins have created some"
+msgstr "Las métricas aparecerán aquí una vez que tus administradores hayan creado algunas"
+
+#: frontend/src/metabase/reference/metrics/MetricList.jsx:27
+msgid "Learn how to create metrics"
+msgstr "Aprende cómo crear métricas"
+
+#: frontend/src/metabase/reference/metrics/MetricQuestions.jsx:35
+msgid "Questions about this metric will appear here as they're added"
+msgstr "Las preguntas sobre esta métrica aparecerán aquí cuando se añadan"
+
+#: frontend/src/metabase/reference/metrics/MetricRevisions.jsx:29
+msgid "There are no revisions for this metric"
+msgstr "No hay revisiones para esta métrica"
+
+#: frontend/src/metabase/reference/metrics/MetricRevisions.jsx:88
+#: frontend/src/metabase/reference/metrics/MetricSidebar.jsx:47
+#: frontend/src/metabase/reference/segments/SegmentRevisions.jsx:88
+msgid "Revision history for {0}"
+msgstr "Historial de revisiones de {0}"
+
+#: frontend/src/metabase/reference/metrics/MetricSidebar.jsx:39
+msgid "X-ray this metric"
+msgstr "Aplica rayos-X a esta métrica"
+
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:217
+msgid "Why this Segment is interesting"
+msgstr "Por qué este segmento es interesante"
+
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:227
+msgid "Things to be aware of about this Segment"
+msgstr "Cosas a tener en cuenta sobre este segmento"
+
+#: frontend/src/metabase/reference/segments/SegmentList.jsx:23
+msgid "Segments are interesting subsets of tables"
+msgstr "Los segmentos son subconjuntos interesantes de tablas"
+
+#: frontend/src/metabase/reference/segments/SegmentList.jsx:24
+msgid "Defining common segments for your team makes it even easier to ask questions"
+msgstr "La definición de segmentos comunes para tu equipo hace que sea aún más fácil hacer preguntas"
+
+#: frontend/src/metabase/reference/segments/SegmentList.jsx:25
+msgid "Segments will appear here once your admins have created some"
+msgstr "Los segmentos aparecerán aquí una vez que los administradores hayan creado algunos"
+
+#: frontend/src/metabase/reference/segments/SegmentList.jsx:27
+msgid "Learn how to create segments"
+msgstr "Aprende a crear segmentos"
+
+#: frontend/src/metabase/reference/segments/SegmentQuestions.jsx:35
+msgid "Questions about this segment will appear here as they're added"
+msgstr "Las preguntas sobre este segmento aparecerán aquí cuando se añadan"
+
+#: frontend/src/metabase/reference/segments/SegmentRevisions.jsx:29
+msgid "There are no revisions for this segment"
+msgstr "No hay revisiones para este segmento"
+
+#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:33
+msgid "Fields in this segment"
+msgstr "Campos en este segmento"
+
+#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:39
+msgid "Questions about this segment"
+msgstr "Preguntas sobre este segmento"
+
+#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:45
+msgid "X-ray this segment"
+msgstr "Aplica rayos-X a este segmento"
+
+#: frontend/src/metabase/routes.jsx:182
+msgid "Login"
+msgstr "Iniciar sesión"
+
+#: frontend/src/metabase/nav/containers/Navbar.jsx:130
+#: frontend/src/metabase/routes.jsx:198
+msgid "Search"
+msgstr "Buscar"
+
+#: frontend/src/metabase/routes.jsx:217
+msgid "Dashboard"
+msgstr "Cuadro de Mando"
+
+#: frontend/src/metabase/routes.jsx:227
+msgid "New Question"
+msgstr "Nueva Pregunta"
+
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:125
+msgid "Select the type of Database you use"
+msgstr "Selecciona el tipo de base de datos que utilizas"
+
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:141
+msgid "Add your data"
+msgstr "Añade tus datos"
+
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:145
+msgid "I'll add my own data later"
+msgstr "Añadiré mis propios datos más tarde"
+
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:146
+msgid "Connecting to {0}"
+msgstr "Conectando con {0}"
+
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:165
+msgid "You’ll need some info about your database, like the username and password. If you don’t have that right now, Metabase also comes with a sample dataset you can get started with."
+msgstr "Necesitarás información sobre tu base de datos, como el nombre de usuario y la contraseña. Si no tienes eso ahora mismo, Metabase también viene con un conjunto de datos de muestra con los que puedes empezar."
+
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:196
+msgid "I'll add my data later"
+msgstr "Añadiré mis datos más tarde"
+
+#: frontend/src/metabase/setup/components/DatabaseSchedulingStep.jsx:41
+msgid "Control automatic scans"
+msgstr "Control de escaneos automáticos"
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:53
+msgid "Usage data preferences"
+msgstr "Preferencias de uso de datos"
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:56
+msgid "Thanks for helping us improve"
+msgstr "Gracias por ayudarnos a mejorar"
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:57
+msgid "We won't collect any usage events"
+msgstr "No recopilaremos ningún evento de uso"
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:76
+msgid "In order to help us improve Metabase, we'd like to collect certain data about usage through Google Analytics."
+msgstr "Para ayudarnos a mejorar Metabase, nos gustaría recopilar cierta información sobre el uso a través de Google Analytics."
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:85
+msgid "Here's a full list of everything we track and why."
+msgstr "Aquí hay una lista completa de todo lo que rastreamos y por qué."
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:98
+msgid "Allow Metabase to anonymously collect usage events"
+msgstr "Permitir que Metabase recopile eventos de uso de forma anónima"
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:105
+msgid "Metabase {0} collects anything about your data or question results."
+msgstr "Metabase {0} recopila información sobre tus datos o resultados de preguntas."
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:106
+msgid "never"
+msgstr "nunca"
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:108
+msgid "All collection is completely anonymous."
+msgstr "Toda la información obtenida es completamente anónima."
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:110
+msgid "Collection can be turned off at any point in your admin settings."
+msgstr "La recopilación puede desactivarse en cualquier momento en la sección de configuración."
+
+#: frontend/src/metabase/setup/components/Setup.jsx:45
+msgid "If you feel stuck"
+msgstr "Si te sientes abrumado"
+
+#: frontend/src/metabase/setup/components/Setup.jsx:52
+msgid "our getting started guide"
+msgstr "nuestra guía de inicio"
+
+#: frontend/src/metabase/setup/components/Setup.jsx:53
+msgid "is just a click away."
+msgstr "esta a un solo click."
+
+#: frontend/src/metabase/setup/components/Setup.jsx:95
+msgid "Welcome to Metabase"
+msgstr "Bienvenid@ a Metabase"
+
+#: frontend/src/metabase/setup/components/Setup.jsx:96
+msgid "Looks like everything is working. Now let’s get to know you, connect to your data, and start finding you some answers!"
+msgstr "Parece que todo está funcionando. ¡Ahora vamos a conocerte, conectarte con tus datos y comenzar a encontrarte algunas respuestas!"
+
+#: frontend/src/metabase/setup/components/Setup.jsx:100
+msgid "Let's get started"
+msgstr "Empecemos"
+
+#: frontend/src/metabase/setup/components/Setup.jsx:145
+msgid "You're all set up!"
+msgstr "¡Estás listo!"
+
+#: frontend/src/metabase/setup/components/Setup.jsx:156
+msgid "Take me to Metabase"
+msgstr "Llévame a Metabase"
+
+#: frontend/src/metabase/setup/components/UserStep.jsx:155
+msgid "What should we call you?"
+msgstr "¿Cómo deberíamos llamarte?"
+
+#: frontend/src/metabase/setup/components/UserStep.jsx:156
+msgid "Hi, {0}. nice to meet you!"
+msgstr "Hola, {0} ¡Encantado de conocerte!"
+
+#: frontend/src/metabase/setup/components/UserStep.jsx:243
+msgid "Create a password"
+msgstr "Crea una contraseña"
+
+#: frontend/src/metabase/setup/components/UserStep.jsx:259
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:116
+msgid "Shhh..."
+msgstr "Shhh..."
+
+#: frontend/src/metabase/setup/components/UserStep.jsx:269
+msgid "Confirm password"
+msgstr "Confirma la contraseña"
+
+#: frontend/src/metabase/setup/components/UserStep.jsx:278
+msgid "Shhh... but one more time so we get it right"
+msgstr "Shhh... otra vez, para asegurarnos que lo hacemos bien"
+
+#: frontend/src/metabase/setup/components/UserStep.jsx:287
+msgid "Your company or team name"
+msgstr "El nombre de tu empresa o equipo"
+
+#: frontend/src/metabase/setup/components/UserStep.jsx:296
+msgid "Department of awesome"
+msgstr "Departamento impresionante"
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:26
+msgid "Metabot is admiring your integers…"
+msgstr "Metabot está admirando tus enteros…"
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:27
+msgid "Metabot is performing billions of differential equations…"
+msgstr "Metabot está realizando miles de millones de ecuaciones diferenciales…"
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:28
+msgid "Metabot is doing science…"
+msgstr "Metabot está haciendo ciencia…"
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:29
+msgid "Metabot is checking out your metrics…"
+msgstr "Metabot está revisando tus métricas…"
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:30
+msgid "Metabot is looking for trends and outliers…"
+msgstr "Metabot está buscando tendencias y valores atípicos…"
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:31
+msgid "Metabot is consulting the quantum abacus…"
+msgstr "Metabot está consultando el ábaco cuántico…"
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:32
+msgid "Metabot is feeling pretty good about all this…"
+msgstr "Metabot se siente bien con todo esto…"
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:52
+msgid "We’ll show you some interesting explorations of your data in\n"
+"just a few minutes."
+msgstr "Te mostraremos algunas exploraciones interesantes de tus datos\n"
+"en solo unos minutos."
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:72
+msgid "This seems to be taking a while. In the meantime, you can check out one of these example explorations to see what Metabase can do for you."
+msgstr "Esto parece que está tardando un tiempo. Mientras tanto, puedes ver una de estas exploraciones de ejemplo para ver lo qué Metabase puede hacer por ti."
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:86
+msgid "I took a look at the data you just connected, and I have some explorations of interesting things I found. Hope you like them!"
+msgstr "He revisado los datos que acabas de conectar, y tengo algunas exploraciones de cosas interesantes que he encontrado. ¡Espero que te gusten!"
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:98
+msgid "I'm done exploring for now"
+msgstr "He terminado de explorar por ahora"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:20
+msgid "Welcome to the Query Builder!"
+msgstr "Bienvenido al Generador de Consultas!"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:22
+msgid "The Query Builder lets you assemble questions (or \"queries\") to ask about your data."
+msgstr "El generador de consultas te permite construir preguntas (o \"consultas\") para indagar sobre tus datos."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:26
+msgid "Tell me more"
+msgstr "Dime más"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:43
+msgid "Start by picking the table with the data that you have a question about."
+msgstr "Empieza escogiendo la tabla con los datos sobre los que tienes una pregunta."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:45
+msgid "Go ahead and select the \"Orders\" table from the dropdown menu."
+msgstr "Adelante y selecciona la tabla  \"Pedidos\" en el menú desplegable."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:78
+msgid "Filter your data to get just what you want."
+msgstr "Filtra tus datos para obtener exactamente lo que quieres."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:79
+msgid "Click the plus button and select the \"Created At\" field."
+msgstr "Haz clic en el botón más y selecciona el campo \"Created At\"."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:93
+msgid "Here we can pick how many days we want to see data for, try 10"
+msgstr "Aquí podemos elegir cuántos días queremos ver los datos, intenta con 10"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:116
+msgid "Here's where you can choose to add or average your data, count the number of rows in the table, or just view the raw data."
+msgstr "Aquí es donde puedes elegir agregar o promediar tus datos, contar el número de filas en la tabla, o simplemente ver los datos sin procesar."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:118
+msgid "Try it: click on <strong>Raw Data</strong> to change it to <strong>Count of rows</strong> so we can count how many orders there are in this table."
+msgstr "Pruébalo: haz clic en <strong>Datos brutos</ strong> para cambiarlo a <strong>Número de filas</ strong> para que podamos contar cuántas órdenes hay en esta tabla."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:142
+msgid "Add a grouping to break out your results by category, day, month, and more."
+msgstr "Añade una agrupación para dividir tus resultados por categoría, día, mes y más."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:144
+msgid "Let's do it: click on <strong>Add a grouping</strong>, and choose <strong>Created At: by Week</strong>."
+msgstr "Hagámoslo: haz clic en <strong>Añadir una agrupación</ strong> y elige <strong>Created At: por semana</ strong>."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:152
+msgid "Click on \"by day\" to change it to \"Week.\""
+msgstr "Haz clic en \"por día\" para cambiarlo a \"Semana\"."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:173
+msgid "Run Your Query."
+msgstr "Ejecuta tu consulta."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:175
+msgid "You're doing so well! Click <strong>Run query</strong> to get your results!"
+msgstr "¡Lo estás haciendo muy bien! Haz clic en <strong>Ejecutar consulta</ strong> para obtener tus resultados."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:192
+msgid "You can view your results as a chart instead of a table."
+msgstr "Puedes ver sus resultados como un gráfico en lugar de una tabla."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:194
+msgid "Everbody likes charts! Click the <strong>Visualization</strong> dropdown and select <strong>Line</strong>."
+msgstr "A todos nos gustan las gráficas! Haz clic en el menú desplegable <strong>Visualización</ strong> y selecciona <strong>Línea</ strong>."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:216
+msgid "Well done!"
+msgstr "Muy bien!"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:218
+msgid "That's all! If you still have questions, check out our"
+msgstr "¡Eso es todo! Si aún tienes preguntas, revisas nuestra"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:223
+msgid "User's Guide"
+msgstr "Guía del Usuario"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:223
+msgid "Have fun exploring your data!"
+msgstr "¡Diviértete explorando tus datos!"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:226
+msgid "Thanks"
+msgstr "Gracias"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:235
+msgid "Save Your Questions"
+msgstr "Guarda tus preguntas"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:237
+msgid "By the way, you can save your questions so you can refer to them later. Saved Questions can also be put into dashboards or Pulses."
+msgstr "Por cierto, puedes guardar tus preguntas para que puedas consultarlas más tarde.Las preguntas guardadas también se pueden poner en cuadros de mando o pulsos."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:241
+msgid "Sounds good"
+msgstr "Suena bien"
+
+#: frontend/src/metabase/tutorial/Tutorial.jsx:248
+msgid "Whoops!"
+msgstr "uy!!"
+
+#: frontend/src/metabase/tutorial/Tutorial.jsx:249
+msgid "Sorry, it looks like something went wrong. Please try restarting the tutorial in a minute."
+msgstr "Lo siento, parece que algo salió mal. Intenta reiniciar el tutorial en un minuto."
+
+#: frontend/src/metabase/user/actions.js:34
+msgid "Password updated successfully!"
+msgstr "¡Contraseña actualizada correctamente!"
+
+#: frontend/src/metabase/user/actions.js:53
+msgid "Account updated successfully!"
+msgstr "Cuenta actualizada con éxito!"
+
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:107
+msgid "Current password"
+msgstr "Contraseña actual"
+
+#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:137
+msgid "Sign in with Google Email address"
+msgstr "Inicia sesión con la dirección de correo electrónico de Google"
+
+#: frontend/src/metabase/user/components/UserSettings.jsx:65
+msgid "User Details"
+msgstr "Detalles Usuario"
+
+#: frontend/src/metabase/visualizations/components/ChartSettings.jsx:225
+msgid "Reset to defaults"
+msgstr "Restablecer los valores predeterminados"
+
+#: frontend/src/metabase/visualizations/components/ChoroplethMap.jsx:123
+msgid "unknown map"
+msgstr "mapa desconocido"
+
+#: frontend/src/metabase/visualizations/components/LeafletGridHeatMap.jsx:26
+msgid "Grid map requires binned longitude/latitude."
+msgstr "El mapa de cuadrícula requiere longitud/latitud agrupada."
+
+#: frontend/src/metabase/visualizations/components/LegendVertical.jsx:112
+msgid "more"
+msgstr "más"
+
+#: frontend/src/metabase/visualizations/components/LineAreaBarChart.jsx:101
+msgid "Which fields do you want to use for the X and Y axes?"
+msgstr "¿Qué campos quieres usar para los ejes X e Y?"
+
+#: frontend/src/metabase/visualizations/components/LineAreaBarChart.jsx:103
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:59
+msgid "Choose fields"
+msgstr "Elige campos"
+
+#: frontend/src/metabase/visualizations/components/PinMap.jsx:204
+msgid "Save as default view"
+msgstr "Guardar como vista predeterminada"
+
+#: frontend/src/metabase/visualizations/components/PinMap.jsx:226
+msgid "Draw box to filter"
+msgstr "Dibujar cuadro para filtrar"
+
+#: frontend/src/metabase/visualizations/components/PinMap.jsx:226
+msgid "Cancel filter"
+msgstr "Cancelar Filtro"
+
+#: frontend/src/metabase/visualizations/components/PinMap.jsx:47
+msgid "Pin Map"
+msgstr "Mapa de Pin"
+
+#: frontend/src/metabase/visualizations/components/TableInteractive.jsx:423
+msgid "Unset"
+msgstr "Desmarcar"
+
+#: frontend/src/metabase/visualizations/components/TableSimple.jsx:227
+msgid "Rows {0}-{1} of {2}"
+msgstr "Filas {0}-{1} de {2}"
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:184
+msgid "Data truncated to {0} rows."
+msgstr "Datos truncados a {0} filas."
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:349
+msgid "Could not find visualization"
+msgstr "No se pudo encontrar la visualización"
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:356
+msgid "Could not display this chart with this data."
+msgstr "No se pudo mostrar este gráfico con esta información."
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:454
+msgid "No results!"
+msgstr "Sin resultados!"
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:475
+msgid "Still Waiting..."
+msgstr "Esperando..."
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:478
+msgid "This usually takes an average of {0}."
+msgstr "Esto suele tardar unos {0}."
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:484
+msgid "(This is a bit long for a dashboard)"
+msgstr "(Es un poco largo para un cuadro de mando)"
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:488
+msgid "This is usually pretty fast but seems to be taking awhile right now."
+msgstr "Esto suele ser bastante rápido, pero parece estar tardando en este momento."
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingFieldPicker.jsx:14
+msgid "Select a field"
+msgstr "Selecciona un campo"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingFieldsPicker.jsx:42
+msgid "error"
+msgstr "error"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingOrderedColumns.jsx:107
+msgid "Click and drag to change their order"
+msgstr "Pulsa y arrastra para cambiar el orden"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingOrderedColumns.jsx:119
+msgid "Add fields from the list below"
+msgstr "Añade campos de la lista inferior"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:24
+msgid "less than"
+msgstr "menor que"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:25
+msgid "greater than"
+msgstr "mayor que"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:26
+msgid "less than or equal to"
+msgstr "menor o igual a"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:27
+msgid "greater than or equal to"
+msgstr "nayor o igual a"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:28
+msgid "equal to"
+msgstr "igual a"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:29
+msgid "not equal to"
+msgstr "distinto a"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:169
+msgid "Conditional formatting"
+msgstr "Formato condicional"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:171
+msgid "You can add rules to make the cells in this table change color if\n"
+"they meet certain conditions."
+msgstr "Puedes añadir reglas para hacer que las celdas de esta tabla cambien\n"
+"de color si cumplen ciertas condiciones."
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:181
+msgid "Add a rule"
+msgstr "Añade una regla"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:186
+msgid "Rules will be applied in this order"
+msgstr "Las reglas se aplicarán en este orden"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:187
+msgid "Click and drag to reorder."
+msgstr "Pulsa y arrastra para cambiar el orden"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:220
+msgid "No columns selected"
+msgstr "Ninguna columna seleccionada"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:277
+msgid "Cells in this column will be tinted based on their values."
+msgstr "Las celdas en esta columna se colorearán según sus valores."
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:279
+msgid "When a cell in these columns is {0} it will be tinted this color."
+msgstr "Cuando una celda en estas columnas es {0}, estará teñida de este color."
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:290
+msgid "Which columns should be affected?"
+msgstr "¿A qué columnas se aplica?"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:300
+msgid "Formatting style"
+msgstr "Estilo de formato"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:304
+msgid "Single color"
+msgstr "Color único"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:305
+msgid "Color range"
+msgstr "Rango de Color"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:312
+msgid "When a cell in this column is…"
+msgstr "Cuando una celda en esta columna es…"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:325
+msgid "…turn its background this color:"
+msgstr "…pinta el fondo de este color:"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:331
+msgid "Highlight the whole row"
+msgstr "Resalta la fila entera"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:339
+msgid "Colors"
+msgstr "Colores"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:344
+msgid "Start the range at"
+msgstr "Empieza el rango en"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:349
+msgid "Smallest value in this column"
+msgstr "El valor más pequeño en esta columna"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:351
+msgid "Smallest value in each column"
+msgstr "El valor más pequeño en cada columna"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:353
+msgid "Smallest value in all of these columns"
+msgstr "El valor más pequeño en todas estas columnas"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:357
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:379
+msgid "Custom value"
+msgstr "Valor personalizado"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:366
+msgid "End the range at"
+msgstr "Finaliza el rango en"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:371
+msgid "Largest value in this column"
+msgstr "El valor más grande en esta columna"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:373
+msgid "Largest value in each column"
+msgstr "El valor más grande en cada columna"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:375
+msgid "Largest value in all of these columns"
+msgstr "El valor más grande en todas estas columnas"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:407
+msgid "Add rule"
+msgstr "Añadir regla"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:407
+msgid "Update rule"
+msgstr "Actualizar regla"
+
+#: frontend/src/metabase/visualizations/index.js:30
+msgid "Visualization is null"
+msgstr "Visualización Vacía"
+
+#: frontend/src/metabase/visualizations/index.js:35
+msgid "Visualization must define an 'identifier' static variable: "
+msgstr "La visualización debe definir una variable estática 'identificadora':"
+
+#: frontend/src/metabase/visualizations/index.js:41
+msgid "Visualization with that identifier is already registered: "
+msgstr "La visualización con ese identificador ya está registrada:"
+
+#: frontend/src/metabase/visualizations/index.js:69
+msgid "No visualization for {0}"
+msgstr "No hay visualización para {0}"
+
+#: frontend/src/metabase/visualizations/lib/LineAreaBarRenderer.js:71
+msgid "\"{0}\" is an unaggregated field: if it has more than one value at a point on the x-axis, the values will be summed."
+msgstr "\"{0}\" es un campo no agregado: si tienes más de un valor en un punto en el eje X, los valores se sumarán."
+
+#: frontend/src/metabase/visualizations/lib/LineAreaBarRenderer.js:87
+msgid "This chart type requires at least 2 columns."
+msgstr "Este tipo de gráfico requiere al menos 2 columnas."
+
+#: frontend/src/metabase/visualizations/lib/LineAreaBarRenderer.js:92
+msgid "This chart type doesn't support more than {0} series of data."
+msgstr "Este tipo de gráfico no admite más de {0} series de datos."
+
+#: frontend/src/metabase/visualizations/lib/LineAreaBarRenderer.js:509
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:42
+msgid "Goal"
+msgstr "Objetivo"
+
+#: frontend/src/metabase/visualizations/lib/errors.js:9
+msgid "Doh! The data from your query doesn't fit the chosen display choice. This visualization requires at least {0} {1} of data."
+msgstr "Vaya! Los datos de tu consulta no se ajustan a la opción de visualización elegida. Esta visualización requiere al menos {0} {1} de datos."
+
+#: frontend/src/metabase/visualizations/lib/errors.js:9
+msgid "column"
+msgid_plural "columns"
+msgstr[0] "columna"
+msgstr[1] "columnas"
+
+#: frontend/src/metabase/visualizations/lib/errors.js:21
+msgid "No dice. We have {0} data {1} to show and that's not enough for this visualization."
+msgstr "No hay suerte. Tenemos {0} datos {1} para mostrar pero no es suficiente para esta visualización."
+
+#: frontend/src/metabase/visualizations/lib/errors.js:21
+msgid "point"
+msgid_plural "points"
+msgstr[0] "punto"
+msgstr[1] "puntos"
+
+#: frontend/src/metabase/visualizations/lib/errors.js:33
+msgid "Bummer. We can't actually do a pin map for this data because we require both a latitude and longitude column."
+msgstr "Vaya. No podemos hacer un mapa de pin para estos datos porque necesitamos una columna de latitud y longitud."
+
+#: frontend/src/metabase/visualizations/lib/errors.js:42
+msgid "Please configure this chart in the chart settings"
+msgstr "Por favor, configura este gráfico en la configuración del gráfico"
+
+#: frontend/src/metabase/visualizations/lib/errors.js:44
+msgid "Edit Settings"
+msgstr "Editar Configuración"
+
+#: frontend/src/metabase/visualizations/lib/fill_data.js:38
+msgid "xValues missing!"
+msgstr "Faltan valores x!"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:56
+#: frontend/src/metabase/visualizations/visualizations/RowChart.jsx:31
+msgid "X-axis"
+msgstr "Eje-X"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:82
+msgid "Add a series breakout..."
+msgstr "Añade un desglose de series..."
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:93
+#: frontend/src/metabase/visualizations/visualizations/RowChart.jsx:35
+msgid "Y-axis"
+msgstr "Eje-Y"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:118
+msgid "Add another series..."
+msgstr "Añade otra serie..."
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:132
+msgid "Bubble size"
+msgstr "Tamaño de burbuja"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:161
+#: frontend/src/metabase/visualizations/visualizations/LineChart.jsx:17
+msgid "Line"
+msgstr "Línea"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:162
+msgid "Curve"
+msgstr "Curva"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:163
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:67
+msgid "Step"
+msgstr "Paso"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:170
+msgid "Show point markers on lines"
+msgstr "Mostrar marcadores de puntos en líneas"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:178
+msgid "Stacking"
+msgstr "Apilado"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:182
+msgid "Don't stack"
+msgstr "No Apilado"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:183
+msgid "Stack"
+msgstr "Apilar"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:184
+msgid "Stack - 100%"
+msgstr "Apilado - 100%"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:201
+msgid "Show goal"
+msgstr "Mostrar objetivo"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:207
+msgid "Goal value"
+msgstr "Valor del Objetivo"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:218
+msgid "Replace missing values with"
+msgstr "Reemplazar los valores faltantes con"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:223
+msgid "Zero"
+msgstr "Cero"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:224
+msgid "Nothing"
+msgstr "Nada"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:225
+msgid "Linear Interpolated"
+msgstr "Lineal Interpolado"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:284
+msgid "X-axis scale"
+msgstr "Escala Eje-X"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:301
+msgid "Timeseries"
+msgstr "Series de tiempo"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:304
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:322
+msgid "Linear"
+msgstr "Lineal"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:306
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:323
+msgid "Power"
+msgstr "Exponente"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:307
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:324
+msgid "Log"
+msgstr "Logarítmico"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:309
+msgid "Histogram"
+msgstr "Histograma"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:311
+msgid "Ordinal"
+msgstr "Ordinal"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:317
+msgid "Y-axis scale"
+msgstr "Escala Eje-Y"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:330
+msgid "Show x-axis line and marks"
+msgstr "Mostrar línea y marcas del Eje-X"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:336
+msgid "Compact"
+msgstr "Compacto"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:337
+msgid "Rotate 45°"
+msgstr "Rotar 45°"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:338
+msgid "Rotate 90°"
+msgstr "Rotar 90°"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:345
+msgid "Show y-axis line and marks"
+msgstr "Mostrar línea y marcas del Eje-Y"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:357
+msgid "Auto y-axis range"
+msgstr "Rango del Eje-Y automático"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:401
+msgid "Use a split y-axis when necessary"
+msgstr "Utiliza un Eje-Y dividido cuando sea necesario"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:408
+msgid "Show label on x-axis"
+msgstr "Mostrar etiqueta en el Eje-X"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:414
+msgid "X-axis label"
+msgstr "Etiqueta Eje-X"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:423
+msgid "Show label on y-axis"
+msgstr "Mostrar etiqueta en el Eje-Y"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:429
+msgid "Y-axis label"
+msgstr "Etiqueta Eje-Y"
+
+#: frontend/src/metabase/visualizations/lib/utils.js:116
+msgid "Standard Deviation"
+msgstr "Desviación Estándar"
+
+#: frontend/src/metabase/visualizations/visualizations/AreaChart.jsx:18
+msgid "Area"
+msgstr "Area"
+
+#: frontend/src/metabase/visualizations/visualizations/AreaChart.jsx:21
+msgid "area chart"
+msgstr "Gráfico de área"
+
+#: frontend/src/metabase/visualizations/visualizations/BarChart.jsx:16
+msgid "Bar"
+msgstr "Barra"
+
+#: frontend/src/metabase/visualizations/visualizations/BarChart.jsx:19
+msgid "bar chart"
+msgstr "Gráfico de barras"
+
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:57
+msgid "Which fields do you want to use?"
+msgstr "¿Qué campos quieres usar?"
+
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:31
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:85
+msgid "Funnel"
+msgstr "Embudo"
+
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:74
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:67
+msgid "Measure"
+msgstr "Medida"
+
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:80
+msgid "Funnel type"
+msgstr "Tipo Embudo"
+
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:86
+msgid "Bar chart"
+msgstr "Gráfico de barras"
+
+#: frontend/src/metabase/visualizations/visualizations/LineChart.jsx:20
+msgid "line chart"
+msgstr "Gráfico de líneas"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:211
+msgid "Please select longitude and latitude columns in the chart settings."
+msgstr "Selecciona las columnas de longitud y latitud en la configuración del gráfico."
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:217
+msgid "Please select a region map."
+msgstr "Por favor, selecciona un mapa de la región."
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:221
+msgid "Please select region and metric columns in the chart settings."
+msgstr "Selecciona las columnas de región y métricas en la configuración del gráfico."
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:31
+msgid "Map"
+msgstr "Mapa"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:45
+msgid "Map type"
+msgstr "Tipo mapa"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:49
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:146
+msgid "Region map"
+msgstr "Mapa de la región"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:50
+msgid "Pin map"
+msgstr "Mapa de pin"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:96
+msgid "Pin type"
+msgstr "Tipo Pin"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:101
+msgid "Tiles"
+msgstr "Baldosas"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:102
+msgid "Markers"
+msgstr "Marcadores"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:118
+msgid "Latitude field"
+msgstr "Campo Latitud"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:128
+msgid "Longitude field"
+msgstr "Campo Longitud"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:138
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:165
+msgid "Metric field"
+msgstr "Campo Métrica"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:170
+msgid "Region field"
+msgstr "Campo Región"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:179
+msgid "Radius"
+msgstr "Radio"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:185
+msgid "Blur"
+msgstr "Difuminar"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:191
+msgid "Min Opacity"
+msgstr "Opacidad Mínima"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:197
+msgid "Max Zoom"
+msgstr "Acercamiento Máximo"
+
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:175
+msgid "No relationships found."
+msgstr "No se encontraron relaciones."
+
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:213
+msgid "via {0}"
+msgstr "via {0}"
+
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:290
+msgid "This {0} is connected to:"
+msgstr "Esta {0} está conectada a:"
+
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:47
+msgid "Object Detail"
+msgstr "Detalle del Objeto"
+
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:50
+msgid "object"
+msgstr "objeto"
+
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:254
+msgid "Total"
+msgstr "Total"
+
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:53
+msgid "Which columns do you want to use?"
+msgstr "¿Qué columnas quieres usar?"
+
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:40
+msgid "Pie"
+msgstr "Pastel"
+
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:62
+msgid "Dimension"
+msgstr "Dimensión"
+
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:72
+msgid "Show legend"
+msgstr "Mostrar Leyenda"
+
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:77
+msgid "Show percentages in legend"
+msgstr "Mostrar porcentajes en la leyenda"
+
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:83
+msgid "Minimum slice percentage"
+msgstr "Porcentaje mínimo de porción"
+
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:136
+msgid "Goal met"
+msgstr "Objetivo conseguido"
+
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:138
+msgid "Goal exceeded"
+msgstr "Objetivo superado"
+
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:208
+msgid "Goal {0}"
+msgstr "Objetivo {0}"
+
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:35
+msgid "Progress visualization requires a number."
+msgstr "La visualización del progreso requiere un número."
+
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:23
+msgid "Progress"
+msgstr "Progreso"
+
+#: frontend/src/metabase/entities/collections.js:101
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:48
+msgid "Color"
+msgstr "Color"
+
+#: frontend/src/metabase/visualizations/visualizations/RowChart.jsx:13
+msgid "Row Chart"
+msgstr "Gráfico de Filas"
+
+#: frontend/src/metabase/visualizations/visualizations/RowChart.jsx:16
+msgid "row chart"
+msgstr "gráfico de filas"
+
+#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:75
+msgid "Separator style"
+msgstr "Estilo separador"
+
+#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:88
+msgid "Number of decimal places"
+msgstr "Número de decimales"
+
+#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:92
+msgid "Add a prefix"
+msgstr "Añade un prefijo"
+
+#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:96
+msgid "Add a suffix"
+msgstr "Añade un sufijo"
+
+#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:100
+msgid "Multiply by a number"
+msgstr "Multiplicar por un número"
+
+#: frontend/src/metabase/visualizations/visualizations/ScatterPlot.jsx:16
+msgid "Scatter"
+msgstr "Dispersión"
+
+#: frontend/src/metabase/visualizations/visualizations/ScatterPlot.jsx:19
+msgid "scatter plot"
+msgstr "gráfico de dispersión"
+
+#: frontend/src/metabase/visualizations/visualizations/Table.jsx:61
+msgid "Pivot the table"
+msgstr "Pivote la tabla"
+
+#: frontend/src/metabase/visualizations/visualizations/Table.jsx:73
+msgid "Visible fields"
+msgstr "Campos visibles"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:167
+msgid "Write here, and use Markdown if you''d like"
+msgstr "Escribe aquí. Puedes utilizar Markdown"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:73
+msgid "Vertical Alignment"
+msgstr "Alineamiento Vertical"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:77
+msgid "Top"
+msgstr "Arriba:"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:78
+msgid "Middle"
+msgstr "Medio"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:79
+msgid "Bottom"
+msgstr "Abajo"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:86
+msgid "Horizontal Alignment"
+msgstr "Alineamiento Horizontal"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:90
+msgid "Left"
+msgstr "Izquierda"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:91
+msgid "Center"
+msgstr "Centro"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:92
+msgid "Right"
+msgstr "Derecho"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:99
+msgid "Show background"
+msgstr "Mostrar fondo"
+
+#: frontend/src/metabase-lib/lib/Dimension.js:497
+msgid "{0} bin"
+msgid_plural "{0} bins"
+msgstr[0] "{0} agrupación"
+msgstr[1] "{0} agrupaciones"
+
+#: frontend/src/metabase-lib/lib/Dimension.js:503
+msgid "Auto binned"
+msgstr "Agrupación Auto"
+
+#: src/metabase/api/alert.clj
+msgid "DELETE /api/alert/:id is deprecated. Instead, change its `archived` value via PUT /api/alert/:id."
+msgstr "DELETE /api/alert/:id esta descontinuado. Cambia el valor de `archived` via PUT /api/alert/:id."
+
+#: src/metabase/api/automagic_dashboards.clj
+msgid "invalid show value"
+msgstr "valor no válido para el campo mostrar"
+
+#: src/metabase/api/automagic_dashboards.clj
+msgid "invalid value for prefix"
+msgstr "valor no válido para el prefijo"
+
+#: src/metabase/api/automagic_dashboards.clj
+msgid "invalid value for rule name"
+msgstr "valor no válido para nombre de regla"
+
+#: src/metabase/api/automagic_dashboards.clj
+msgid "value couldn''t be parsed as base64 encoded JSON"
+msgstr "valor no se pudo analizar como JSON codificado base64"
+
+#: src/metabase/api/automagic_dashboards.clj
+msgid "Invalid entity type"
+msgstr "Tipo de entidad inválida"
+
+#: src/metabase/api/automagic_dashboards.clj
+msgid "Invalid comparison entity type. Can only be one of \"table\", \"segment\", or \"adhoc\""
+msgstr "Tipo de entidad de comparación inválida. Solo puede ser \"tabla\", \"segmento\" o \"adhoc\""
+
+#: src/metabase/api/card.clj
+msgid "Error running query to determine Card result metadata:"
+msgstr "Error determinando el tipo de resultado de la tarjeta"
+
+#: src/metabase/api/card.clj
+msgid "DELETE /api/card/:id is deprecated. Instead, change its `archived` value via PUT /api/card/:id."
+msgstr "DELETE /api/card/:id esta descontinuado. Cambia el valor de `archived` via PUT /api/card/:id."
+
+#: src/metabase/api/common.clj src/metabase/api/common/internal.clj
+msgid "Invalid field: {0}"
+msgstr "Campo inválido: {0}"
+
+#: src/metabase/api/common.clj
+msgid "Invalid value ''{0}'' for ''{1}'': {2}"
+msgstr "Valor inválido ''{0}'' para ''{1}'': {2}"
+
+#: src/metabase/api/common.clj
+msgid "must be one of: {0}"
+msgstr "debe ser uno de: {0}"
+
+#: src/metabase/api/common.clj
+msgid "Invalid Request."
+msgstr "Petición inválida."
+
+#: src/metabase/api/common.clj
+msgid "Not found."
+msgstr "No encontrado."
+
+#: src/metabase/api/common.clj
+msgid "You don''t have permissions to do that."
+msgstr "Lo siento, no tienes permiso para hacer eso."
+
+#: src/metabase/api/common.clj
+msgid "Internal server error."
+msgstr "Error de servidor interno."
+
+#: src/metabase/api/common.clj
+msgid "Warning: endpoint {0}/{1} does not have a docstring."
+msgstr "Aviso: la llamada {0}/{1} no tiene un docstring"
+
+#: src/metabase/api/common.clj
+msgid "starting streaming request"
+msgstr "iniciando streaming"
+
+#: src/metabase/api/common.clj
+msgid "connection closed, canceling request"
+msgstr "iniciando la solicitud de transmisión"
+
+#. a newline padding character as it's harmless and will allow us to check if the client is connected. If
+#. sending this character fails because the connection is closed, the chan will then close.  Newlines are
+#. no-ops when reading JSON which this depends upon.
+#: src/metabase/api/common.clj
+msgid "Response not ready, writing one byte & sleeping..."
+msgstr "La respuesta no está lista, escribiendo un byte y esperando..."
+
+#: src/metabase/api/common.clj
+msgid "Public sharing is not enabled."
+msgstr "La compartición pública no está habilitada."
+
+#: src/metabase/api/common.clj
+msgid "Embedding is not enabled."
+msgstr "La incrustación no está habilitada."
+
+#: src/metabase/api/common.clj
+msgid "The object has been archived."
+msgstr "El objeto ha sido archivado."
+
+#: src/metabase/api/common/internal.clj
+msgid "Attempted to return a boolean as an API response. This is not allowed!"
+msgstr "Se ha intentado devolver un booleano como respuesta de API. ¡Esto no esta permitido!"
+
+#: src/metabase/api/dataset.clj
+msgid "Source query for this query is Card {0}"
+msgstr "La consulta base de esta consulta es Tarjeta {0}"
+
+#: src/metabase/api/dataset.clj
+msgid "Invalid export format: {0}"
+msgstr "Formato de exportación inválido: {0}"
+
+#: src/metabase/api/geojson.clj
+msgid "Invalid JSON URL or resource: {0}"
+msgstr "Recurso o URL JSON no válido: {0}"
+
+#: src/metabase/api/geojson.clj
+msgid "JSON containing information about custom GeoJSON files for use in map visualizations instead of the default US State or World GeoJSON."
+msgstr "JSON que contiene información sobre archivos GeoJSON personalizados para su uso en visualizaciones de mapas en lugar del GeoJSON por defecto de EEUU o el Mundo."
+
+#: src/metabase/api/geojson.clj
+msgid "Invalid custom GeoJSON key: {0}"
+msgstr "Clave personalizada GeoJSON inválida: {0}"
+
+#. ...but if we *still* couldn't find a match, throw an Exception, because we don't want people
+#. trying to inject new params
+#: src/metabase/api/public.clj
+msgid "Invalid param: {0}"
+msgstr "Parámetro inválido: {0}"
+
+#: src/metabase/api/pulse.clj
+msgid "DELETE /api/pulse/:id is deprecated. Instead, change its `archived` value via PUT /api/pulse/:id."
+msgstr "DELETE /api/pulse/:id esta descontinuado. Cambia el valor de `archived` via PUT /api/pulse/:id."
+
+#: src/metabase/api/routes.clj
+msgid "API endpoint does not exist."
+msgstr "No existe este servicio API"
+
+#: src/metabase/api/session.clj
+msgid "Password did not match stored password."
+msgstr "La contraseña no coincide con la almacenada"
+
+#: src/metabase/api/session.clj
+msgid "did not match stored password"
+msgstr "no coincide con la contraseña almacenada"
+
+#: src/metabase/api/session.clj
+msgid "Problem connecting to LDAP server, will fallback to local authentication {0}"
+msgstr "Problema al conectar con el servidor LDAP, se recurrirá a la autentificación local {0}"
+
+#: src/metabase/api/session.clj
+msgid "Invalid reset token"
+msgstr "Token de reinicio inválido"
+
+#: src/metabase/api/session.clj
+msgid "Client ID for Google Auth SSO. If this is set, Google Auth is considered to be enabled."
+msgstr "ID de cliente para Google Auth SSO. Si esto está configurado, Google Auth se considera habilitado."
+
+#: src/metabase/api/session.clj
+msgid "When set, allow users to sign up on their own if their Google account email address is from this domain."
+msgstr "Permite que los usuarios se registren solos si la dirección de correo electrónico de su cuenta de Google es de este dominio."
+
+#: src/metabase/api/session.clj
+msgid "Invalid Google Auth token."
+msgstr "Token de autenticación de Google no válido"
+
+#: src/metabase/api/session.clj
+msgid "Email is not verified."
+msgstr "Correo no verificado."
+
+#: src/metabase/api/session.clj
+msgid "You''ll need an administrator to create a Metabase account before you can use Google to log in."
+msgstr "Necesitarás un administrador para crear una cuenta Metabase antes de poder usar Google para iniciar sesión."
+
+#: src/metabase/api/session.clj
+msgid "Successfully authenticated Google Auth token for: {0} {1}"
+msgstr "Se ha autentificado con éxito el token de Google Auth para: {0} {1}"
+
+#: src/metabase/api/setup.clj
+msgid "Add a database"
+msgstr "Añade una Base de Datos"
+
+#: src/metabase/api/setup.clj
+msgid "Get connected"
+msgstr "Conectarse"
+
+#: src/metabase/api/setup.clj
+msgid "Connect to your data so your whole team can start to explore."
+msgstr "Conecta tus datos para que todo el equipo pueda comenzar a explorar."
+
+#: src/metabase/api/setup.clj
+msgid "Set up email"
+msgstr "Configura el email"
+
+#: src/metabase/api/setup.clj
+msgid "Add email credentials so you can more easily invite team members and get updates via Pulses."
+msgstr "Añade credenciales de correo electrónico para que puedas invitar más fácilmente a los miembros del equipo y obtener actualizaciones a través de Pulsos."
+
+#: src/metabase/api/setup.clj
+msgid "Set Slack credentials"
+msgstr "Establecer credenciales de Slack"
+
+#: src/metabase/api/setup.clj
+msgid "Does your team use Slack? If so, you can send automated updates via pulses and ask questions with MetaBot."
+msgstr "¿Tu equipo usa Slack? Si es así, puedes enviar actualizaciones automáticas por pulsos y hacer preguntas con MedaBot."
+
+#: src/metabase/api/setup.clj
+msgid "Invite team members"
+msgstr "Invitar a miembros del equipo"
+
+#: src/metabase/api/setup.clj
+msgid "Share answers and data with the rest of your team."
+msgstr "Comparte respuestas y datos con el resto de tu equipo."
+
+#: src/metabase/api/setup.clj
+msgid "Hide irrelevant tables"
+msgstr "Ocultar tablas irrelevantes"
+
+#: src/metabase/api/setup.clj
+msgid "Curate your data"
+msgstr "Mima tus datos"
+
+#: src/metabase/api/setup.clj
+msgid "If your data contains technical or irrelevant info you can hide it."
+msgstr "Si tus datos contienen información técnica o irrelevante, puedes ocultarla."
+
+#: src/metabase/api/setup.clj
+msgid "Organize questions"
+msgstr "Organizar preguntas"
+
+#: src/metabase/api/setup.clj
+msgid "Have a lot of saved questions in {0}? Create collections to help manage them and add context."
+msgstr "¿Tienes muchas preguntas guardadas en {0}? Crea colecciones para ayudar a administrarlas y añadir contexto."
+
+#. 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
+#: src/metabase/api/setup.clj
+msgid "Metabase"
+msgstr "Metabase"
+
+#: src/metabase/api/setup.clj
+msgid "Create metrics"
+msgstr "Crear métricas"
+
+#: src/metabase/api/setup.clj
+msgid "Define canonical metrics to make it easier for the rest of your team to get the right answers."
+msgstr "Define métricas canónicas para que el resto de tu equipo obtenga las respuestas correctas."
+
+#: src/metabase/api/setup.clj
+msgid "Create segments"
+msgstr "Crear segmentos"
+
+#: src/metabase/api/setup.clj
+msgid "Keep everyone on the same page by creating canonical sets of filters anyone can use while asking questions."
+msgstr "Mantiene a todos en la misma página mediante la creación de conjuntos canónicos de filtros que cualquier persona puede usar al hacer preguntas."
+
+#: src/metabase/api/table.clj
+msgid "Table ''{0}'' is now visible. Resyncing."
+msgstr "Ha aparecido la tabla ''{0}''. Sincronizando."
+
+#: src/metabase/api/table.clj
+msgid "Auto bin"
+msgstr "Agrupación Auto"
+
+#: src/metabase/api/table.clj
+msgid "Don''t bin"
+msgstr "Sin Agrupación"
+
+#: src/metabase/api/table.clj
+msgid "Day"
+msgstr "Día"
+
+#. note the order of these options corresponds to the order they will be shown to the user in the UI
+#: src/metabase/api/table.clj
+msgid "Minute"
+msgstr "Minuto"
+
+#: src/metabase/api/table.clj
+msgid "Hour"
+msgstr "Hora"
+
+#: src/metabase/api/table.clj
+msgid "Quarter"
+msgstr "Trimestre"
+
+#: src/metabase/api/table.clj
+msgid "Minute of Hour"
+msgstr "Minuto de Hora"
+
+#: src/metabase/api/table.clj
+msgid "Hour of Day"
+msgstr "Hora del Día"
+
+#: src/metabase/api/table.clj
+msgid "Day of Week"
+msgstr "Día de la Semana"
+
+#: src/metabase/api/table.clj
+msgid "Day of Month"
+msgstr "Día del Mes"
+
+#: src/metabase/api/table.clj
+msgid "Day of Year"
+msgstr "Día del Año"
+
+#: src/metabase/api/table.clj
+msgid "Week of Year"
+msgstr "Semana del Año"
+
+#: src/metabase/api/table.clj
+msgid "Month of Year"
+msgstr "Mes del Año"
+
+#: src/metabase/api/table.clj
+msgid "Quarter of Year"
+msgstr "Trimestre del Año"
+
+#: src/metabase/api/table.clj
+msgid "10 bins"
+msgstr "10 agrupaciones"
+
+#: src/metabase/api/table.clj
+msgid "50 bins"
+msgstr "50 agrupaciones"
+
+#: src/metabase/api/table.clj
+msgid "100 bins"
+msgstr "100 agrupaciones"
+
+#: src/metabase/api/table.clj
+msgid "Bin every 0.1 degrees"
+msgstr "Agrupa cada 0.1 grados"
+
+#: src/metabase/api/table.clj
+msgid "Bin every 1 degree"
+msgstr "Agrupa cada 1 grado"
+
+#: src/metabase/api/table.clj
+msgid "Bin every 10 degrees"
+msgstr "Agrupa cada 10 grados"
+
+#: src/metabase/api/table.clj
+msgid "Bin every 20 degrees"
+msgstr "Agrupa cada 20 grados"
+
+#. returns `true` if successful -- see JavaDoc
+#: src/metabase/api/tiles.clj src/metabase/pulse/render.clj
+msgid "No appropriate image writer found!"
+msgstr "¡No se encontró un escritor de imágenes apropiado!"
+
+#: src/metabase/api/user.clj
+msgid "Email address already in use."
+msgstr "Dirección de Email ya en uso"
+
+#: src/metabase/api/user.clj
+msgid "Email address already associated to another user."
+msgstr "Dirección de Email asociado a otro usuario"
+
+#: src/metabase/api/user.clj
+msgid "Not able to reactivate an active user"
+msgstr "No se puede activar un usuario activo"
+
+#: src/metabase/api/user.clj
+msgid "Invalid password"
+msgstr "Contraseña inválida"
+
+#: src/metabase/automagic_dashboards/comparison.clj
+msgid "All {0}"
+msgstr "Todos los {0}"
+
+#: src/metabase/automagic_dashboards/comparison.clj
+msgid "{0}, all {1}"
+msgstr "{0}, todos {1}"
+
+#: src/metabase/automagic_dashboards/comparison.clj
+msgid "Comparison of {0} and {1}"
+msgstr "Comparativa de {0} y {1}"
+
+#: src/metabase/automagic_dashboards/comparison.clj
+msgid "Automatically generated comparison dashboard comparing {0} and {1}"
+msgstr "Crea automáticamente comparativas de {0} y {1}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "sum"
+msgstr "suma"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "average"
+msgstr "media"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "minumum"
+msgstr "mínimo"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "maximum"
+msgstr "máximo"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "distinct count"
+msgstr "Cuenta distinta"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "standard deviation"
+msgstr "desviación estándar"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "cumulative count"
+msgstr "recuento acumulativo"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "cumulative sum"
+msgstr "suma acumulativa"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} and {1}"
+msgstr "{0} y {1}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} of {1}"
+msgstr "{0} de {1}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} by {1}"
+msgstr "{0} por {1}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} in the {1} segment"
+msgstr "{0} en el segmento {1}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} segment"
+msgstr "{0} segmento"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} metric"
+msgstr "{0} métrica"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} field"
+msgstr "{0} campo"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "\"{0}\" question"
+msgstr "\"{0}\" pregunta"
+
+#: src/metabase/automagic_dashboards/comparison.clj
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Compare with {0}"
+msgstr "Compara con {0}"
+
+#: src/metabase/automagic_dashboards/comparison.clj
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Compare with entire dataset"
+msgstr "Compara con todos los datos"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Applying heuristic %s to %s."
+msgstr "Aplicando heurística %s a %s."
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Dimensions bindings:n%s"
+msgstr "Enlaces de dimensiones:n%s"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Using definitions:nMetrics:n%snFilters:n%s"
+msgstr "Usando definicioness:nMétricas:n%snFiltros:n%s"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Can''t create dashboard for {0}"
+msgstr "No se puede crear un cuadro de mando para {0}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0}st"
+msgstr "{0}º"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0}nd"
+msgstr "{0}º"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0}rd"
+msgstr "{0}º"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0}th"
+msgstr "{0}º"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "at {0}"
+msgstr "via {0}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "on {0}"
+msgstr "en {0}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "in {0} week - {1}"
+msgstr "en {0} semana - {1}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "in {0}"
+msgstr "en {0}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "in Q{0} - {1}"
+msgstr "in T{0} {1}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Q{0}"
+msgstr "T{0}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} is {1}"
+msgstr "{0} es {1}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} is between {1} and {2}"
+msgstr "{0} está entre {1} y {2}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} is between {1} and {2}; and {3} is between {4} and {5}"
+msgstr "{0} está entre {1} y {2} y {3} está entre {4} y {5}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "where {0}"
+msgstr "donde {0}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "A closer look at {0}"
+msgstr "Ver más detalles de {0}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "A closer look at the {0}"
+msgstr "Ver más detalles de {0}"
+
+#: src/metabase/automagic_dashboards/populate.clj
+msgid "Adding %s cards to dashboard %s:n%s"
+msgstr "Añadiendo %s tarjetas al Cuadro de Mando %s:n%s"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "0 <= score <= {0}"
+msgstr "0 <= puntuación <= {0}"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "1 <= width <= {0}"
+msgstr "1 <= anchura <= {0}"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid metrics references"
+msgstr "Referencias de métricas válidas"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid filters references"
+msgstr "Referencias de filtros válidos"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid group references"
+msgstr "Referencias de grupos válidos"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid order_by references"
+msgstr "Referencias de ordenación válidas"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid dashboard filters references"
+msgstr "Referencias de filtros de cuadros de mando válidas"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid dimension references"
+msgstr "Referencias de dimensiones válidas"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid card dimension references"
+msgstr "Referencias de dimensiones de tarjeta válidas"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Error parsing %s:n%s"
+msgstr "Error procesando %s:n%s"
+
+#: src/metabase/cmd/reset_password.clj
+msgid "No user found with email address ''{0}''. "
+msgstr "No se ha encontrado un usuario con email ''{0}''. "
+
+#: src/metabase/cmd/reset_password.clj
+msgid "Please check the spelling and try again."
+msgstr "Por favor, verifícalo e intenta de nuevo"
+
+#: src/metabase/cmd/reset_password.clj
+msgid "Resetting password for {0}..."
+msgstr "Restableciendo la contraseña de {0} ..."
+
+#: src/metabase/cmd/reset_password.clj
+msgid "OK [[[{0}]]]"
+msgstr "VALE [[[{0}]]]"
+
+#: src/metabase/cmd/reset_password.clj
+msgid "FAIL [[[{0}]]]"
+msgstr "FALLO [[[{0}]]]"
+
+#: src/metabase/core.clj
+msgid "Please use the following URL to setup your Metabase installation:"
+msgstr "Utiliza la siguiente URL para configurar tu instalación de Metabase:"
+
+#: src/metabase/core.clj
+msgid "Metabase Shutting Down ..."
+msgstr "Apagando Metabase ..."
+
+#: src/metabase/core.clj
+msgid "Metabase Shutdown COMPLETE"
+msgstr "Metabase apagado correctamente"
+
+#: src/metabase/core.clj
+msgid "Starting Metabase version {0} ..."
+msgstr "Iniciando Metabase {0} ..."
+
+#: src/metabase/core.clj
+msgid "System timezone is ''{0}'' ..."
+msgstr "La zona horaria del sistema es ''{0}'' ..."
+
+#. startup database.  validates connection & runs any necessary migrations
+#: src/metabase/core.clj
+msgid "Setting up and migrating Metabase DB. Please sit tight, this may take a minute..."
+msgstr "Configurando y migrando la base de datos de Metabase. Por favor, siéntate, esto puede tardar unos minutos ..."
+
+#: src/metabase/core.clj
+msgid "Looks like this is a new installation ... preparing setup wizard"
+msgstr "Parece que es una instalación nueva ... iniciando el asistente de configuración"
+
+#: src/metabase/core.clj
+msgid "Metabase Initialization COMPLETE"
+msgstr "Inicialización de Metabase COMPLETADA"
+
+#: src/metabase/core.clj
+msgid "Launching Embedded Jetty Webserver with config:"
+msgstr "Iniciando el servidor embebido Jetty con la configuración:"
+
+#: src/metabase/core.clj
+msgid "Shutting Down Embedded Jetty Webserver"
+msgstr "Apagando el servidor Jetty embebido"
+
+#: src/metabase/core.clj
+msgid "Starting Metabase in STANDALONE mode"
+msgstr "Iniciando Metabase en modo INDEPENDIENTE"
+
+#: src/metabase/core.clj
+msgid "Metabase Initialization FAILED"
+msgstr "La inicialización de Metabase ha FALLADO"
+
+#: src/metabase/db.clj
+msgid "Database has migration lock; cannot run migrations."
+msgstr "La base de datos tiene un bloqueo de migración por lo que no podemos actualizar."
+
+#: src/metabase/db.clj
+msgid "You can force-release these locks by running `java -jar metabase.jar migrate release-locks`."
+msgstr "Puedes forzar la liberación de estos bloqueos ejecutando `java -jar metabase.jar migrate release-locks`."
+
+#: src/metabase/db.clj
+msgid "Checking if Database has unrun migrations..."
+msgstr "Comprobando si la base de datos tiene migraciones sin terminar..."
+
+#: src/metabase/db.clj
+msgid "Database has unrun migrations. Waiting for migration lock to be cleared..."
+msgstr "La base de datos tiene migraciones sin terminar. Esperando a que se borre el bloqueo de migración..."
+
+#: src/metabase/db.clj
+msgid "Migration lock is cleared. Running migrations..."
+msgstr "Se ha eliminado el bloqueo de migración. Ejecutando migraciones..."
+
+#: src/metabase/db.clj
+msgid "Migration lock cleared, but nothing to do here! Migrations were finished by another instance."
+msgstr "Se borró el bloqueo de migración, ¡pero no hay nada que hacer aquí! Las migraciones fueron terminadas por otra instancia."
+
+#. Set up liquibase and let it do its thing
+#: src/metabase/db.clj
+msgid "Setting up Liquibase..."
+msgstr "Preparando Liquibase..."
+
+#: src/metabase/db.clj
+msgid "Liquibase is ready."
+msgstr "Liquibase preparado"
+
+#: src/metabase/db.clj
+msgid "Verifying {0} Database Connection ..."
+msgstr "Verificando la conexión a la base de datos {0} ..."
+
+#: src/metabase/db.clj
+msgid "Verify Database Connection ... "
+msgstr "Verifica la conexión a la base de datos ... "
+
+#: src/metabase/db.clj
+msgid "Running Database Migrations..."
+msgstr "Ejecutando migraciones de la base de datos..."
+
+#: src/metabase/db.clj
+msgid "Database Migrations Current ... "
+msgstr "Migraciones de la base de datos actuales ..."
+
+#: src/metabase/driver.clj
+msgid "Hmm, we couldn''t connect to the database."
+msgstr "Hmm, no he podido conectar con la base de datos."
+
+#: src/metabase/driver.clj
+msgid "Make sure your host and port settings are correct"
+msgstr "Asegúrate de que la configuración del host y del puerto sean correctas"
+
+#: src/metabase/driver.clj
+msgid "We couldn''t connect to the ssh tunnel host."
+msgstr "no he podido conectar con el servidor del tunel ssh"
+
+#: src/metabase/driver.clj
+msgid "Check the username, password."
+msgstr "Verifica el nombre de usuario/contraseña."
+
+#: src/metabase/driver.clj
+msgid "Check the hostname and port."
+msgstr "Verifica el servidor y puerto"
+
+#: src/metabase/driver.clj
+msgid "Looks like the database name is incorrect."
+msgstr "Parece que el nombre de la base de datos es incorrecto"
+
+#: src/metabase/driver.clj
+msgid "It looks like your host is invalid."
+msgstr "Parece que tu servidor no es válido"
+
+#: src/metabase/driver.clj
+msgid "Please double-check it and try again."
+msgstr "Por favor, vuelve a verificarlo e intenta de nuevo"
+
+#: src/metabase/driver.clj
+msgid "Looks like your password is incorrect."
+msgstr "Parece que tu contraseña es incorrecta"
+
+#: src/metabase/driver.clj
+msgid "Looks like you forgot to enter your password."
+msgstr "Parece que olvidaste poner tu contraseña"
+
+#: src/metabase/driver.clj
+msgid "Looks like your username is incorrect."
+msgstr "Parece que tu nombre de usuario es incorrecto."
+
+#: src/metabase/driver.clj
+msgid "Looks like the username or password is incorrect."
+msgstr "Parece que el nombre de usuario o la contraseña son incorrectos."
+
+#. ## CONFIG
+#: src/metabase/driver.clj
+msgid "Connection timezone to use when executing queries. Defaults to system timezone."
+msgstr "Zona horaria de conexión que se utilizará al ejecutar consultas. El valor predeterminado es la zona horaria del sistema."
+
+#: src/metabase/driver.clj
+msgid "Registered driver {0} {1}"
+msgstr "Controlador registrado {0} {1}"
+
+#: src/metabase/driver.clj
+msgid "No -init-driver function found for ''{0}''"
+msgstr "No se ha encontrado ninguna función -init-driver para ''{0}''"
+
+#: src/metabase/driver.clj
+msgid "Unable to parse date string ''{0}'' for database engine ''{1}''"
+msgstr "No se puede analizar la cadena de fecha ''{0}'' para el motor de base de datos ''{1}''"
+
+#. all-NULL columns in DBs like Mongo w/o explicit types
+#: src/metabase/driver.clj
+msgid "Don''t know how to map class ''{0}'' to a Field base_type, falling back to :type/*."
+msgstr "No se cómo utilizar la clase ''{0}'' como tipo base de campo, volviendo a :type/*."
+
+#: src/metabase/driver.clj
+msgid "Failed to connect to database: {0}"
+msgstr "Error al conectarse a la base de datos: {0}"
+
+#: src/metabase/driver/bigquery.clj
+msgid "Invalid BigQuery identifier: ''{0}''"
+msgstr "Identificador de BigQuery inválido: ''{0}''"
+
+#: src/metabase/driver/bigquery.clj
+msgid "BigQuery statements can't be parameterized!"
+msgstr "Los comandos BigQuery no soportan parámetros!"
+
+#: src/metabase/driver/generic_sql/query_processor.clj
+msgid "Failed to set timezone:"
+msgstr "No se pudo establecer la zona horaria:"
+
+#: src/metabase/driver/googleanalytics.clj
+msgid "You must enable the Google Analytics API. Use this link to go to the Google Developers Console: {0}"
+msgstr "Debes habilitar el API de Google Analytics. Utiliza este enlace para ir a la Consola de Desarrollo de Google: {0}"
+
+#: src/metabase/driver/h2.clj
+msgid "Running SQL queries against H2 databases using the default (admin) database user is forbidden."
+msgstr "Está prohibido ejecutar consultas SQL en bases de datos H2 utilizando el usuario de base de datos predeterminado (administrador)."
+
+#: src/metabase/driver/sparksql.clj
+msgid "Error: metabase.driver.FixedHiveDriver is registered, but JDBC does not seem to be using it."
+msgstr "Error: metabase.driver.FixedHiveDriver está registrado, pero parece que JDBC no lo está usando."
+
+#: src/metabase/driver/sparksql.clj
+msgid "Found metabase.driver.FixedHiveDriver."
+msgstr "Se ha encontrado metabase.driver.FixedHiveDriver."
+
+#: src/metabase/driver/sparksql.clj
+msgid "Successfully registered metabase.driver.FixedHiveDriver with JDBC."
+msgstr "Se ha registrado metabase.driver.FixedHiveDriver con JDBC."
+
+#. CONFIG
+#. TODO - smtp-port should be switched to type :integer
+#: src/metabase/email.clj
+msgid "Email address you want to use as the sender of Metabase."
+msgstr "Dirección de correo electrónico que quieres usar como remitente de Metabase."
+
+#: src/metabase/email.clj
+msgid "The address of the SMTP server that handles your emails."
+msgstr "La dirección del servidor SMTP que maneja tus correos electrónicos."
+
+#: src/metabase/email.clj
+msgid "SMTP username."
+msgstr "Usuario SMTP."
+
+#: src/metabase/email.clj
+msgid "SMTP password."
+msgstr "Contraseña SMTP."
+
+#: src/metabase/email.clj
+msgid "The port your SMTP server uses for outgoing emails."
+msgstr "El puerto que usa tu servidor SMTP para los correos electrónicos salientes."
+
+#: src/metabase/email.clj
+msgid "SMTP secure connection protocol. (tls, ssl, starttls, or none)"
+msgstr "Protocolo de conexión segura SMTP. (tls, ssl, starttls, o ninguno)"
+
+#: src/metabase/email.clj
+msgid "none"
+msgstr "ninguno"
+
+#: src/metabase/email.clj
+msgid "SMTP host is not set."
+msgstr "El servidor SMTP no está configurado."
+
+#: src/metabase/email.clj
+msgid "Failed to send email"
+msgstr "No se pudo enviar el correo electrónico"
+
+#: src/metabase/email.clj
+msgid "Error testing SMTP connection"
+msgstr "Error al probar la conexión SMTP"
+
+#: src/metabase/integrations/ldap.clj
+msgid "Enable LDAP authentication."
+msgstr "Habilita la autenticación LDAP."
+
+#: src/metabase/integrations/ldap.clj
+msgid "Server hostname."
+msgstr "Nombre servidor"
+
+#: src/metabase/integrations/ldap.clj
+msgid "Server port, usually 389 or 636 if SSL is used."
+msgstr "Puerto del servidor, generalmente 389 o 636 si se usa SSL."
+
+#: src/metabase/integrations/ldap.clj
+msgid "Use SSL, TLS or plain text."
+msgstr "Utiliza SSL, TLS o texto sin formato."
+
+#: src/metabase/integrations/ldap.clj
+msgid "The Distinguished Name to bind as (if any), this user will be used to lookup information about other users."
+msgstr "El nombre distinguido para enlazar como (si existe), este usuario se utilizará para buscar información sobre otros usuarios."
+
+#: src/metabase/integrations/ldap.clj
+msgid "The password to bind with for the lookup user."
+msgstr "La contraseña para vincularse con el usuario de búsqueda."
+
+#: src/metabase/integrations/ldap.clj
+msgid "Search base for users. (Will be searched recursively)"
+msgstr "Base de búsqueda para usuarios. (Se buscará recursivamente)"
+
+#: src/metabase/integrations/ldap.clj
+msgid "User lookup filter, the placeholder '{login}' will be replaced by the user supplied login."
+msgstr "Filtro de búsqueda de usuario, el marcador de posición '{login}' se reemplazará por el inicio de sesión proporcionado por el usuario."
+
+#: src/metabase/integrations/ldap.clj
+msgid "Attribute to use for the user's email. (usually ''mail'', ''email'' or ''userPrincipalName'')"
+msgstr "Atributo de correo electrónico del usuario. (generalmente ''correo'', ''correo electrónico'' o ''userPrincipalName'')"
+
+#: src/metabase/integrations/ldap.clj
+msgid "Attribute to use for the user''s first name. (usually ''givenName'')"
+msgstr "Atributo a usar para el primer nombre del usuario. (generalmente ''givenName'')"
+
+#: src/metabase/integrations/ldap.clj
+msgid "Attribute to use for the user''s last name. (usually ''sn'')"
+msgstr "Atributo para usar para el apellido del usuario. (generalmente ''sn'')"
+
+#: src/metabase/integrations/ldap.clj
+msgid "Enable group membership synchronization with LDAP."
+msgstr "Habilite la sincronización de membresía grupal con LDAP."
+
+#: src/metabase/integrations/ldap.clj
+msgid "Search base for groups, not required if your LDAP directory provides a ''memberOf'' overlay. (Will be searched recursively)"
+msgstr "Base de búsqueda para grupos, no requerida si tu directorio LDAP proporciona una superposición ''memberOf''. (Se buscará recursivamente)"
+
+#. Should be in the form: {"cn=Some Group,dc=...": [1, 2, 3]} where keys are LDAP groups and values are lists of MB groups IDs
+#: src/metabase/integrations/ldap.clj
+msgid "JSON containing LDAP to Metabase group mappings."
+msgstr "JSON que contiene las correspondencia de LDAP a grupos de Metabase."
+
+#. Define a setting which captures our Slack api token
+#: src/metabase/integrations/slack.clj
+msgid "Slack API bearer token obtained from https://api.slack.com/web#authentication"
+msgstr "Slack API bearer token obtenido de https://api.slack.com/web#authentication"
+
+#: src/metabase/metabot.clj
+msgid "Enable MetaBot, which lets you search for and view your saved questions directly via Slack."
+msgstr "Habilita MetaBot, que te permite buscar y ver tus preguntas guardadas directamente a través de Slack."
+
+#: src/metabase/metabot.clj
+msgid "Last MetaBot checkin was {0} ago."
+msgstr "Ultimo acceso de MetaBot fue hace {0}"
+
+#: src/metabase/metabot.clj
+msgid "This instance will now handle MetaBot duties."
+msgstr "Esta instalación ahora manejará tareas de MetaBot"
+
+#: src/metabase/metabot.clj
+msgid "Here''s what I can {0}:"
+msgstr "Esto es lo que puedo {0}:"
+
+#: src/metabase/metabot.clj
+msgid "I don''t know how to {0} `{1}`.n{2}"
+msgstr "No se como {0} `{1}`.n{2}"
+
+#: src/metabase/metabot.clj
+msgid "Uh oh! :cry:n> {0}"
+msgstr "Oh! :cry:n>"
+
+#: src/metabase/metabot.clj
+msgid "Here''s your {0} most recent cards:n{1}"
+msgstr "Aquí están tus {0} tarjetas más recientes: n{1}"
+
+#: src/metabase/metabot.clj
+msgid "Could you be a little more specific? I found these cards with names that matched:n{0}"
+msgstr "¿Podrías ser un poco más específico? He encontrado estas tarjetas con nombres que coinciden: n{0}"
+
+#: src/metabase/metabot.clj
+msgid "I don''t know what Card `{0}` is. Give me a Card ID or name."
+msgstr "No sé lo qué es la Tarjeta `{0}`. Dame un ID o nombre de tarjeta."
+
+#: src/metabase/metabot.clj
+msgid "Show which card? Give me a part of a card name or its ID and I can show it to you. If you don''t know which card you want, try `metabot list`."
+msgstr "¿Qué tarjeta mostrar? Dame una parte del nombre de una tarjeta o su ID y te lo puedo mostrar. Si no sabes qué tarjeta quieres, prueba con `metabot list`."
+
+#: src/metabase/metabot.clj
+msgid "Ok, just a second..."
+msgstr "Vale, solo un segundo..."
+
+#: src/metabase/metabot.clj
+msgid "Not Found"
+msgstr "No Encontrado"
+
+#: src/metabase/metabot.clj
+msgid "Loading Kanye quotes..."
+msgstr "Cargando citas de Kanye..."
+
+#: src/metabase/metabot.clj
+msgid "Evaluating Metabot command:"
+msgstr "Evaluando el comando de Metabot:"
+
+#: src/metabase/metabot.clj
+msgid "Go home websocket, you're drunk."
+msgstr "El websocket esta borracho."
+
+#: src/metabase/metabot.clj
+msgid "Error launching metabot:"
+msgstr "Error al iniciar metabot:"
+
+#: src/metabase/metabot.clj
+msgid "MetaBot WebSocket is closed. Reconnecting now."
+msgstr "MetaBot WebSocket está cerrado. Reconectando ahora."
+
+#: src/metabase/metabot.clj
+msgid "Error connecting websocket:"
+msgstr "Error al conectar websocket:"
+
+#: src/metabase/metabot.clj
+msgid "This instance is performing MetaBot duties."
+msgstr "Esta instalación está manejando tareas de MetaBot"
+
+#: src/metabase/metabot.clj
+msgid "Another instance is already handling MetaBot duties."
+msgstr "Otra instalación está manejando las tareas de MetaBot"
+
+#: src/metabase/metabot.clj
+msgid "Starting MetaBot threads..."
+msgstr "Iniciando hilos de MetaBot..."
+
+#: src/metabase/metabot.clj
+msgid "Stopping MetaBot...  🤖"
+msgstr "Parando MetaBot...  🤖"
+
+#: src/metabase/metabot.clj
+msgid "MetaBot already running. Killing the previous WebSocket listener first."
+msgstr "MetaBot ya se está ejecutando. Matando al oyente WebSocket previo primero."
+
+#: src/metabase/middleware.clj
+msgid "Base-64 encoded public key for this site's SSL certificate."
+msgstr "Clave pública codificada en Base-64 para el certificado SSL de este sitio."
+
+#: src/metabase/middleware.clj
+msgid "Specify this to enable HTTP Public Key Pinning."
+msgstr "Especifica esto para habilitar la fijación de clave pública HTTP."
+
+#: src/metabase/middleware.clj
+msgid "See {0} for more information."
+msgstr "Ver {0} para más información."
+
+#: src/metabase/models/card.clj
+msgid "Cannot save Question: source query has circular references."
+msgstr "No se puede guardar la pregunta debido a referencias circulares."
+
+#: src/metabase/models/card.clj src/metabase/models/query/permissions.clj
+#: src/metabase/query_processor/middleware/permissions.clj
+msgid "Card {0} does not exist."
+msgstr "No existe la tarjeta {0}"
+
+#: src/metabase/models/card.clj
+msgid "You do not have permissions to run ad-hoc native queries against Database {0}."
+msgstr "Lo siento, no tienes permiso para ejecutar consultas directas en la base de datos {0}."
+
+#: src/metabase/models/collection.clj
+msgid "Invalid color"
+msgstr "Color inválido"
+
+#: src/metabase/models/collection.clj
+msgid "must be a valid 6-character hex color code"
+msgstr "debe ser un código de color hexadecimal válido de 6 caracteres"
+
+#: src/metabase/models/collection.clj
+msgid "Collection name cannot be blank!"
+msgstr "¡El nombre de la colección no puede ser vacío!"
+
+#: src/metabase/models/collection.clj
+msgid "cannot be blank"
+msgstr "no puede ser vacío"
+
+#: src/metabase/models/collection.clj
+msgid "Invalid Collection location: path is invalid."
+msgstr "Ubicación de colección inválida: la ruta no es válida."
+
+#: src/metabase/models/collection.clj
+msgid "You cannot move a Personal Collection."
+msgstr "No puedes mover una Colección Personal."
+
+#: src/metabase/models/collection.clj
+msgid "Invalid Collection location: some or all ancestors do not exist."
+msgstr "Ubicación de colección inválida: uno o más antepasados no existen."
+
+#: src/metabase/models/collection.clj
+msgid "You cannot archive the Root Collection."
+msgstr "No puedes archivar la Colección Raíz!"
+
+#: src/metabase/models/collection.clj
+msgid "You cannot archive a Personal Collection."
+msgstr "No puedes archivar una Colección Personal."
+
+#: src/metabase/models/collection.clj
+msgid "You cannot move the Root Collection."
+msgstr "No puedes mover la Colección Raíz!"
+
+#: src/metabase/models/collection.clj
+msgid "You cannot move a Collection into itself or into one of its descendants."
+msgstr "No puedes mover una Colección dentro de si misma o dentro de uno de sus descendientes."
+
+#. first move this Collection
+#: src/metabase/models/collection.clj
+msgid "Moving Collection {0} and its descendants from {1} to {2}"
+msgstr "Moviendo la Colección {0} y sus descendientes de {1} a {2}"
+
+#: src/metabase/models/collection.clj
+msgid "You're not allowed to change the owner of a Personal Collection."
+msgstr "No está permitido cambiar el propietario de una Colección Personal."
+
+#: src/metabase/models/collection.clj
+msgid "You're not allowed to move a Personal Collection."
+msgstr "No tienes permiso para mover una Colección Personal."
+
+#: src/metabase/models/collection.clj
+msgid "You cannot move a Collection and archive it at the same time."
+msgstr "No puedes mover y archivar una Colección al mismo tiempo."
+
+#: src/metabase/models/collection.clj
+msgid "You cannot delete a Personal Collection!"
+msgstr "No puedes eliminar una Colección Personal!"
+
+#: src/metabase/models/collection.clj
+msgid "{0} {1}''s Personal Collection"
+msgstr "Colección Personal de {0} {1}"
+
+#: src/metabase/models/collection_revision.clj
+msgid "You cannot update a CollectionRevision!"
+msgstr "No puedes actualzar una revisión de Colección!"
+
+#: src/metabase/models/field_values.clj
+msgid "Field {0} was previously automatically set to show a list widget, but now has {1} values."
+msgstr "El campo {0} se estableció automáticamente para mostrar una lista, pero ahora tiene {1} valores."
+
+#: src/metabase/models/field_values.clj
+msgid "Switching Field to use a search widget instead."
+msgstr "Cambio de campo para usar un elemento de búsqueda en su lugar."
+
+#: src/metabase/models/field_values.clj
+msgid "Storing updated FieldValues for Field {0}..."
+msgstr "Almacenando FieldValues actualizados para el campo {0} ..."
+
+#: src/metabase/models/field_values.clj
+msgid "Storing FieldValues for Field {0}..."
+msgstr "Almacenando FieldValues para el campo {0} ..."
+
+#: src/metabase/models/humanization.clj
+msgid "Metabase can attempt to transform your table and field names into more sensible, human-readable versions, e.g. \"somehorriblename\" becomes \"Some Horrible Name\"."
+msgstr "Metabase puede intentar transformar los nombres de tablas y campos en versiones más entendibles y legibles para el ser humano, p.e. \"unnombrehorrible\" se convierte en \"Un Nombre Horrible\". "
+
+#: src/metabase/models/humanization.clj
+msgid "This doesn’t work all that well if the names are in a language other than English, however."
+msgstr "Sin embargo, esto no funciona muy bien si los nombres están en un idioma que no sea el inglés."
+
+#: src/metabase/models/humanization.clj
+msgid "Do you want us to take a guess?"
+msgstr "¿Quieres que adivinemos?"
+
+#: src/metabase/models/permissions.clj
+msgid "You cannot create or revoke permissions for the 'Admin' group."
+msgstr "No puedes crear o revocar permisos para el grupo 'Admin'."
+
+#: src/metabase/models/permissions.clj
+msgid "Invalid permissions object path: ''{0}''."
+msgstr "Ruta del objeto de permisos inválido: ''{0}''."
+
+#: src/metabase/models/permissions.clj
+msgid "You cannot update a permissions entry!"
+msgstr "¡No puedes actualizar una entrada de permisos!"
+
+#: src/metabase/models/permissions.clj
+msgid "Delete it and create a new one."
+msgstr "Elimínalo y crear uno nuevo."
+
+#: src/metabase/models/permissions.clj
+msgid "You cannot edit permissions for a Personal Collection or its descendants."
+msgstr "No puedes editar los permisos de una Colección personal ni sus descendientes."
+
+#: src/metabase/models/permissions.clj
+msgid "Looks like someone else edited the permissions and your data is out of date."
+msgstr "Parece que alguien más editó los permisos y tus datos están desactualizados."
+
+#: src/metabase/models/permissions.clj
+msgid "Please fetch new data and try again."
+msgstr "Por favor, obtiene nuevos datos e intenta de nuevo"
+
+#: src/metabase/models/permissions_group.clj
+msgid "Created magic permissions group ''{0}'' (ID = {1})"
+msgstr "Creado grupo de permisos mágicos ''{0}'' (ID = {1}"
+
+#: src/metabase/models/permissions_group.clj
+msgid "A group with that name already exists."
+msgstr "Ya existe un grupo con ese nombre"
+
+#: src/metabase/models/permissions_group.clj
+msgid "You cannot edit or delete the ''{0}'' permissions group!"
+msgstr "¡No puedes editar o eliminar el grupo de permisos ''{0}''!"
+
+#: src/metabase/models/permissions_group_membership.clj
+msgid "You cannot add or remove users to/from the 'MetaBot' group."
+msgstr "No puedes añadir o eliminar usuarios a/del grupo 'MetaBot'."
+
+#: src/metabase/models/permissions_group_membership.clj
+msgid "You cannot add or remove users to/from the 'All Users' group."
+msgstr "No puedes añadir o eliminar usuarios a/del grupo 'All'."
+
+#: src/metabase/models/permissions_group_membership.clj
+msgid "You cannot remove the last member of the 'Admin' group!"
+msgstr "¡No puedes eliminar al último miembro del grupo 'Admin'!"
+
+#: src/metabase/models/permissions_revision.clj
+msgid "You cannot update a PermissionsRevision!"
+msgstr "¡No puedes actualizar una revisión de permisos!"
+
+#. if there's still not a Card, throw an Exception!
+#: src/metabase/models/pulse.clj
+msgid "Invalid Alert: Alert does not have a Card assoicated with it"
+msgstr "Alerta Inválida: no tiene asociada un tarjeta"
+
+#: src/metabase/models/pulse.clj
+msgid "value must be a map with the keys `{0}`, `{1}`, and `{2}`."
+msgstr "el valor debe ser un mapa con los índices `{0}`, `{1}`, and `{2}`."
+
+#: src/metabase/models/pulse.clj
+msgid "value must be a map with the following keys `({0})`"
+msgstr "el valor debe ser un mapa con los índices `({0})`."
+
+#: src/metabase/models/query/permissions.clj
+msgid "Error calculating permissions for query: {0}"
+msgstr "Error calculando los permisos para la consulta: {0}"
+
+#: src/metabase/models/query/permissions.clj
+msgid "Invalid query type: {0}"
+msgstr "Tipo de consulta inválida: {0}"
+
+#: src/metabase/models/query_execution.clj
+msgid "You cannot update a QueryExecution!"
+msgstr "¡No puedes actualizar una ejecución de consulta!"
+
+#: src/metabase/models/revision.clj
+msgid "You cannot update a Revision!"
+msgstr "¡No puedes actualizar una revisión!"
+
+#: src/metabase/models/setting.clj
+msgid "Setting {0} does not exist.nFound: {1}"
+msgstr "El ajuste {0} no existe.\n"
+"Encontrado: {1}"
+
+#: src/metabase/models/setting.clj
+msgid "Updating value of settings-last-updated in DB..."
+msgstr "Actualizando ulitmo cambio en configuración..."
+
+#: src/metabase/models/setting.clj
+msgid "Checking whether settings cache is out of date (requires DB call)..."
+msgstr "Comprobando si la memoria caché de configuración está desactualizada..."
+
+#: src/metabase/models/setting.clj
+msgid "Settings have been changed on another instance, and will be reloaded here."
+msgstr "La configuración se modificó en otra instancia y se volverá a cargar aquí."
+
+#: src/metabase/models/setting.clj
+msgid "Refreshing Settings cache..."
+msgstr "Actualizando caché de Configuración"
+
+#: src/metabase/models/setting.clj
+msgid "Invalid value for string: must be either \"true\" or \"false\" (case-insensitive)."
+msgstr "Valor no válido para la cadena: debe ser \"true\" o \"false\" (no distingue entre mayúsculas y minúsculas)"
+
+#: src/metabase/models/setting.clj
+msgid "You cannot update `settings-last-updated` yourself! This is done automatically."
+msgstr "ulitmo cambio en configuración se actualiza automáticamente, no puedes cambiarlo"
+
+#. go ahead and log the Exception anyway on the off chance that it *wasn't* just a race condition issue
+#: src/metabase/models/setting.clj
+msgid "Error inserting a new Setting:"
+msgstr "Error insertando nueva configuración:"
+
+#: src/metabase/models/setting.clj
+msgid "Assuming Setting already exists in DB and updating existing value."
+msgstr "Asumiendo que la configuración ya existe en DB y actualizando el valor existente."
+
+#: src/metabase/models/user.clj
+msgid "value must be a map with each value either a string or number."
+msgstr "el valor debe ser un mapa con valores numéricos o de cadena."
+
+#: src/metabase/plugins.clj
+msgid "Loading plugins in directory {0}..."
+msgstr "Cargando complementos en el directorio {0}..."
+
+#: src/metabase/plugins.clj
+msgid "Loading plugin {0}... "
+msgstr "Cargando complemento {0}..."
+
+#: src/metabase/plugins.clj
+msgid "It looks like you have some external dependencies in your Metabase plugins directory."
+msgstr "Parece que tienes algunas dependencias externas en tu directorio de complementos de Metabase."
+
+#: src/metabase/plugins.clj
+msgid "With Java 9 or higher, Metabase cannot automatically add them to your classpath."
+msgstr "Con Java 9 o superior, Metabase no puede agregarlos automáticamente a tu classpath."
+
+#: src/metabase/plugins.clj
+msgid "Instead, you should include them at launch with the -cp option. For example:"
+msgstr "En su lugar, debes incluirlos en el lanzamiento con la opción -cp. Por ejemplo:"
+
+#: src/metabase/plugins.clj
+msgid "See https://metabase.com/docs/latest/operations-guide/start.html#java-versions for more details."
+msgstr "Ver https://metabase.com/docs/latest/operations-guide/start.html#java-versions para más detalles."
+
+#: src/metabase/plugins.clj
+msgid "(If you're already running Metabase this way, you can ignore this message.)"
+msgstr "(Si ya estás ejecutando Metabase de esta manera, puedes ignorar este mensaje.)"
+
+#: src/metabase/public_settings.clj
+msgid "Identify when new versions of Metabase are available."
+msgstr "Informar cuándo hay nuevas versiones de Metabase."
+
+#: src/metabase/public_settings.clj
+msgid "Information about available versions of Metabase."
+msgstr "Información sobre las versiones disponibles de Metabase."
+
+#: src/metabase/public_settings.clj
+msgid "The name used for this instance of Metabase."
+msgstr "El nombre usado para esta instancia de Metabase."
+
+#: src/metabase/public_settings.clj
+msgid "The base URL of this Metabase instance, e.g. \"http://metabase.my-company.com\"."
+msgstr "La URL base de esta instancia de Metabase, p.e. \"http://metabase.my-company.com\"."
+
+#: src/metabase/public_settings.clj
+msgid "The default language for this Metabase instance."
+msgstr "El idioma predeterminado para esta instancia de Metabase."
+
+#: src/metabase/public_settings.clj
+msgid "This only applies to emails, Pulses, etc. Users'' browsers will specify the language used in the user interface."
+msgstr " Esto solo se aplica a correos electrónicos, pulsos, etc. Los navegadores de los usuarios especificarán el idioma utilizado en la interfaz de usuario."
+
+#: src/metabase/public_settings.clj
+msgid "The email address users should be referred to if they encounter a problem."
+msgstr "La dirección de correo electrónico al que se dirigirán los usuarios si encuentran un problema."
+
+#: src/metabase/public_settings.clj
+msgid "Enable the collection of anonymous usage data in order to help Metabase improve."
+msgstr "Habilita la recopilación de datos de uso anónimos para ayudar a mejorar Metabase."
+
+#: src/metabase/public_settings.clj
+msgid "The map tile server URL template used in map visualizations, for example from OpenStreetMaps or MapBox."
+msgstr "La plantilla de URL del servidor de capas de mapa utilizada en las visualizaciones de mapas, por ejemplo desde OpenStreetMaps o MapBox."
+
+#: src/metabase/public_settings.clj
+msgid "Enable admins to create publicly viewable links (and embeddable iframes) for Questions and Dashboards?"
+msgstr "¿Habilita a los administradores para crear enlaces visibles públicamente (y iframes incrustados) para Preguntas y Cuadros de Mando?"
+
+#: src/metabase/public_settings.clj
+msgid "Allow admins to securely embed questions and dashboards within other applications?"
+msgstr "¿Permite a los administradores insertar preguntas y cuadros de mando de forma segura dentro de otras aplicaciones?"
+
+#: src/metabase/public_settings.clj
+msgid "Allow using a saved question as the source for other queries?"
+msgstr "¿Permitir usar una pregunta guardada como fuente para otras consultas?"
+
+#: src/metabase/public_settings.clj
+msgid "Enabling caching will save the results of queries that take a long time to run."
+msgstr "Al habilitar el almacenamiento en caché se guardan los resultados de las consultas que tardan mucho tiempo en ejecutarse."
+
+#: src/metabase/public_settings.clj
+msgid "The maximum size of the cache, per saved question, in kilobytes:"
+msgstr "El tamaño máximo de la memoria caché, por pregunta guardada, en kilobytes:"
+
+#: src/metabase/public_settings.clj
+msgid "The absolute maximum time to keep any cached query results, in seconds."
+msgstr "El tiempo máximo absoluto para mantener los resultados de las consultas en caché, en segundos."
+
+#: src/metabase/public_settings.clj
+msgid "Metabase will cache all saved questions with an average query execution time longer than this many seconds:"
+msgstr "Metabase guardará en caché todas las preguntas guardadas con un tiempo promedio de ejecución de la consulta más largo que estos segundos:"
+
+#: src/metabase/public_settings.clj
+msgid "To determine how long each saved question''s cached result should stick around, we take the query''s average execution time and multiply that by whatever you input here."
+msgstr "Para determinar cuánto tiempo debe permanecer el resultado guardado en la memoria de cada pregunta guardada, tomamos el tiempo de ejecución promedio de la consulta y lo multiplicamos por lo que pones aquí."
+
+#: src/metabase/public_settings.clj
+msgid "So if a query takes on average 2 minutes to run, and you input 10 for your multiplier, its cache entry will persist for 20 minutes."
+msgstr "Así que, si una consulta tarda un promedio de 2 minutos en ejecutarse, e ingresas 10 para su multiplicador, su entrada en la memoria caché se mantendrá por 20 minutos."
+
+#: src/metabase/public_settings.clj
+msgid "When using the default binning strategy and a number of bins is not provided, this number will be used as the default."
+msgstr "Cuando se usa la estrategia de agrupación predeterminada y no se proporciona una cantidad de tramos, este número se usará como el predeterminado."
+
+#: src/metabase/public_settings.clj
+msgid "When using the default binning strategy for a field of type Coordinate (such as Latitude and Longitude), this number will be used as the default bin width (in degrees)."
+msgstr "Cuando se utiliza la estrategia de agrupación predeterminada para un campo de tipo Coordenada (como Latitud y Longitud), este número se utilizará como el ancho predeterminado del tramo (en grados)."
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Unable to validate token."
+msgstr "No se puede validar el token."
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Error fetching token status:"
+msgstr "Error consultando el estado del Token:"
+
+#: src/metabase/public_settings/metastore.clj
+msgid "There was an error checking whether this token was valid."
+msgstr "Hubo un error al verificar si este token es válido."
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Token validation timed out."
+msgstr "La validación de token agotó el tiempo de espera."
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Invalid token: token isn't in the right format."
+msgstr "Token inválido: el token no está en el formato correcto."
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Checking with the MetaStore to see whether {0} is valid..."
+msgstr "Verificando en el MetaStore para ver si {0} es válido ..."
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Token for premium embedding. Go to the MetaStore to get yours!"
+msgstr "Token para incrustación premium. ¡Ve a MetaStore para obtener el tuyo!"
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Token is valid."
+msgstr "Token válido"
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Error setting premium embedding token"
+msgstr "Error al configurar el Token de inserción premium"
+
+#: src/metabase/pulse.clj
+msgid "Unable to compare results to goal for alert."
+msgstr "No se pueden comparar los resultados con el objetivo de alerta."
+
+#: src/metabase/pulse.clj
+msgid "Question ID is ''{0}'' with visualization settings ''{1}''"
+msgstr "El ID de la pregunta es ''{0}'' con configuración de visualización ''{1}''"
+
+#: src/metabase/pulse.clj
+msgid "Unrecognized alert with condition ''{0}''"
+msgstr "Alerta desconocida con condición ''{0}''"
+
+#: src/metabase/pulse.clj
+msgid "Unrecognized channel type {0}"
+msgstr "El tipo de canal {0} es desconocido"
+
+#: src/metabase/pulse.clj
+msgid "Error sending notification!"
+msgstr "Error enviando norificación!"
+
+#: src/metabase/pulse/color.clj
+msgid "Can't find JS color selector at ''{0}''"
+msgstr "No se ha podido encontrar selector JS en ''{0}''"
+
+#: src/metabase/pulse/render.clj
+msgid "Card has errors: {0}"
+msgstr "La tarjeta tiene errores: {0}"
+
+#: src/metabase/pulse/render.clj
+msgid "Pulse card render error"
+msgstr "Error al procesar Pulso"
+
+#: src/metabase/query_processor/middleware/fetch_source_query.clj
+msgid "Trimming trailing comment from card with id {0}"
+msgstr "Eliminando el comentario final de la tarjeta con id {0}"
+
+#: src/metabase/query_processor/middleware/parameters/sql.clj
+msgid "Can't find field with ID: {0}"
+msgstr "No se puede encontrar el campo con ID: {0}"
+
+#: src/metabase/query_processor/middleware/parameters/sql.clj
+msgid "''{0}'' is a required param."
+msgstr "''{0}'' es un parámetro requerido"
+
+#: src/metabase/query_processor/middleware/parameters/sql.clj
+msgid "Found ''{0}'' with no terminating ''{1}'' in query ''{2}''"
+msgstr "Encontrado ''{0}'' sin terminación ''{1}'' en la consulta ''{2}''"
+
+#: src/metabase/query_processor/middleware/parameters/sql.clj
+msgid "Unable to substitute ''{0}'': param not specified.nFound: {1}"
+msgstr "No se puede sustituir ''{0}'': parámetro no especificado.\n"
+"Encontrado: {1}"
+
+#: src/metabase/query_processor/middleware/permissions.clj
+msgid "You do not have permissions to view Card {0}."
+msgstr "No tienes permiso para ver la Tarjeta {0}."
+
+#: src/metabase/query_processor/middleware/permissions.clj
+msgid "You do not have permissions to run this query."
+msgstr "No tienes permiso para ejecutar esta consulta."
+
+#: src/metabase/sync/analyze.clj
+msgid "Fingerprint updates attempted {0}, updated {1}, no data found {2}, failed {3}"
+msgstr "Actualizaciones de huellas dactilares intentadas {0}, actualizadas {1}, no se encontraron datos {2}, fallaron {3}"
+
+#: src/metabase/sync/analyze.clj
+msgid "Total number of fields classified {0}, {1} failed"
+msgstr "El número total de campos clasificados {0}, {1} han fallado"
+
+#: src/metabase/sync/analyze.clj
+msgid "Total number of tables classified {0}, {1} updated"
+msgstr "Número total de tablas clasificadas {0}, {1} actualizadas"
+
+#: src/metabase/sync/analyze/fingerprint/fingerprinters.clj
+msgid "Error generating fingerprint for {0}"
+msgstr "Error generando huella para {0}"
+
+#: src/metabase/sync/field_values.clj
+msgid "Updated {0} field value sets, created {1}, deleted {2} with {3} errors"
+msgstr "Conjuntos de valores de campo actualizados {0}, creados {1}, eliminados {2} con {3} errores"
+
+#: src/metabase/sync/sync_metadata.clj
+msgid "Total number of fields sync''d {0}, number of fields updated {1}"
+msgstr "Número total de campos sincronizados {0}, número de campos actualizados {1}"
+
+#: src/metabase/sync/sync_metadata.clj
+msgid "Total number of tables sync''d {0}, number of tables updated {1}"
+msgstr "Número total de tablas sincronizados {0}, número de tablas actualizados {1}"
+
+#: src/metabase/sync/sync_metadata.clj
+msgid "Found timezone id {0}"
+msgstr "Encontrado la zona horaria {0}"
+
+#: src/metabase/sync/sync_metadata.clj
+msgid "Total number of foreign keys sync''d {0}, {1} updated and {2} tables failed to update"
+msgstr "El número total de claves externas sincronizadas {0}, {1} actualizadas y {2} tablas han fallado"
+
+#: src/metabase/sync/util.clj
+msgid "{0} Database {1} ''{2}''"
+msgstr "{0} Base de Datos {1} ''{2}''"
+
+#: src/metabase/sync/util.clj
+msgid "Table {0} ''{1}''"
+msgstr "Tabla {0} ''{1}''"
+
+#: src/metabase/sync/util.clj
+msgid "Field {0} ''{1}''"
+msgstr "Campo {0} ''{1}''"
+
+#: src/metabase/sync/util.clj
+msgid "Field ''{0}''"
+msgstr "Campo ''{0}''"
+
+#: src/metabase/sync/util.clj
+msgid "step ''{0}'' for {1}"
+msgstr "paso ''{0}'' para {1}"
+
+#: src/metabase/sync/util.clj
+msgid "Completed {0} on {1}"
+msgstr "Realizadas {0} de {1}"
+
+#: src/metabase/sync/util.clj
+msgid "Start: {0}"
+msgstr "Inicio: {0}"
+
+#: src/metabase/sync/util.clj
+msgid "End: {0}"
+msgstr "Fin: {0}"
+
+#: src/metabase/sync/util.clj
+msgid "Duration: {0}"
+msgstr "Duración: {0}"
+
+#: src/metabase/sync/util.clj
+msgid "Completed step ''{0}''"
+msgstr "Realizado el paso ''{0}''"
+
+#: src/metabase/task.clj
+msgid "Loading tasks namespace:"
+msgstr "Cargando espacio de nombres para las tareas:"
+
+#: src/metabase/task.clj
+msgid "Starting Quartz Scheduler"
+msgstr "Iniciando Programador Quartz"
+
+#: src/metabase/task.clj
+msgid "Stopping Quartz Scheduler"
+msgstr "Parando Programador Quartz"
+
+#: src/metabase/task.clj
+msgid "Job already exists:"
+msgstr "Tarea ya existe:"
+
+#. 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
+#: src/metabase/util.clj
+msgid "Loading Metabase..."
+msgstr "Cargando Metabase..."
+
+#: src/metabase/util/date.clj
+msgid "Possible timezone conflict found on database {0}."
+msgstr "Posible conflicto de zona horaria en la base de datos {0}"
+
+#: src/metabase/util/date.clj
+msgid "JVM timezone is {0} and detected database timezone is {1}."
+msgstr "La zona horaria de la JVM es {0} y la zona horaria de la base de datos es {1}."
+
+#: src/metabase/util/date.clj
+msgid "Configure a report timezone to ensure proper date and time conversions."
+msgstr "Configura un informe con huso horario para comprobar las conversiones correctas de fecha y hora."
+
+#: src/metabase/util/embed.clj
+msgid "Secret key used to sign JSON Web Tokens for requests to `/api/embed` endpoints."
+msgstr "Clave secreta utilizada para firmar tokens web JSON para solicitudes al API `/api/embed`."
+
+#: src/metabase/util/encryption.clj
+msgid "MB_ENCRYPTION_SECRET_KEY must be at least 16 characters."
+msgstr "MB_ENCRYPTION_SECRET_KEY debe tener al menos 16 caracteres"
+
+#: src/metabase/util/encryption.clj
+msgid "Saved credentials encryption is ENABLED for this Metabase instance."
+msgstr "Guardar credenciales encriptadas está HABILITADO para esta instancia de Metabase."
+
+#: src/metabase/util/encryption.clj
+msgid "Saved credentials encryption is DISABLED for this Metabase instance."
+msgstr "Guardar credenciales encriptadas está DESHABILITADO para esta instancia de Metabase."
+
+#: src/metabase/util/encryption.clj
+msgid "nFor more information, see"
+msgstr "Para más información, ver"
+
+#: src/metabase/util/schema.clj
+msgid "value must be an integer."
+msgstr "el valor debe ser un entero."
+
+#: src/metabase/util/schema.clj
+msgid "value must be a string."
+msgstr "el valor debe ser un texto."
+
+#: src/metabase/util/schema.clj
+msgid "value must be a boolean."
+msgstr "el valor debe ser un booleano"
+
+#: src/metabase/util/schema.clj
+msgid "value must be a string that matches the regex `{0}`."
+msgstr "el valor debe ser un texto que cumpla la expresión regular `{0}`."
+
+#: src/metabase/util/schema.clj
+msgid "value must satisfy one of the following requirements: "
+msgstr "el valor debe satisfacer uno de los requerimientos siguientes:"
+
+#: src/metabase/util/schema.clj
+msgid "value may be nil, or if non-nil, {0}"
+msgstr "el valor puede ser nulo, o si no es nulo, {0}"
+
+#: src/metabase/util/schema.clj
+msgid "value must be one of: {0}."
+msgstr "el valor debe ser uno de: {0}."
+
+#: src/metabase/util/schema.clj
+msgid "value must be an array."
+msgstr "el valor debe ser un conjunto"
+
+#: src/metabase/util/schema.clj
+msgid "Each {0}"
+msgstr "Cada {0}"
+
+#: src/metabase/util/schema.clj
+msgid "The array cannot be empty."
+msgstr "El conjunto no puede estar vacío."
+
+#: src/metabase/util/schema.clj
+msgid "value must be a non-blank string."
+msgstr "el valor debe ser un texto no vacío"
+
+#: src/metabase/util/schema.clj
+msgid "Integer greater than zero"
+msgstr "Entero mayor que cero"
+
+#: src/metabase/util/schema.clj
+msgid "value must be an integer greater than zero."
+msgstr "el valor debe ser un entero mayor que cero."
+
+#: src/metabase/util/schema.clj
+msgid "Number greater than zero"
+msgstr "Número mayor que cero"
+
+#: src/metabase/util/schema.clj
+msgid "value must be a number greater than zero."
+msgstr "el valor debe ser un número mayor que cero."
+
+#: src/metabase/util/schema.clj
+msgid "Keyword or string"
+msgstr "Palabra clave o texto"
+
+#: src/metabase/util/schema.clj
+msgid "Valid field type"
+msgstr "Tipo de campo válido"
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid field type."
+msgstr "el valor debe ser un tipo de campo válido"
+
+#: src/metabase/util/schema.clj
+msgid "Valid field type (keyword or string)"
+msgstr "Tipo de campo válido (palabra clave o texto)"
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid field type (keyword or string)."
+msgstr "el valor debe ser un tipo de campo válido (palabra clave o texto)."
+
+#: src/metabase/util/schema.clj
+msgid "Valid entity type (keyword or string)"
+msgstr "Tipo de campo válido (palabra clave o texto)"
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid entity type (keyword or string)."
+msgstr "el valor debe ser un tipo de campo válido (palabra clave o texto)."
+
+#: src/metabase/util/schema.clj
+msgid "Valid map"
+msgstr "Mapa válido"
+
+#: src/metabase/util/schema.clj
+msgid "value must be a map."
+msgstr "el valor debe ser un mapa"
+
+#: src/metabase/util/schema.clj
+msgid "Valid email address"
+msgstr "Dirección de Email válido"
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid email address."
+msgstr "el valor debe ser una dirección de correo electrónico válida"
+
+#: src/metabase/util/schema.clj
+msgid "Insufficient password strength"
+msgstr "Insuficiente complejidad de contraseña"
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid integer."
+msgstr "el valor debe ser un entero válido"
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid integer greater than zero."
+msgstr "el valor debe ser un entero válido mayor que cero."
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid boolean string (''true'' or ''false'')."
+msgstr "el valor debe ser un booleano válido (''true'' o ''false'')."
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid JSON string."
+msgstr "el valor debe ser un texto JSON válido."
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid embedding params map."
+msgstr "el valor debe ser un mapa de parámetros de integración válido."
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsTabs.jsx:12
+msgid "Data permissions"
+msgstr "Permisos de acceso de datos"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsTabs.jsx:13
+msgid "Collection permissions"
+msgstr "Permisos de acceso de colecciones"
+
+#: frontend/src/metabase/admin/permissions/containers/CollectionPermissionsModal.jsx:56
+msgid "See all collection permissions"
+msgstr "Ver todos los permisos de colecciones"
+
+#: frontend/src/metabase/admin/permissions/containers/TogglePropagateAction.jsx:25
+msgid "Also change sub-collections"
+msgstr "También cambiar sub-colecciones"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:278
+msgid "Can edit this collection and its contents"
+msgstr "Puede editar esta colección y sus contenidos"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:285
+msgid "Can view items in this collection"
+msgstr "Puede ver artículos en esta colección"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:745
+msgid "Collection Access"
+msgstr "Acceso a Colección"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:821
+msgid "This group has permission to view at least one subcollection of this collection."
+msgstr "Este grupo tiene permiso para ver al menos una subcolección de esta colección."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:826
+msgid "This group has permission to edit at least one subcollection of this collection."
+msgstr "Este grupo tiene permiso para editar al menos una subcolección de esta colección."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:839
+msgid "View sub-collections"
+msgstr "Ver subcolección"
+
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:211
+msgid "Remember Me"
+msgstr "Recuerdame"
+
+#: frontend/src/metabase/components/BrowseApp.jsx:118
+msgid "X-ray this schema"
+msgstr "Aplica rayos-X a este esquema"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:246
+msgid "Edit the permissions for this collection"
+msgstr "Editar los permisos de acceso de esta colección"
+
+#: frontend/src/metabase/containers/AddToDashSelectDashModal.jsx:51
+msgid "Add this question to a dashboard"
+msgstr "Añadir esta pregunta a un cuadro de mando"
+
+#: frontend/src/metabase/containers/AddToDashSelectDashModal.jsx:61
+msgid "Create a new dashboard"
+msgstr "Crea un Cuadro de Mando"
+
+#: frontend/src/metabase/containers/ErrorPages.jsx:45
+msgid "The page you asked for couldn't be found."
+msgstr "La página que pidió no ha sido encontrada."
+
+#: frontend/src/metabase/containers/ItemSelect.jsx:30
+msgid "Select a {0}"
+msgstr "Seleccione un(a) {0}"
+
+#: frontend/src/metabase/containers/Overworld.jsx:188
+msgid "Save dashboards, questions, and collections in \"{0}\""
+msgstr "Guardar cuadros de mando, preguntas, y colecciones en \"{0}\""
+
+#: frontend/src/metabase/containers/Overworld.jsx:191
+msgid "Access dashboards, questions, and collections in \"{0}\""
+msgstr "Acceder cuadros de mando, preguntas, y colecciones en \"{0}\""
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:216
+msgid "Compare"
+msgstr "Compara"
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:224
+msgid "Zoom out"
+msgstr "Alejarse"
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:228
+msgid "Related"
+msgstr "Relacionado"
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:288
+msgid "More X-rays"
+msgstr "Más Rayos-X"
+
+#: frontend/src/metabase/home/containers/SearchApp.jsx:50
+msgid "No results"
+msgstr "No hay resultados"
+
+#: frontend/src/metabase/home/containers/SearchApp.jsx:51
+msgid "Metabase couldn't find any results for your search."
+msgstr "Metabase no pudo encontrar ningún resultado para su busqueda."
+
+#: frontend/src/metabase/new_query/containers/MetricSearch.jsx:111
+msgid "No metrics"
+msgstr "Métricas no encontradas"
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:31
+msgid "Aggregations"
+msgstr "Agregaciones"
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:32
+msgid "Operators"
+msgstr "Operadores"
+
+#: frontend/src/metabase/query_builder/components/expressions/Expressions.jsx:30
+msgid "Custom fields"
+msgstr "Campos personalizados"
+
+#. 2. Create the new collections.
+#: src/metabase/db/migrations.clj
+msgid "Migrated Dashboards"
+msgstr "Cuadros de Mando migrados"
+
+#: src/metabase/db/migrations.clj
+msgid "Migrated Pulses"
+msgstr "Pulsos migrados"
+
+#: src/metabase/db/migrations.clj
+msgid "Migrated Questions"
+msgstr "Preguntas migradas"
+
+#. 4. move everything not in this Collection to a new Collection
+#: src/metabase/db/migrations.clj
+msgid "Moving instances of {0} that aren't in a Collection to {1} Collection {2}"
+msgstr "Moviendo instancias de {0} que no están en ninguna colección a {1} Colección {2}"
+
+#: src/metabase/models/permissions.clj
+msgid "Failed to grant permissions: {0}"
+msgstr "Error al conceder permisos: {0}"
+
+#: src/metabase/util/encryption.clj
+msgid "Cannot decrypt encrypted string. Have you changed or forgot to set MB_ENCRYPTION_SECRET_KEY?"
+msgstr "No se ha podido desencriptar la cadena. ¿Has configurado correctamenteMB_ENCRYPTION_SECRET_KEY?"
+
+#: frontend/src/metabase/entities/collections.js:157
+msgid "All personal collections"
+msgstr "Todas las colecciones personales"
+
+#: src/metabase/driver.clj
+msgid "Host"
+msgstr "Servidor"
+
+#: src/metabase/driver.clj
+msgid "Port"
+msgstr "Puerto"
+
+#: src/metabase/driver.clj
+msgid "Database username"
+msgstr "nombre de usuario de base de datos"
+
+#: src/metabase/driver.clj
+msgid "What username do you use to login to the database?"
+msgstr "¿Qué nombre de usuario usas para iniciar sesión en la base de datos?"
+
+#: src/metabase/driver.clj
+msgid "Database password"
+msgstr "Contraseña de la base de datos"
+
+#: src/metabase/driver.clj
+msgid "Database name"
+msgstr "nombre de la base de datos"
+
+#: src/metabase/driver.clj
+msgid "birds_of_the_world"
+msgstr "aves_del_mundo"
+
+#: src/metabase/driver.clj
+msgid "Use a secure connection (SSL)?"
+msgstr "¿Utilizar una conexión segura (SSL)?"
+
+#: src/metabase/driver.clj
+msgid "Additional JDBC connection string options"
+msgstr "Opciones adicionales de cadena de conexión JDBC"
+
+#: src/metabase/driver/bigquery.clj
+msgid "Project ID"
+msgstr "ID de proyecto"
+
+#: src/metabase/driver/bigquery.clj
+msgid "praxis-beacon-120871"
+msgstr "praxis-beacon-120871"
+
+#: src/metabase/driver/bigquery.clj
+msgid "Dataset ID"
+msgstr "Conjunto de datos ID"
+
+#: src/metabase/driver/bigquery.clj
+msgid "toucanSightings"
+msgstr "avistamientosDeTucanes"
+
+#: src/metabase/driver/bigquery.clj src/metabase/driver/googleanalytics.clj
+msgid "Client ID"
+msgstr "ID de cliente"
+
+#: src/metabase/driver/bigquery.clj src/metabase/driver/googleanalytics.clj
+msgid "Client Secret"
+msgstr "Clave secreta del cliente"
+
+#: src/metabase/driver/bigquery.clj src/metabase/driver/googleanalytics.clj
+msgid "Auth Code"
+msgstr "Código de Autorización"
+
+#: src/metabase/driver/crate.clj
+msgid "Hosts"
+msgstr "Servidores"
+
+#: src/metabase/driver/druid.clj
+msgid "Broker node port"
+msgstr "Puerto de nodo intermediario"
+
+#: src/metabase/driver/googleanalytics.clj
+msgid "Google Analytics Account ID"
+msgstr "ID de cuenta de Google Analytics"
+
+#: src/metabase/driver/h2.clj
+msgid "Connection String"
+msgstr "Cadena de conexión"
+
+#: src/metabase/driver/h2.clj
+msgid "Users/camsaul/bird_sightings/toucans"
+msgstr "Users/camsaul/avistamientos_de_aves/tucanes"
+
+#: src/metabase/driver/mongo.clj
+msgid "carrierPigeonDeliveries"
+msgstr "entregasPorPalomasMensajeras"
+
+#: src/metabase/driver/mongo.clj
+msgid "Authentication Database"
+msgstr "base de datos para autenticación"
+
+#: src/metabase/driver/mongo.clj
+msgid "Optional database to use when authenticating"
+msgstr "base de datos opcional para la autenticación"
+
+#: src/metabase/driver/mongo.clj
+msgid "Additional Mongo connection string options"
+msgstr "Opciones adicionales de cadena de conexión Mongo"
+
+#: src/metabase/driver/oracle.clj
+msgid "Oracle system ID (SID)"
+msgstr "ID del sistema Oracle (SID)"
+
+#: src/metabase/driver/oracle.clj
+msgid "Usually something like ORCL or XE."
+msgstr "Generalmente algo como ORCL o XE"
+
+#: src/metabase/driver/oracle.clj
+msgid "Optional if using service name"
+msgstr "Opcional cuando se usa nombre del servicio"
+
+#: src/metabase/driver/oracle.clj
+msgid "Oracle service name"
+msgstr "Nombre del servicio Oracle"
+
+#: src/metabase/driver/oracle.clj
+msgid "Optional TNS alias"
+msgstr "Alias TNS (opcional)"
+
+#: src/metabase/driver/presto.clj
+msgid "hive"
+msgstr "hive"
+
+#: src/metabase/driver/redshift.clj
+msgid "my-cluster-name.abcd1234.us-east-1.redshift.amazonaws.com"
+msgstr "nombre-de-mi-cluster.abcd1234.us-east-1.redshift.amazonaws.com"
+
+#: src/metabase/driver/redshift.clj
+msgid "toucan_sightings"
+msgstr "avistamientos_de_tucanes"
+
+#: src/metabase/driver/sparksql.clj
+msgid "default"
+msgstr "por defecto"
+
+#: src/metabase/driver/sqlite.clj
+msgid "Filename"
+msgstr "Nombre del archivo"
+
+#: src/metabase/driver/sqlite.clj
+msgid "/home/camsaul/toucan_sightings.sqlite 😋"
+msgstr "/home/camsaul/avistamientos_de_tucanes.sqlite"
+
+#: src/metabase/driver/sqlserver.clj
+msgid "BirdsOfTheWorld"
+msgstr "AvesDelMundo"
+
+#: src/metabase/driver/sqlserver.clj
+msgid "Database instance name"
+msgstr "Nombre de la instancia de base de datos"
+
+#: src/metabase/driver/sqlserver.clj
+msgid "N/A"
+msgstr "No aplica"
+
+#: src/metabase/driver/sqlserver.clj
+msgid "Windows domain"
+msgstr "Dominio de Windows"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:407
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:413
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:422
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:428
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:437
+msgid "Labels"
+msgstr "Etiquetas"
+
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:329
+msgid "Add members"
+msgstr "Añadir miembros"
+
+#: frontend/src/metabase/entities/collections.js:108
+msgid "Collection it's saved in"
+msgstr "Colección que se guarda en"
+
+#: frontend/src/metabase/lib/groups.js:4
+msgid "All Users"
+msgstr "Todos los Usuarios"
+
+#: frontend/src/metabase/lib/groups.js:5
+msgid "Administrators"
+msgstr "Administradores"
+
+#: frontend/src/metabase/lib/groups.js:6
+msgid "MetaBot"
+msgstr "MetaBot"
+
+#: frontend/src/metabase/public/components/widgets/EmbedModalContent.jsx:290
+msgid "Sharing"
+msgstr "Compartir"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:156
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:169
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:177
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:200
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:206
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:217
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:233
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:81
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:71
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:76
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:82
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:41
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:47
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:72
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:85
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:98
+msgid "Display"
+msgstr "Visualización"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:283
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:316
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:329
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:344
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:356
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:362
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:370
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:400
+msgid "Axes"
+msgstr "Ejes"
+
+#: frontend/src/metabase/visualizations/visualizations/Table.jsx:93
+msgid "Formatting"
+msgstr "Formato"
+
+#: frontend/src/metabase/containers/Overworld.jsx:105
+msgid "Try these x-rays based on your data."
+msgstr "Pruebe estos rayos-X en función de tus datos."
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:35
+msgid "There was a problem displaying this chart."
+msgstr "Hubo un problema al mostrar esta gráfico."
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:36
+msgid "Sorry, you don't have permission to see this card."
+msgstr "Lo siento, no tienes permiso para ver etsa tarjeta."
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:51
+msgid "Just a heads up:"
+msgstr "Solo un aviso:"
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:59
+msgid "{0} without the Sample Dataset, the Query Builder tutorial won't work. You can always restore the Sample Dataset, but any questions you've saved using this data will be lost."
+msgstr "{0} sin el conjunto de datos de muestra, el tutorial del generador de consultas no funcionará. Siempre puede restaurar el conjunto de datos de muestra, pero cualquier pregunta que haya guardado con esta información se perderá"
+
+#: frontend/src/metabase/qb/components/drill/AutomaticDashboardDrill.jsx:27
+msgid "X-ray"
+msgstr "Aplica rayos-X"
+
+#: frontend/src/metabase/qb/components/drill/CompareToRestDrill.js:27
+msgid "Compare to the rest"
+msgstr "Compara con el resto"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:247
+msgid "Use the Java Virtual Machine (JVM) timezone"
+msgstr "Utiliza la zona horaria de la máquina virtual Java"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:249
+msgid "We suggest you leave this off unless you're doing manual timezone casting in\n"
+"many or most of your queries with this data."
+msgstr "Te sugerimos que desactives esto a menos que estés forzando manualmente la zona horaria en\n"
+"muchas o la mayoría de tus consultas con estos datos."
+
+#: frontend/src/metabase/containers/Overworld.jsx:313
+msgid "Your team's most important dashboards go here"
+msgstr "Los cuadros de mando más importantes van aquí"
+
+#: frontend/src/metabase/containers/Overworld.jsx:314
+msgid "Pin dashboards in {0} to have them appear in this space for everyone"
+msgstr "Fija los cuadros de mando en {0} para que aparezcan en este espacio para todos"
+
+#: src/metabase/db.clj
+msgid "Unable to release the Liquibase lock after a migration failure"
+msgstr "No se ha podido eliminar el bloqueo Liquibase tras un error en la migración"
+
+#: src/metabase/driver/bigquery.clj
+msgid "Use JVM Time Zone"
+msgstr "Utiliza la zona horaria del JVM"
+
diff --git a/locales/fr.po b/locales/fr.po
new file mode 100644
index 0000000000000000000000000000000000000000..a7ccd4ce4d8d00a2e92282b81fd81c91b5548dc0
--- /dev/null
+++ b/locales/fr.po
@@ -0,0 +1,9316 @@
+msgid ""
+msgstr ""
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: POEditor.com\n"
+"Project-Id-Version: Metabase\n"
+"Language: fr\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#: frontend/src/metabase/admin/databases/components/CreatedDatabaseModal.jsx:19
+msgid "Your database has been added!"
+msgstr "Votre base de données a été ajoutée !"
+
+#: frontend/src/metabase/admin/databases/components/CreatedDatabaseModal.jsx:22
+msgid "We took a look at your data, and we have some automated explorations that we can show you!"
+msgstr "Nous avons jeté un œil à vos données et nous avons des explorations automatiques à vous présenter !"
+
+#: frontend/src/metabase/admin/databases/components/CreatedDatabaseModal.jsx:27
+msgid "I'm good thanks"
+msgstr "Je vais bien, merci"
+
+#: frontend/src/metabase/admin/databases/components/CreatedDatabaseModal.jsx:32
+msgid "Explore this data"
+msgstr "Explorer ces données"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseEditForms.jsx:42
+msgid "Select a database type"
+msgstr "Sélectionnez un type de base de données"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseEditForms.jsx:75
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:170
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:435
+#: frontend/src/metabase/admin/permissions/containers/CollectionPermissionsModal.jsx:71
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:182
+#: frontend/src/metabase/components/ActionButton.jsx:51
+#: frontend/src/metabase/components/ButtonWithStatus.jsx:7
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:180
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:197
+#: frontend/src/metabase/reference/components/EditHeader.jsx:54
+#: frontend/src/metabase/reference/components/EditHeader.jsx:69
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:171
+#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:164
+msgid "Save"
+msgstr "Sauvegarder"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:122
+msgid "To do some of its magic, Metabase needs to scan your database. We will also rescan it periodically to keep the metadata up-to-date. You can control when the periodic rescans happen below."
+msgstr "Metabase doit analyser votre base de données. Nous procéderons à une nouvelle analyse périodique pour maintenir les métadonnées à jour. Vous pouvez contrôler ci dessous le moment où ces analyses périodiques auront lieu."
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:127
+msgid "Database syncing"
+msgstr "Synchronisation de la base de données"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:128
+msgid "This is a lightweight process that checks for\n"
+"updates to this database’s schema. In most cases, you should be fine leaving this\n"
+"set to sync hourly."
+msgstr "Ceci est un processus léger qui vérifie les\n"
+"mises à jour du schéma de données. Dans la plupart des cas, vous pouvez l'effectuer chaque heure."
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:147
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:184
+msgid "Scan"
+msgstr "Analyser"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:152
+msgid "Scanning for Filter Values"
+msgstr "Analyse des valeurs de filtre"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:153
+msgid "Metabase can scan the values present in each\n"
+"field in this database to enable checkbox filters in dashboards and questions. This\n"
+"can be a somewhat resource-intensive process, particularly if you have a very large\n"
+"database."
+msgstr "Metabase peut analyser les champs de cette base de données pour en proposer les valeurs dans les filtres des tableaux de bord et des questions. Cette analyse peut être un processus gourmand en ressources, surtout si vous avez une très grande base de données."
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:159
+msgid "When should Metabase automatically scan and cache field values?"
+msgstr "Quand Metabase doit-il automatiquement analyser et mettre en cache les valeurs des champs ?"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:164
+msgid "Regularly, on a schedule"
+msgstr "Régulièrement, selon un planning"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:195
+msgid "Only when adding a new filter widget"
+msgstr "Uniquement lors de l'ajout d'un nouveau filtre"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:199
+msgid "When a user adds a new filter to a dashboard or a SQL question, Metabase will\n"
+"scan the field(s) mapped to that filter in order to show the list of selectable values."
+msgstr "Lorsqu'un utilisateur ajoute un nouveau filtre à un tableau de bord ou à une question SQL, Metabase analysera le(s) champ(s) associé(s) à ce filtre pour pouvoir proposer des valeurs."
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:210
+msgid "Never, I'll do this manually if I need to"
+msgstr "Jamais, je le ferai manuellement au besoin"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:222
+#: frontend/src/metabase/admin/settings/components/SettingsBatchForm.jsx:27
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:222
+#: frontend/src/metabase/components/ActionButton.jsx:52
+#: frontend/src/metabase/components/ButtonWithStatus.jsx:8
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:429
+msgid "Saving..."
+msgstr "Sauvegarde..."
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:38
+#: frontend/src/metabase/components/form/FormMessage.jsx:4
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:144
+msgid "Server error encountered"
+msgstr "Erreur serveur"
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:54
+msgid "Delete this database?"
+msgstr "Supprimer la base de données ?"
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:62
+msgid "All saved questions, metrics, and segments that rely on this database will be lost."
+msgstr "Toutes les questions, métriques et segments sauvegardés qui dépendent de cette base de données seront perdus."
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:63
+msgid "This cannot be undone."
+msgstr "Cette action ne peut pas être annulée."
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:66
+msgid "If you're sure, please type"
+msgstr "Si vous êtes sûr(e), veuillez saisir"
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:66
+msgid "DELETE"
+msgstr "SUPPRIMER"
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:67
+msgid "in this box:"
+msgstr "dans cette case :"
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:78
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:50
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:87
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:93
+#: frontend/src/metabase/admin/people/components/AddRow.jsx:27
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:250
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:302
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:322
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:343
+#: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:49
+#: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:52
+#: frontend/src/metabase/admin/permissions/containers/CollectionPermissionsModal.jsx:58
+#: frontend/src/metabase/admin/permissions/selectors.js:156
+#: frontend/src/metabase/admin/permissions/selectors.js:166
+#: frontend/src/metabase/admin/permissions/selectors.js:181
+#: frontend/src/metabase/admin/permissions/selectors.js:220
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:355
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:181
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:247
+#: frontend/src/metabase/components/ConfirmContent.jsx:18
+#: frontend/src/metabase/components/DeleteModalWithConfirm.jsx:72
+#: frontend/src/metabase/components/HeaderModal.jsx:49
+#: frontend/src/metabase/components/form/StandardForm.jsx:55
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:196
+#: frontend/src/metabase/dashboard/components/AddSeriesModal.jsx:289
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:162
+#: frontend/src/metabase/dashboard/components/RemoveFromDashboardModal.jsx:42
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:189
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:192
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:352
+#: frontend/src/metabase/query_builder/components/RunButton.jsx:24
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:83
+#: frontend/src/metabase/query_builder/containers/ArchiveQuestionModal.jsx:48
+#: frontend/src/metabase/reference/components/EditHeader.jsx:34
+#: frontend/src/metabase/reference/components/RevisionMessageModal.jsx:52
+#: frontend/src/metabase/visualizations/components/ChartSettings.jsx:209
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:397
+msgid "Cancel"
+msgstr "Annuler"
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:84
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:123
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:397
+msgid "Delete"
+msgstr "Supprimer"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:128
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:76
+#: frontend/src/metabase/admin/permissions/selectors.js:316
+#: frontend/src/metabase/admin/permissions/selectors.js:323
+#: frontend/src/metabase/admin/permissions/selectors.js:419
+#: frontend/src/metabase/admin/routes.jsx:39
+#: frontend/src/metabase/nav/containers/Navbar.jsx:215
+#: frontend/src/metabase/reference/databases/DatabaseSidebar.jsx:18
+#: frontend/src/metabase/reference/databases/TableSidebar.jsx:18
+msgid "Databases"
+msgstr "Bases de données"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:129
+msgid "Add Database"
+msgstr "Ajouter une base de données"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:60
+msgid "Connection"
+msgstr "Connexion"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:61
+msgid "Scheduling"
+msgstr "Planification"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:170
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:78
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:84
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:253
+#: frontend/src/metabase/admin/settings/components/SettingsBatchForm.jsx:26
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:221
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:182
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:354
+#: frontend/src/metabase/reference/components/RevisionMessageModal.jsx:47
+msgid "Save changes"
+msgstr "Sauvegarder les modifications"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:185
+#: frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx:38
+#: frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx:38
+msgid "Actions"
+msgstr "Actions"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:193
+msgid "Sync database schema now"
+msgstr "Synchroniser le schéma de base de données"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:194
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:206
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:788
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:796
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:112
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:120
+msgid "Starting…"
+msgstr "Démarrage..."
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:195
+msgid "Failed to sync"
+msgstr "Échec de la synchronisation"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:196
+msgid "Sync triggered!"
+msgstr "Synchronisation déclenchée !"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:205
+msgid "Re-scan field values now"
+msgstr "Analyser à nouveau les valeurs des filtres maintenant"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:207
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:789
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:113
+msgid "Failed to start scan"
+msgstr "Impossible de démarrer l'analyse"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:208
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:790
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:114
+msgid "Scan triggered!"
+msgstr "Analyse déclenchée !"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:215
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:399
+msgid "Danger Zone"
+msgstr "Zone Dangereuse"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:221
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:224
+msgid "Discard saved field values"
+msgstr "Supprimer les valeurs mises en cache"
+
+#. Personnellement la première fois que j'ai voulu supprimer une base, j'aurais aimé que soit mentionné le fait que seules les métadonnées sont supprimées, et pas les données.
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:239
+msgid "Remove this database"
+msgstr "Retirer cette base de données de Metabase"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:75
+msgid "Add database"
+msgstr "Ajouter une base de données"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:87
+#: frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx:36
+#: frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx:36
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:468
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:183
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:91
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:402
+#: frontend/src/metabase/containers/EntitySearch.jsx:26
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:218
+#: frontend/src/metabase/entities/collections.js:86
+#: frontend/src/metabase/entities/dashboards.js:96
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:461
+#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:62
+msgid "Name"
+msgstr "Nom"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:88
+msgid "Engine"
+msgstr "Moteur"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:117
+msgid "Deleting..."
+msgstr "Suppression ..."
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:147
+msgid "Loading ..."
+msgstr "Chargement ..."
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:163
+msgid "Bring the sample dataset back"
+msgstr "Charger de nouveau le jeu de données d'exemple"
+
+#: frontend/src/metabase/admin/databases/database.js:175
+msgid "Couldn't connect to the database. Please check the connection details."
+msgstr "Impossible de se connecter à la base de données. Veuillez vérifier les paramètres de connexion."
+
+#: frontend/src/metabase/admin/databases/database.js:383
+msgid "Successfully created!"
+msgstr "Création réussie!"
+
+#: frontend/src/metabase/admin/databases/database.js:393
+msgid "Successfully saved!"
+msgstr "Sauvegarde réussie!"
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectActionSelect.jsx:44
+#: frontend/src/metabase/dashboard/components/DashCard.jsx:276
+#: frontend/src/metabase/parameters/components/ParameterWidget.jsx:173
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:209
+#: frontend/src/metabase/reference/components/EditButton.jsx:18
+msgid "Edit"
+msgstr "Modifier"
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectActionSelect.jsx:59
+msgid "Revision History"
+msgstr "Historique des modifications"
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:33
+msgid "Retire this {0}?"
+msgstr "Retirer ce {0}?"
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:38
+msgid "Saved questions and other things that depend on this {0} will continue to work, but this {1} will no longer be selectable from the query builder."
+msgstr "Les questions sauvegardées et autres éléments dépendants de ce {0} continueront de fonctionner, mais ce {1} ne sera plus sélectionnable dans le créateur de requêtes."
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:39
+msgid "If you're sure you want to retire this {0}, please write a quick explanation of why it's being retired:"
+msgstr "Si vous êtes sûr de vouloir retirer ce {0}, veuillez écrire les raisons pour lesquelles il est retiré :"
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:43
+msgid "This will show up in the activity feed and in an email that will be sent to anyone on your team who created something that uses this {0}."
+msgstr "Le retrait de ce {0} apparaîtra dans le flux d'activités et dans un courriel envoyé à tous les utilisateurs l'ayant utilisé."
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:58
+msgid "Retire"
+msgstr "Retirer"
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:59
+msgid "Retiring…"
+msgstr "Retrait..."
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:60
+msgid "Failed"
+msgstr "Échec"
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:61
+msgid "Success"
+msgstr "Succès"
+
+#: frontend/src/metabase/admin/datamodel/components/PartialQueryBuilder.jsx:118
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:110
+msgid "Preview"
+msgstr "Prévisualisation"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnItem.jsx:97
+msgid "No column description yet"
+msgstr "Aucune description pour cette colonne"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnItem.jsx:133
+msgid "Select a field visibility"
+msgstr "Sélectionner une visibilité de champ"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnItem.jsx:189
+msgid "No special type"
+msgstr "Aucun type particulier"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnItem.jsx:190
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:34
+#: frontend/src/metabase/reference/components/Field.jsx:57
+#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:42
+msgid "Other"
+msgstr "Autre"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnItem.jsx:208
+msgid "Select a special type"
+msgstr "Sélectionner un type"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnItem.jsx:222
+msgid "Select a target"
+msgstr "Sélectionner une cible"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnsList.jsx:17
+msgid "Columns"
+msgstr "Colonnes"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnsList.jsx:22
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataSchema.jsx:44
+msgid "Column"
+msgstr "Colonne"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnsList.jsx:24
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:121
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:203
+msgid "Visibility"
+msgstr "Visibilité"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnsList.jsx:25
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:214
+msgid "Type"
+msgstr "Type"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataHeader.jsx:87
+msgid "Current database:"
+msgstr "Base de données actuelle :"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataHeader.jsx:92
+msgid "Show original schema"
+msgstr "Montrer le schéma original"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataSchema.jsx:45
+msgid "Data Type"
+msgstr "Type de donnée"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataSchema.jsx:46
+msgid "Additional Info"
+msgstr "Informations additionnelles"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataSchemaList.jsx:44
+msgid "Find a schema"
+msgstr "Trouver un schéma"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataSchemaList.jsx:51
+msgid "{0} schema"
+msgid_plural "{0} schemas"
+msgstr[0] "{0} schéma"
+msgstr[1] "{0} schémas"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:82
+msgid "Why Hide?"
+msgstr "Pourquoi masquer ?"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:83
+msgid "Technical Data"
+msgstr "Donnée technique"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:84
+msgid "Irrelevant/Cruft"
+msgstr "Non pertinent"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:90
+msgid "Queryable"
+msgstr "Interrogeable"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:91
+msgid "Hidden"
+msgstr "Masqué"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:117
+msgid "No table description yet"
+msgstr "Aucune description pour cette table"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:124
+msgid "Metadata Strength"
+msgstr "Force des métadonnées"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTableList.jsx:87
+msgid "{0} Queryable Table"
+msgid_plural "{0} Queryable Tables"
+msgstr[0] "{0} Table interrogeable"
+msgstr[1] "{0} Tables interrogeables"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTableList.jsx:96
+msgid "{0} Hidden Table"
+msgid_plural "{0} Hidden Tables"
+msgstr[0] "{0} Table cachée"
+msgstr[1] "{0} Tables cachées"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTableList.jsx:113
+msgid "Find a table"
+msgstr "Trouver une table"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTableList.jsx:126
+msgid "Schemas"
+msgstr "Schémas"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx:24
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:137
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:33
+#: frontend/src/metabase/reference/guide/BaseSidebar.jsx:27
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:189
+#: frontend/src/metabase/reference/metrics/MetricList.jsx:56
+#: frontend/src/metabase/reference/metrics/MetricSidebar.jsx:18
+#: frontend/src/metabase/routes.jsx:231
+msgid "Metrics"
+msgstr "Métriques"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx:30
+msgid "Add a Metric"
+msgstr "Ajouter une métrique"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx:37
+#: frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx:37
+#: frontend/src/metabase/query_builder/components/QueryDefinitionTooltip.jsx:30
+msgid "Definition"
+msgstr "Définition"
+
+#. Bonjour François, ici il semble s'agir de la liste déroulante 'View' du générateur de requête
+#: frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx:54
+msgid "Create metrics to add them to the View dropdown in the query builder"
+msgstr "Créer des métriques pour les ajouter au menu déroulant 'Vue' du générateur de requêtes"
+
+#: frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx:24
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:922
+#: frontend/src/metabase/reference/guide/BaseSidebar.jsx:33
+#: frontend/src/metabase/reference/segments/SegmentFieldSidebar.jsx:19
+#: frontend/src/metabase/reference/segments/SegmentList.jsx:56
+#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:18
+msgid "Segments"
+msgstr "Segments"
+
+#: frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx:30
+msgid "Add a Segment"
+msgstr "Ajouter un segment"
+
+#: frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx:54
+msgid "Create segments to add them to the Filter dropdown in the query builder"
+msgstr "Créer des segments pour les ajouter à la liste déroulante Filtre dans le générateur de requêtes"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/Revision.jsx:24
+msgid "created"
+msgstr "créé"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/Revision.jsx:27
+msgid "reverted to a previous version"
+msgstr "retour à une version précédente"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/Revision.jsx:33
+msgid "edited the title"
+msgstr "modifier le titre"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/Revision.jsx:35
+msgid "edited the description"
+msgstr "modifier la description"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/Revision.jsx:37
+msgid "edited the "
+msgstr "modifier le "
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/Revision.jsx:40
+msgid "made some changes"
+msgstr "a fait des modifications"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/Revision.jsx:46
+#: frontend/src/metabase/home/components/Activity.jsx:80
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:343
+msgid "You"
+msgstr "Vous"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/RevisionHistory.jsx:37
+msgid "Datamodel"
+msgstr "Modèle de données"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/RevisionHistory.jsx:43
+msgid " History"
+msgstr " Historique"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/RevisionHistory.jsx:48
+msgid "Revision History for"
+msgstr "Historique de modification pour"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:185
+msgid "{0} – Field Settings"
+msgstr "{0} – Réglages du champ"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:204
+msgid "Where this field will appear throughout Metabase"
+msgstr "Où ce champ apparaîtra dans Metabase"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:226
+msgid "Filtering on this field"
+msgstr "Filtrer sur ce champ"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:227
+msgid "When this field is used in a filter, what should people use to enter the value they want to filter on?"
+msgstr "Lorsque ce champ est utilisé dans un filtre, que doivent faire les utilisateurs pour saisir la valeur à filtrer?"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:334
+msgid "No description for this field yet"
+msgstr "Aucune description pour ce champ"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:413
+msgid "Original value"
+msgstr "Valeur d'origine"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:414
+msgid "Mapped value"
+msgstr "Valeur correspondante"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:457
+msgid "Enter value"
+msgstr "Saisir une valeur"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:480
+msgid "Use original value"
+msgstr "Utiliser la valeur d'origine"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:481
+msgid "Use foreign key"
+msgstr "Utiliser une clé étrangère"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:482
+msgid "Custom mapping"
+msgstr "Correspondance personnalisée"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:510
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:610
+msgid "Unrecognized mapping type"
+msgstr "Type de correspondance non reconnu"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:544
+msgid "Current field isn't a foreign key or FK target table metadata is missing"
+msgstr "Le champ actuel n'est pas une clé étrangère ou les métadonnées de la table cible de la clé étrangère sont manquantes"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:641
+msgid "The selected field isn't a foreign key"
+msgstr "Le champ sélectionné n'est pas une clé étrangère"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:703
+msgid "Display values"
+msgstr "Afficher les valeurs"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:704
+msgid "Choose to show the original value from the database, or have this field display associated or custom information."
+msgstr "Choisir d'afficher la valeur originale dans la base de données, ou une information associée ou personnalisée."
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:730
+msgid "Choose a field"
+msgstr "Choisissez un champ"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:751
+msgid "Please select a column to use for display."
+msgstr "S'il vous plaît, sélectionnez une colonne à utiliser pour l'affichage"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:771
+msgid "Tip:"
+msgstr "Astuce :"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:772
+msgid "You might want to update the field name to make sure it still makes sense based on your remapping choices."
+msgstr "Il se peut que vous vouliez mettre à jour le nom du champ pour s'assurer qu'il est toujours pertinent selon vos choix de mise en corespondance."
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:781
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:105
+msgid "Cached field values"
+msgstr "Valeurs des filtres mises en cache"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:782
+msgid "Metabase can scan the values for this field to enable checkbox filters in dashboards and questions."
+msgstr "Metabase peut analyser les valeurs de ce champ pour proposer des filtres dans les tableaux de bord et les questions."
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:787
+msgid "Re-scan this field"
+msgstr "Analyse à nouveau ce champ"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:795
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:119
+msgid "Discard cached field values"
+msgstr "Supprimer les valeurs des filtres du cache"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:797
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:121
+msgid "Failed to discard values"
+msgstr "Impossible de supprimer les valeurs"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:798
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:122
+msgid "Discard triggered!"
+msgstr "Suppression lancée"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetadataEditorApp.jsx:105
+msgid "Select any table to see its schema and add or edit metadata."
+msgstr "Sélectionner une table pour voir son schéma et enrichir ses métadonnées"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:37
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:34
+#: frontend/src/metabase/entities/collections.js:89
+msgid "Name is required"
+msgstr "Le nom est requis"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:40
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:37
+msgid "Description is required"
+msgstr "La description est requise"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:44
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:41
+msgid "Revision message is required"
+msgstr "Un message d'historique est requis"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:50
+msgid "Aggregation is required"
+msgstr "Une agrégation est requise."
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:110
+msgid "Edit Your Metric"
+msgstr "Éditer votre métrique"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:111
+msgid "Create Your Metric"
+msgstr "Créer votre métrique"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:115
+msgid "Make changes to your metric and leave an explanatory note."
+msgstr "Modifier votre métrique et laisser un commentaire pour ces changements."
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:116
+msgid "You can create saved metrics to add a named metric option to this table. Saved metrics include the aggregation type, the aggregated field, and optionally any filter you add. As an example, you might use this to create something like the official way of calculating \"Average Price\" for an Orders table."
+msgstr "Sauvegardez des métriques pour les rendre disponibles dans l'option de Vue de cette table. Les métriques se définissent par un type d'agrégation, un champ aggrégé, et optionnellement tous filtres que vous y ajouterez. Par exemple, vous pourriez vouloir créer une métrique pour définir la façon officielle de calculer le \"Prix moyen\" d'une table qui contient des Commandes."
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:149
+msgid "Result: "
+msgstr "Résultat : "
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:157
+msgid "Name Your Metric"
+msgstr "Nommez votre métrique"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:158
+msgid "Give your metric a name to help others find it."
+msgstr "Donnez un nom à votre métrique pour aider les autres à la retrouver."
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:162
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:166
+msgid "Something descriptive but not too long"
+msgstr "Description syntétique"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:166
+msgid "Describe Your Metric"
+msgstr "Décrivez votre métrique"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:167
+msgid "Give your metric a description to help others understand what it's about."
+msgstr "Donnez une description à votre métrique pour aider les autres à mieux l'utiliser."
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:171
+msgid "This is a good place to be more specific about less obvious metric rules"
+msgstr "C'est le bon endroit pour expliquer plus précisement à quoi sert votre métrique et comment vous l'avez construite."
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:175
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:179
+msgid "Reason For Changes"
+msgstr "Raison des modifications"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:177
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:181
+msgid "Leave a note to explain what changes you made and why they were required."
+msgstr "Laisser un mot d'explication sur les changements que vous avez fait et pourquoi ils étaient nécessaires."
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:181
+msgid "This will show up in the revision history for this metric to help everyone remember why things changed"
+msgstr "Sera affiché dans l'historique des révisions de cette métrique afin d'aider tout le monde à se souvenir des changements effectués."
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:49
+msgid "At least one filter is required"
+msgstr "Un filter minimum est requis"
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:116
+msgid "Edit Your Segment"
+msgstr "Modifier votre segment"
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:117
+msgid "Create Your Segment"
+msgstr "Créez votre segment"
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:121
+msgid "Make changes to your segment and leave an explanatory note."
+msgstr "Faites des changement à votre segment et laissez une note explicative"
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:122
+msgid "Select and add filters to create your new segment for the {0} table"
+msgstr "Sélectionnez et ajoutez des filtres pour créer un nouveau segement pour la table {0}"
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:161
+msgid "Name Your Segment"
+msgstr "Nommez votre segment"
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:162
+msgid "Give your segment a name to help others find it."
+msgstr "Donnez un nom à votre segment pour aider les utilisateurs à le retrouver."
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:170
+msgid "Describe Your Segment"
+msgstr "Décrivez votre segment"
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:171
+msgid "Give your segment a description to help others understand what it's about."
+msgstr "Donner une description à votre segment pour aider les autres à mieux l'utiliser."
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:175
+msgid "This is a good place to be more specific about less obvious segment rules"
+msgstr "C'est le bon endroit pour être plus spécifique sur les règles de segment moins évidentes"
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:185
+msgid "This will show up in the revision history for this segment to help everyone remember why things changed"
+msgstr "Ce sera affiché dans l'historique des révisions pour ce segement, afin d'aider tout le monde à se souvenir pour ça a été changé"
+
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:91
+#: frontend/src/metabase/admin/routes.jsx:79
+#: frontend/src/metabase/admin/settings/containers/SettingsEditorApp.jsx:266
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:96
+#: frontend/src/metabase/nav/containers/Navbar.jsx:200
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorSidebar.jsx:99
+msgid "Settings"
+msgstr "Paramètres"
+
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:106
+msgid "Metabase can scan the values in this table to enable checkbox filters in dashboards and questions."
+msgstr "Metabase peut analyser les valeurs des champs de cette table pour activer les filtres de case à cocher dans les tableaux de bord et les questions."
+
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:111
+msgid "Re-scan this table"
+msgstr "Analyser à nouveau cette table"
+
+#: frontend/src/metabase/admin/people/components/AddRow.jsx:34
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:194
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:253
+#: frontend/src/metabase/dashboard/components/DashCard.jsx:276
+msgid "Add"
+msgstr "Ajouter"
+
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:80
+#: frontend/src/metabase/setup/components/UserStep.jsx:103
+#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:67
+msgid "Not a valid formatted email address"
+msgstr "Format de l'adresse électronique invalide"
+
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:135
+#: frontend/src/metabase/setup/components/UserStep.jsx:186
+#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:100
+msgid "First name"
+msgstr "Prénom"
+
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:156
+#: frontend/src/metabase/setup/components/UserStep.jsx:203
+#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:117
+msgid "Last name"
+msgstr "Nom"
+
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:178
+#: frontend/src/metabase/auth/containers/ForgotPasswordApp.jsx:77
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:158
+#: frontend/src/metabase/components/NewsletterForm.jsx:94
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:470
+#: frontend/src/metabase/setup/components/UserStep.jsx:222
+#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:138
+msgid "Email address"
+msgstr "Adresse électronique"
+
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:202
+msgid "Permission Groups"
+msgstr "Groupes de permissions"
+
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:238
+msgid "Make this user an admin"
+msgstr "Faire de cet utilisateur un administrateur"
+
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:32
+msgid "All users belong to the {0} group and can't be removed from it. Setting permissions for this group is a great way to\n"
+"make sure you know what new Metabase users will be able to see."
+msgstr "Tous les utilisateurs appartiennent au groupe {0} et ne peuvent pas en être supprimé. Les réglages de permission pour ce groupe sont un bon moyen de s'assurer de ce que les nouveaux utilisateurs de Metabase verront."
+
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:41
+msgid "This is a special group whose members can see everything in the Metabase instance, and who can access and make changes to the\n"
+"settings in the Admin Panel, including changing permissions! So, add people to this group with care."
+msgstr "Il s’agit d’un groupe spécial dont les membres peuvent tout voir dans l’instance Metabase et qui peut accéder et apporter des modifications aux paramètres du panneau d'administration, y compris la modification des autorisations ! Donc, ajoutez des personnes à ce groupe avec précaution."
+
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:45
+msgid "To make sure you don't get locked out of Metabase, there always has to be at least one user in this group."
+msgstr "Pour s'assurer de ne pas être exclu de Metabase, il doit toujours y avoir au moins un utilisateur dans ce groupe."
+
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:177
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:219
+msgid "Members"
+msgstr "Membres"
+
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:177
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:470
+#: frontend/src/metabase/admin/settings/selectors.js:107
+#: frontend/src/metabase/lib/core.js:50
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:293
+msgid "Email"
+msgstr "Adresse électronique"
+
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:203
+msgid "A group is only as good as its members."
+msgstr "Un groupe n'a de valeur que lorsqu'il contient des utilisateurs."
+
+#. tous les items du menu paramètres sont des noms...
+#: frontend/src/metabase/admin/people/components/GroupSummary.jsx:15
+#: frontend/src/metabase/admin/routes.jsx:34
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:50
+msgid "Admin"
+msgstr "Administration"
+
+#: frontend/src/metabase/admin/people/components/GroupSummary.jsx:16
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:237
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:290
+msgid "and"
+msgstr "et"
+
+#: frontend/src/metabase/admin/people/components/GroupSummary.jsx:19
+#: frontend/src/metabase/admin/people/components/GroupSummary.jsx:31
+msgid "{0} other group"
+msgid_plural "{0} other groups"
+msgstr[0] "{0} autre groupe"
+msgstr[1] "{0} autres groupes"
+
+#: frontend/src/metabase/admin/people/components/GroupSummary.jsx:37
+msgid "Default"
+msgstr "Défaut"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:40
+msgid "Something like \"Marketing\""
+msgstr "Quelque chose comme \"Marketing\""
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:59
+msgid "Remove this group?"
+msgstr "Supprimer ce groupe ?"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:61
+msgid "Are you sure? All members of this group will lose any permissions settings they have based on this group.\n"
+"This can't be undone."
+msgstr "Êtes-vous sûr ? Tous les membres de ce groupe perdront toutes leurs  autorisations basées sur ce groupe.\n"
+"Cette action ne peut pas être annulée."
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:72
+#: frontend/src/metabase/components/ConfirmContent.jsx:17
+msgid "Yes"
+msgstr "Oui"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:75
+msgid "No"
+msgstr "Non"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:93
+msgid "Edit Name"
+msgstr "Modifier le Nom"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:96
+msgid "Remove Group"
+msgstr "Supprimer le groupe"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:139
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:225
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:263
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:367
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:385
+#: frontend/src/metabase/components/HeaderModal.jsx:43
+#: frontend/src/metabase/dashboard/components/AddSeriesModal.jsx:282
+#: frontend/src/metabase/parameters/components/widgets/CategoryWidget.jsx:106
+#: frontend/src/metabase/query_builder/components/AggregationPopover.jsx:298
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:194
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:96
+#: frontend/src/metabase/visualizations/components/ChartSettings.jsx:215
+msgid "Done"
+msgstr "Fait"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:219
+msgid "Group name"
+msgstr "Nom du groupe"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:399
+#: frontend/src/metabase/admin/people/containers/AdminPeopleApp.jsx:25
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:477
+#: frontend/src/metabase/admin/routes.jsx:72
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:154
+msgid "Groups"
+msgstr "Groupes"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:400
+msgid "Create a group"
+msgstr "Créer un groupe"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:406
+msgid "You can use groups to control your users' access to your data. Put users in groups and then go to the Permissions section to control each group's access. The Administrators and All Users groups are special default groups that can't be removed."
+msgstr "Vous pouvez utiliser des groupes pour contrôler l'accès de vos utilisateurs à vos données. Placez les utilisateurs dans des groupes, puis accédez à la section Autorisations pour contrôler l'accès de chaque groupe. Les groupes Administrateurs et \"Tous les utilisateurs\" sont des groupes par défaut spéciaux qui ne peuvent être supprimés."
+
+#: frontend/src/metabase/admin/people/components/UserActionsSelect.jsx:79
+msgid "Edit Details"
+msgstr "Modifier les Détails"
+
+#: frontend/src/metabase/admin/people/components/UserActionsSelect.jsx:85
+msgid "Re-send Invite"
+msgstr "Renvoyer l'invitation"
+
+#: frontend/src/metabase/admin/people/components/UserActionsSelect.jsx:90
+msgid "Reset Password"
+msgstr "Réinitialiser le mot de passe"
+
+#: frontend/src/metabase/admin/people/components/UserActionsSelect.jsx:97
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:304
+msgid "Deactivate"
+msgstr "Désactiver"
+
+#: frontend/src/metabase/admin/people/containers/AdminPeopleApp.jsx:24
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:435
+#: frontend/src/metabase/admin/routes.jsx:70
+#: frontend/src/metabase/nav/containers/Navbar.jsx:205
+msgid "People"
+msgstr "Utilisateurs"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:192
+msgid "Who do you want to add?"
+msgstr "Que voulez vous ajouter?"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:207
+msgid "Edit {0}'s details"
+msgstr "Modifier les détails de {0}"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:220
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:258
+msgid "{0} has been added"
+msgstr "{0} a été ajouté"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:224
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:262
+msgid "Add another person"
+msgstr "Ajouter une autre personne"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:231
+msgid "We couldn’t send them an email invitation,\n"
+"so make sure to tell them to log in using {0}\n"
+"and this password we’ve generated for them:"
+msgstr "Nous n'avons pas pu lui envoyer un courriel d'invitation, assurez-vous donc qu'il se connecte en utilisant {0} et le mot de passe que nous avons généré :"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:242
+msgid "If you want to be able to send email invites, just go to the {0} page."
+msgstr "Si vous souhaitez pouvoir envoyer des courriels d'invitation, accédez à la page {0}."
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:268
+msgid "We’ve sent an invite to {0} with instructions to set their password."
+msgstr "Nous avons envoyé une invitation à {0} avec les instructions pour modifier le mot de passe"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:283
+msgid "We've re-sent {0}'s invite"
+msgstr "Nous avons renvoyé l'invitation de {0}"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:285
+#: frontend/src/metabase/query_builder/components/SavedQuestionIntroModal.jsx:22
+#: frontend/src/metabase/tutorial/Tutorial.jsx:253
+msgid "Okay"
+msgstr "OK"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:289
+msgid "Any previous email invites they have will no longer work."
+msgstr "Les invitations précédentes, envoyées par courriel, ne fonctionneront plus."
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:300
+msgid "Deactivate {0}?"
+msgstr "Désactiver {0}?"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:309
+msgid "{0} won't be able to log in anymore."
+msgstr "{0} ne pourra plus se connecter."
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:320
+msgid "Reactivate {0}'s account?"
+msgstr "Réactiver le compte de {0}?"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:326
+msgid "Reactivate"
+msgstr "Réactiver"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:330
+msgid "They'll be able to log in again, and they'll be placed back into the groups they were in before their account was deactivated."
+msgstr "Ces utilisateurs pourront se connecter à nouveau et ils seront replacés dans les groupes où ils se trouvaient avant que leur compte ne soit désactivé."
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:341
+msgid "Reset {0}'s password?"
+msgstr "Réinitialiser le mot de passe de {0}"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:347
+#: frontend/src/metabase/components/form/StandardForm.jsx:71
+msgid "Reset"
+msgstr "Réinitialiser"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:351
+#: frontend/src/metabase/components/ConfirmContent.jsx:13
+#: frontend/src/metabase/dashboard/components/ArchiveDashboardModal.jsx:44
+msgid "Are you sure you want to do this?"
+msgstr "Êtes-vous sûr de vouloir faire cela ?"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:362
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:384
+msgid "{0}'s password has been reset"
+msgstr "Le mot de passe de {0} a été réinitialisé"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:371
+msgid "Here’s a temporary password they can use to log in and then change their password."
+msgstr "Voici un mot de passe temporaire que les utilisateurs peuvent utiliser pour se connecter, puis modifier leur mot de passe."
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:388
+msgid "We've sent them an email with instructions for creating a new password."
+msgstr "Nous avons envoyé un courriel à cet utilisateur avec les instructions pour créer un nouveau mot de passe."
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:443
+msgid "Active"
+msgstr "Actif"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:444
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:473
+msgid "Deactivated"
+msgstr "Désactivé"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:459
+msgid "Add someone"
+msgstr "Ajouter quelqu'un"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:478
+msgid "Last Login"
+msgstr "Dernier login"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:501
+msgid "Signed up via Google"
+msgstr "Inscription via Google"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:506
+msgid "Signed up via LDAP"
+msgstr "Inscription via LDAP"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:518
+msgid "Reactivate this account"
+msgstr "Ré-activer ce compte"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:545
+msgid "Never"
+msgstr "Jamais"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:27
+#: frontend/src/metabase/query_builder/components/dataref/MainPane.jsx:24
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} table"
+msgid_plural "{0} tables"
+msgstr[0] "{0} table"
+msgstr[1] "{0} tables"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:45
+msgid " will be "
+msgstr " sera/seront "
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:48
+msgid "given access to"
+msgstr "accès donné à"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:53
+msgid " and "
+msgstr " et "
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:56
+msgid "denied access to"
+msgstr "accès accordé à"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:70
+msgid " will no longer be able to "
+msgstr " ne sera/seront plus autorisé(s) à "
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:71
+msgid " will now be able to "
+msgstr " sera/seront désormais autorisé(s) à "
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:79
+msgid " native queries for "
+msgstr " questions brutes pour "
+
+#: frontend/src/metabase/admin/permissions/routes.jsx:12
+#: frontend/src/metabase/nav/containers/Navbar.jsx:220
+msgid "Permissions"
+msgstr "Permissions"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:32
+msgid "Save permissions?"
+msgstr "Sauvegarder les permissions ?"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:38
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:161
+msgid "Save Changes"
+msgstr "Sauvegarder les modifications"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:44
+msgid "Discard changes?"
+msgstr "Annuler les modifications ?"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:46
+msgid "No changes to permissions will be made."
+msgstr "Aucune modification ne sera faite aux permissions."
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:65
+msgid "You've made changes to permissions."
+msgstr "Vous avez fait des modifications aux permissions."
+
+#: frontend/src/metabase/admin/permissions/containers/CollectionPermissionsModal.jsx:52
+msgid "Permissions for this collection"
+msgstr "Permissions pour cette collection"
+
+#: frontend/src/metabase/admin/permissions/containers/PermissionsApp.jsx:53
+msgid "You have unsaved changes"
+msgstr "Vous avez des modifications non sauvegardées"
+
+#: frontend/src/metabase/admin/permissions/containers/PermissionsApp.jsx:54
+msgid "Do you want to leave this page and discard your changes?"
+msgstr "Voulez-vous quitter cette page et ignorer vos modifications ?"
+
+#: frontend/src/metabase/admin/permissions/permissions.js:137
+msgid "Sorry, an error occurred."
+msgstr "Désolé, une erreur est survenue."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:59
+msgid "Administrators always have the highest level of access to everything in Metabase."
+msgstr "Les administrateurs ont toujours le plus haut niveau d'accès à tout dans Metabase."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:61
+msgid "Every Metabase user belongs to the All Users group. If you want to limit or restrict a group's access to something, make sure the All Users group has an equal or lower level of access."
+msgstr "Tous les utilisateurs de Metabase appartiennent au groupe \"Tous les utilisateurs\". Si vous voulez limiter ou restreindre l'accès d'un groupe à un élément, assurez-vous que le groupe \"Tous les utilisateurs\" a un niveau d'accès égal ou inférieur."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:63
+msgid "MetaBot is Metabase's Slack bot. You can choose what it has access to here."
+msgstr "MetaBot est le bot Slack de Metabase. Vous pouvez choisir à quoi il a accès ici."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:115
+msgid "The \"{0}\" group may have access to a different set of {1} than this group, which may give this group additional access to some {2}."
+msgstr "Le groupe \"{0}\" peut avoir accès à un ensemble différent de {1} que ce groupe, ce qui peut donner à ce groupe un accès supplémentaire à certains {2}."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:120
+msgid "The \"{0}\" group has a higher level of access than this, which will override this setting. You should limit or revoke the \"{1}\" group's access to this item."
+msgstr "Le groupe \"{0}\" a un niveau d'accès plus élevé que celui-ci, qui remplacera ce paramètre. Vous devez limiter ou révoquer l'accès du groupe \"{1}\" à cet élément."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:150
+msgid "Limit"
+msgstr "Limite"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:150
+msgid "Revoke"
+msgstr "Révoquer"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:152
+msgid "access even though \"{0}\" has greater access?"
+msgstr "accéder même si \"{0}\" a accès supérieur?"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:155
+#: frontend/src/metabase/admin/permissions/selectors.js:254
+msgid "Limit access"
+msgstr "Limiter l'accès"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:155
+#: frontend/src/metabase/admin/permissions/selectors.js:219
+#: frontend/src/metabase/admin/permissions/selectors.js:262
+msgid "Revoke access"
+msgstr "Révoquer l'accès"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:164
+msgid "Change access to this database to limited?"
+msgstr "Changer l'accès de cette base de données à limité ?"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:165
+msgid "Change"
+msgstr "Changer"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:178
+msgid "Allow Raw Query Writing?"
+msgstr "Autoriser l'écriture de requête brute ?"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:179
+msgid "This will also change this group's data access to Unrestricted for this database."
+msgstr "Cela aura aussi pour effet de donner à ce groupe un accès non restreint aux données de cette base."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:180
+msgid "Allow"
+msgstr "Autoriser"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:217
+msgid "Revoke access to all tables?"
+msgstr "Révoquer l'accès à toutes les tables ?"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:218
+msgid "This will also revoke this group's access to raw queries for this database."
+msgstr "Cela aura aussi pour effet de révoquer à ce groupe l'usage des questions brutes vers cette base de données."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:247
+msgid "Grant unrestricted access"
+msgstr "Accorder un accès non restreint"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:248
+msgid "Unrestricted access"
+msgstr "Accès non restreint"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:255
+msgid "Limited access"
+msgstr "Accès limité"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:263
+msgid "No access"
+msgstr "Aucun accès"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:269
+msgid "Write raw queries"
+msgstr "Ecrire des questions brutes"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:270
+msgid "Can write raw queries"
+msgstr "Peut/peuvent écrire des questions brutes"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:277
+msgid "Curate collection"
+msgstr "Organiser une collection"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:284
+msgid "View collection"
+msgstr "Voir le contenu d'une collection"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:327
+#: frontend/src/metabase/admin/permissions/selectors.js:423
+#: frontend/src/metabase/admin/permissions/selectors.js:520
+msgid "Data Access"
+msgstr "Accès aux données"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:488
+#: frontend/src/metabase/admin/permissions/selectors.js:645
+#: frontend/src/metabase/admin/permissions/selectors.js:650
+msgid "View tables"
+msgstr "Voir les tables"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:586
+msgid "SQL Queries"
+msgstr "Requêtes SQL"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:656
+msgid "View schemas"
+msgstr "Voir les schémas"
+
+#: frontend/src/metabase/admin/routes.jsx:45
+#: frontend/src/metabase/nav/containers/Navbar.jsx:210
+msgid "Data Model"
+msgstr "Modèles de données"
+
+#: frontend/src/metabase/admin/settings/components/SettingsAuthenticationOptions.jsx:11
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:118
+msgid "Sign in with Google"
+msgstr "S'authentifier avec Google"
+
+#: frontend/src/metabase/admin/settings/components/SettingsAuthenticationOptions.jsx:13
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:120
+msgid "Allows users with existing Metabase accounts to login with a Google account that matches their email address in addition to their Metabase username and password."
+msgstr "Permet aux utilisateurs ayant un compte Metabase de se connecter avec un compte Google ayant la même adresse électronique en plus de leur identifiant et mot de passe Metabase."
+
+#: frontend/src/metabase/admin/settings/components/SettingsAuthenticationOptions.jsx:17
+#: frontend/src/metabase/admin/settings/components/SettingsAuthenticationOptions.jsx:29
+#: frontend/src/metabase/components/ChannelSetupMessage.jsx:32
+msgid "Configure"
+msgstr "Configurer"
+
+#: frontend/src/metabase/admin/settings/components/SettingsAuthenticationOptions.jsx:23
+#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:13
+#: frontend/src/metabase/admin/settings/selectors.js:201
+msgid "LDAP"
+msgstr "LDAP"
+
+#: frontend/src/metabase/admin/settings/components/SettingsAuthenticationOptions.jsx:25
+msgid "Allows users within your LDAP directory to log in to Metabase with their LDAP credentials, and allows automatic mapping of LDAP groups to Metabase groups."
+msgstr "Permet aux utilisateurs de votre annuaire LDAP de se connecter à Metabase avec leur compte LDAP, et offre la possibilité de réaliser une correspondance automatique des groupes LDAP et Metabase."
+
+#: frontend/src/metabase/admin/settings/components/SettingsBatchForm.jsx:17
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:69
+#: frontend/src/metabase/admin/settings/selectors.js:154
+msgid "That's not a valid email address"
+msgstr "Ce n'est pas une adresse électronique valide"
+
+#: frontend/src/metabase/admin/settings/components/SettingsBatchForm.jsx:21
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:73
+msgid "That's not a valid integer"
+msgstr "Ce n'est pas un entier valide"
+
+#: frontend/src/metabase/admin/settings/components/SettingsBatchForm.jsx:28
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:161
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:223
+msgid "Changes saved!"
+msgstr "Modifications sauvegardées  !"
+
+#: frontend/src/metabase/admin/settings/components/SettingsBatchForm.jsx:157
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:132
+msgid "Looks like we ran into some problems"
+msgstr "Il semble que nous avons rencontré des problèmes"
+
+#: frontend/src/metabase/admin/settings/components/SettingsEmailForm.jsx:12
+msgid "Send test email"
+msgstr "Envoyer un courriel de test"
+
+#: frontend/src/metabase/admin/settings/components/SettingsEmailForm.jsx:13
+msgid "Sending..."
+msgstr "Envoi en cours..."
+
+#: frontend/src/metabase/admin/settings/components/SettingsEmailForm.jsx:14
+msgid "Sent!"
+msgstr "Envoyé !"
+
+#: frontend/src/metabase/admin/settings/components/SettingsEmailForm.jsx:82
+msgid "Clear"
+msgstr "Effacer"
+
+#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:12
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:113
+#: frontend/src/metabase/admin/settings/selectors.js:196
+msgid "Authentication"
+msgstr "Authentification"
+
+#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:18
+msgid "Server Settings"
+msgstr "Paramètres serveur"
+
+#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:29
+msgid "User Schema"
+msgstr "Schéma utilisateur"
+
+#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:33
+msgid "Attributes"
+msgstr "Attributs"
+
+#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:42
+msgid "Group Schema"
+msgstr "Schéma de groupe"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSetting.jsx:28
+msgid "Using "
+msgstr "Utilise "
+
+#: frontend/src/metabase/admin/settings/components/SettingsSetupList.jsx:105
+msgid "Getting set up"
+msgstr "Configuration en cours"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSetupList.jsx:106
+msgid "A few things you can do to get the most out of Metabase."
+msgstr "Quelques astuces à suivre pour tirer le meilleur parti de Metabase."
+
+#: frontend/src/metabase/admin/settings/components/SettingsSetupList.jsx:115
+msgid "Recommended next step"
+msgstr "Prochaine étape recommandée"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:114
+msgid "Google Sign-In"
+msgstr "S'authentifier avec Google"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:123
+msgid "To allow users to sign in with Google you'll need to give Metabase a Google Developers console application client ID. It only takes a few steps and instructions on how to create a key can be found {0}"
+msgstr "Pour permettre aux utilisateurs de se connecter avec leur compte Google, vous devez fournir à Metabase le \"Client ID\" créé depuis la console Google API. Cela ne prend que quelques instants et la procédure à suivre se trouve ici : {0}"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:137
+msgid "Your Google client ID"
+msgstr "Votre Google client ID"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:142
+msgid "Allow users to sign up on their own if their Google account email address is from:"
+msgstr "Autoriser les utilisateurs à se connecter uniquement si leur compte Google se termine par :"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:242
+msgid "Answers sent right to your Slack #channels"
+msgstr "Réponses envoyées directement vers vos #canaux Slack"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:251
+msgid "Create a Slack Bot User for MetaBot"
+msgstr "Créer un Slack Bot User pour MetaBot"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:261
+msgid "Once you're there, give it a name and click {0}. Then copy and paste the Bot API Token into the field below. Once you are done, create a \"metabase_files\" channel in Slack. Metabase needs this to upload graphs."
+msgstr "Cela fait, donnez lui/elle un nom et cliquer {0}. Copier et coller ensuite le Bot API Token dans le champ ci-dessous. Enfin, créez un canal Slack \"metabase_files\" . Metabase en a besoin pour télécharger les graphiques."
+
+#: frontend/src/metabase/admin/settings/components/SettingsUpdatesForm.jsx:90
+msgid "You're running Metabase {0} which is the latest and greatest!"
+msgstr "Vous faites tourner Metabase {0} qui est la dernière et meilleure version !"
+
+#: frontend/src/metabase/admin/settings/components/SettingsUpdatesForm.jsx:99
+msgid "Metabase {0} is available.  You're running {1}"
+msgstr "Metabase {0} est disponible. Vous faites actuellement tourner {1}"
+
+#: frontend/src/metabase/admin/settings/components/SettingsUpdatesForm.jsx:112
+#: frontend/src/metabase/components/form/StandardForm.jsx:63
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:96
+msgid "Update"
+msgstr "Mettre à jour"
+
+#: frontend/src/metabase/admin/settings/components/SettingsUpdatesForm.jsx:116
+msgid "What's Changed:"
+msgstr "Ce qui a changé :"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:131
+msgid "Add a map"
+msgstr "Ajouter une carte"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:184
+#: frontend/src/metabase/lib/core.js:100
+msgid "URL"
+msgstr "URL"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:199
+msgid "Delete custom map"
+msgstr "Supprimer une carte personnalisée"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:201
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:327
+#: frontend/src/metabase/dashboard/components/RemoveFromDashboardModal.jsx:48
+#: frontend/src/metabase/parameters/components/ParameterWidget.jsx:177
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:104
+msgid "Remove"
+msgstr "Supprimer"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:226
+#: frontend/src/metabase/dashboard/containers/DashCardCardParameterMapper.jsx:187
+#: frontend/src/metabase/parameters/components/ParameterValueWidget.jsx:241
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:142
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:184
+msgid "Select…"
+msgstr "Sélectionner..."
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:241
+msgid "Sample values:"
+msgstr "Valeurs d'exemple:"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:279
+msgid "Add a new map"
+msgstr "Ajouter une nouvelle carte"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:279
+msgid "Edit map"
+msgstr "Modifier la carte"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:280
+msgid "What do you want to call this map?"
+msgstr "Comment voulez-vous appeler cette carte ?"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:285
+msgid "e.g. United Kingdom, Brazil, Mars"
+msgstr "Exemple : Royaume-Uni, Brésil, Mars"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:292
+msgid "URL for the GeoJSON file you want to use"
+msgstr "URL du fichier GeoJSON que vous voulez utiliser"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:298
+msgid "Like https://my-mb-server.com/maps/my-map.json"
+msgstr "Exemple : https://my-mb-server.com/maps/my-map.json"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:309
+#: frontend/src/metabase/query_builder/components/RunButton.jsx:33
+msgid "Refresh"
+msgstr "Rafraîchir"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:309
+msgid "Load"
+msgstr "Charger"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:315
+msgid "Which property specifies the region’s identifier?"
+msgstr "Quelle propriété spécifie l'identifiant de région ?"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:324
+msgid "Which property specifies the region’s display name?"
+msgstr "Quelle propriété spécifie le libellé de la région ?"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:345
+msgid "Load a GeoJSON file to see a preview"
+msgstr "Charger un fichier GeoJSON pour voir un aperçu"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:363
+msgid "Save map"
+msgstr "Sauvegarder la carte"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:363
+msgid "Add map"
+msgstr "Ajouter une carte"
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLegalese.jsx:7
+msgid "Using embedding"
+msgstr "Utiliser l'intégration"
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLegalese.jsx:9
+msgid "By enabling embedding you're agreeing to the embedding license located at"
+msgstr "En activant l'intégration vous donner votre accord à la licence d'intégration située à"
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLegalese.jsx:19
+msgid "In plain English, when you embed charts or dashboards from Metabase in your own application, that application isn't subject to the Affero General Public License that covers the rest of Metabase, provided you keep the Metabase logo and the \"Powered by Metabase\" visible on those embeds. You should, however, read the license text linked above as that is the actual license that you will be agreeing to by enabling this feature."
+msgstr "Précisément, quand vous intégrez des questions ou des tableaux de bord depuis Metabase dans votre propre application, cette application n'est pas l'objet de l'Affero General Public License qui couvre le reste de Metabase, du moment que vous conservez le logo Metabase et \"Powered by Metabase\" visibles dans ces intégrations. Vous devriez, cependant, lire le texte de la licence référencée ci-dessus, car c'est la licence à laquelle vous donnez dans les faits votre accord en activant cette fonctionnalité."
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLegalese.jsx:32
+msgid "Enable"
+msgstr "Activer"
+
+#. Que pensez-vous de "intégrer" / "intégration" / etc ?
+#. (pour "embed", "embedding", etc)
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLevel.jsx:24
+msgid "Premium embedding enabled"
+msgstr "Intégration Premium activée"
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLevel.jsx:26
+msgid "Enter the token you bought from the Metabase Store"
+msgstr "Saisissez le jeton que vous avez acheté dans la boutique Metabase"
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLevel.jsx:53
+msgid "Premium embedding lets you disable \"Powered by Metabase\" on your embedded dashboards and questions."
+msgstr "L'Intégration Premium vous permet de désactiver \"Powered by Metabase\" dans vos tableaux de bord et questions intégrés."
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLevel.jsx:60
+msgid "Buy a token"
+msgstr "Acheter un jeton"
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLevel.jsx:63
+msgid "Enter a token"
+msgstr "Saisir un jeton"
+
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:134
+msgid "Edit Mappings"
+msgstr "Modifier les correspondances"
+
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:140
+msgid "Group Mappings"
+msgstr "Correspondances de groupe"
+
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:147
+msgid "Create a mapping"
+msgstr "Créer une correspondance"
+
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:149
+msgid "Mappings allow Metabase to automatically add and remove users from groups based on the membership information provided by the\n"
+"directory server. Membership to the Admin group can be granted through mappings, but will not be automatically removed as a\n"
+"failsafe measure."
+msgstr "Les correspondances permettent à Metabase d'ajouter ou retirer automatiquement un utilisateur aux groupes selon les informations fournies par l'annuaire. L'appartenance au groupe 'Admin' peut être accordée par ce mécanisme, mais ne sera pas automatiquement retirée par mesure de sécurité."
+
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:154
+msgid "Distinguished Name"
+msgstr "Distinguished Name"
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:92
+msgid "Public Link"
+msgstr "Lien public"
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:93
+msgid "Revoke Link"
+msgstr "Retirer le lien"
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:121
+msgid "Disable this link?"
+msgstr "Désactiver ce lien ?"
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:122
+msgid "They won't work anymore, and can't be restored, but you can create new links."
+msgstr "Ils ne fonctionneront plus, et ne seront pas restaurés, mais vous pouvez créer de nouveaux liens."
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:149
+msgid "Public Dashboard Listing"
+msgstr "Liste des tableaux de bord publics"
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:152
+msgid "No dashboards have been publicly shared yet."
+msgstr "Aucun tableau de bord n'a encore été publiquement partagé."
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:160
+msgid "Public Card Listing"
+msgstr "Liste des questions publiques"
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:163
+msgid "No questions have been publicly shared yet."
+msgstr "Aucune question n'a encore été publiquement partagée."
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:172
+msgid "Embedded Dashboard Listing"
+msgstr "Liste des tableaux de bord intégrés"
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:173
+msgid "No dashboards have been embedded yet."
+msgstr "Aucun tableau de bord n'a encore été intégré."
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:183
+msgid "Embedded Card Listing"
+msgstr "Liste des questions intégrées"
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:184
+msgid "No questions have been embedded yet."
+msgstr "Aucune question n'a encore été intégrée."
+
+#: frontend/src/metabase/admin/settings/components/widgets/SecretKeyWidget.jsx:35
+msgid "Regenerate embedding key?"
+msgstr "Regenérer la clé d'intégration ?"
+
+#: frontend/src/metabase/admin/settings/components/widgets/SecretKeyWidget.jsx:36
+msgid "This will cause existing embeds to stop working until they are updated with the new key."
+msgstr "Cela provoquera le dysfonctionnement des intégrations existantes jusqu'à leur mise à jour avec la nouvelle clé."
+
+#: frontend/src/metabase/admin/settings/components/widgets/SecretKeyWidget.jsx:39
+msgid "Regenerate key"
+msgstr "Re-générer une clé"
+
+#: frontend/src/metabase/admin/settings/components/widgets/SecretKeyWidget.jsx:47
+msgid "Generate Key"
+msgstr "Générer une clé"
+
+#: frontend/src/metabase/admin/settings/components/widgets/SettingToggle.jsx:11
+#: frontend/src/metabase/admin/settings/selectors.js:77
+#: frontend/src/metabase/admin/settings/selectors.js:86
+msgid "Enabled"
+msgstr "Activé"
+
+#: frontend/src/metabase/admin/settings/components/widgets/SettingToggle.jsx:11
+#: frontend/src/metabase/admin/settings/selectors.js:82
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:103
+msgid "Disabled"
+msgstr "Désactivé"
+
+#: frontend/src/metabase/admin/settings/containers/SettingsEditorApp.jsx:116
+msgid "Unknown setting {0}"
+msgstr "Réglage inconnu {0}"
+
+#: frontend/src/metabase/admin/settings/selectors.js:22
+msgid "Setup"
+msgstr "Réglages"
+
+#: frontend/src/metabase/admin/settings/selectors.js:27
+msgid "General"
+msgstr "Général"
+
+#: frontend/src/metabase/admin/settings/selectors.js:32
+msgid "Site Name"
+msgstr "Nom du site"
+
+#: frontend/src/metabase/admin/settings/selectors.js:37
+msgid "Site URL"
+msgstr "URL du site"
+
+#: frontend/src/metabase/admin/settings/selectors.js:42
+msgid "Email Address for Help Requests"
+msgstr "Adresse électronique pour les demandes d'aide"
+
+#: frontend/src/metabase/admin/settings/selectors.js:47
+msgid "Report Timezone"
+msgstr "Fuseau horaire des rapports"
+
+#: frontend/src/metabase/admin/settings/selectors.js:50
+msgid "Database Default"
+msgstr "Valeur par défaut de la base de données"
+
+#: frontend/src/metabase/admin/settings/selectors.js:53
+msgid "Select a timezone"
+msgstr "Choisir un fuseau horaire"
+
+#: frontend/src/metabase/admin/settings/selectors.js:54
+msgid "Not all databases support timezones, in which case this setting won't take effect."
+msgstr "Toutes les bases de données ne gèrent pas les fuseaux horaires, le cas échéant ce réglage sera sans effet."
+
+#: frontend/src/metabase/admin/settings/selectors.js:59
+msgid "Language"
+msgstr "Langage"
+
+#: frontend/src/metabase/admin/settings/selectors.js:64
+msgid "Select a language"
+msgstr "Choisir une langue"
+
+#: frontend/src/metabase/admin/settings/selectors.js:69
+msgid "Anonymous Tracking"
+msgstr "Tracking anonyme"
+
+#: frontend/src/metabase/admin/settings/selectors.js:74
+msgid "Friendly Table and Field Names"
+msgstr "Adaptation des noms de table et de champ"
+
+#: frontend/src/metabase/admin/settings/selectors.js:80
+msgid "Only replace underscores and dashes with spaces"
+msgstr "Remplacer seulement les soulignés et tirés par un espace"
+
+#: frontend/src/metabase/admin/settings/selectors.js:90
+msgid "Enable Nested Queries"
+msgstr "Autoriser les requêtes imbriquées"
+
+#: frontend/src/metabase/admin/settings/selectors.js:96
+msgid "Updates"
+msgstr "Mises à jour"
+
+#: frontend/src/metabase/admin/settings/selectors.js:101
+msgid "Check for updates"
+msgstr "Rechercher des mises à jour"
+
+#: frontend/src/metabase/admin/settings/selectors.js:112
+msgid "SMTP Host"
+msgstr "Hôte SMTP"
+
+#: frontend/src/metabase/admin/settings/selectors.js:120
+msgid "SMTP Port"
+msgstr "Port SMTP"
+
+#: frontend/src/metabase/admin/settings/selectors.js:124
+#: frontend/src/metabase/admin/settings/selectors.js:224
+msgid "That's not a valid port number"
+msgstr "Ce n'est pas un numéro de port valide"
+
+#: frontend/src/metabase/admin/settings/selectors.js:128
+msgid "SMTP Security"
+msgstr "Sécurité SMTP"
+
+#: frontend/src/metabase/admin/settings/selectors.js:136
+msgid "SMTP Username"
+msgstr "Utilisateur SMTP"
+
+#: frontend/src/metabase/admin/settings/selectors.js:143
+msgid "SMTP Password"
+msgstr "Mot de passe SMTP"
+
+#: frontend/src/metabase/admin/settings/selectors.js:150
+msgid "From Address"
+msgstr "Depuis l'adresse"
+
+#: frontend/src/metabase/admin/settings/selectors.js:164
+msgid "Slack API Token"
+msgstr "Jeton d'API Slack"
+
+#: frontend/src/metabase/admin/settings/selectors.js:166
+msgid "Enter the token you received from Slack"
+msgstr "Saisissez le jeton que vous avez reçu de Slack"
+
+#: frontend/src/metabase/admin/settings/selectors.js:183
+msgid "Single Sign-On"
+msgstr "Authentification unique"
+
+#: frontend/src/metabase/admin/settings/selectors.js:207
+msgid "LDAP Authentication"
+msgstr "Authentification LDAP"
+
+#: frontend/src/metabase/admin/settings/selectors.js:213
+msgid "LDAP Host"
+msgstr "Hôte LDAP"
+
+#: frontend/src/metabase/admin/settings/selectors.js:221
+msgid "LDAP Port"
+msgstr "Port LDAP"
+
+#: frontend/src/metabase/admin/settings/selectors.js:228
+msgid "LDAP Security"
+msgstr "Sécurité LDAP"
+
+#: frontend/src/metabase/admin/settings/selectors.js:236
+msgid "Username or DN"
+msgstr "Utilisateur"
+
+#: frontend/src/metabase/admin/settings/selectors.js:241
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:188
+#: frontend/src/metabase/user/components/UserSettings.jsx:72
+msgid "Password"
+msgstr "Mot de passe"
+
+#: frontend/src/metabase/admin/settings/selectors.js:246
+msgid "User search base"
+msgstr "Base de recherche des utilisateurs"
+
+#: frontend/src/metabase/admin/settings/selectors.js:252
+msgid "User filter"
+msgstr "Filtre utilisateur"
+
+#: frontend/src/metabase/admin/settings/selectors.js:258
+msgid "Check your parentheses"
+msgstr "Vérifier vos parenthèses"
+
+#: frontend/src/metabase/admin/settings/selectors.js:264
+msgid "Email attribute"
+msgstr "Attribut adresse électronique"
+
+#: frontend/src/metabase/admin/settings/selectors.js:269
+msgid "First name attribute"
+msgstr "Attribut prénom"
+
+#: frontend/src/metabase/admin/settings/selectors.js:274
+msgid "Last name attribute"
+msgstr "Attribut Nom"
+
+#: frontend/src/metabase/admin/settings/selectors.js:279
+msgid "Synchronize group memberships"
+msgstr "Synchroniser les appartenances de groupe"
+
+#: frontend/src/metabase/admin/settings/selectors.js:285
+msgid "Group search base"
+msgstr "Base de recherche des groupes"
+
+#: frontend/src/metabase/admin/settings/selectors.js:294
+msgid "Maps"
+msgstr "Cartes"
+
+#: frontend/src/metabase/admin/settings/selectors.js:299
+msgid "Map tile server URL"
+msgstr "URL du serveur de cartes"
+
+#: frontend/src/metabase/admin/settings/selectors.js:300
+msgid "Metabase uses OpenStreetMaps by default."
+msgstr "Metabase utilise OpenStreetMaps par défaut."
+
+#: frontend/src/metabase/admin/settings/selectors.js:305
+msgid "Custom Maps"
+msgstr "Cartes personnalisées"
+
+#: frontend/src/metabase/admin/settings/selectors.js:306
+msgid "Add your own GeoJSON files to enable different region map visualizations"
+msgstr "Ajouter vos propres fichiers GeoJSON pour permettre la visualisation sur d'autres régions"
+
+#: frontend/src/metabase/admin/settings/selectors.js:313
+msgid "Public Sharing"
+msgstr "Partage public"
+
+#: frontend/src/metabase/admin/settings/selectors.js:318
+msgid "Enable Public Sharing"
+msgstr "Activer le partage public"
+
+#: frontend/src/metabase/admin/settings/selectors.js:323
+msgid "Shared Dashboards"
+msgstr "Tableaux de bord partagés"
+
+#: frontend/src/metabase/admin/settings/selectors.js:329
+msgid "Shared Questions"
+msgstr "Questions partagées"
+
+#: frontend/src/metabase/admin/settings/selectors.js:336
+msgid "Embedding in other Applications"
+msgstr "Intégration dans d'autres applications"
+
+#: frontend/src/metabase/admin/settings/selectors.js:363
+msgid "Enable Embedding Metabase in other Applications"
+msgstr "Autoriser l'intégration de Metabase dans d'autres applications"
+
+#: frontend/src/metabase/admin/settings/selectors.js:373
+msgid "Embedding secret key"
+msgstr "Clé secrète d'intégration"
+
+#: frontend/src/metabase/admin/settings/selectors.js:379
+msgid "Embedded Dashboards"
+msgstr "Tableaux de bord intégrés"
+
+#: frontend/src/metabase/admin/settings/selectors.js:385
+msgid "Embedded Questions"
+msgstr "Questions intégrées"
+
+#: frontend/src/metabase/admin/settings/selectors.js:392
+msgid "Caching"
+msgstr "Mise en cache"
+
+#: frontend/src/metabase/admin/settings/selectors.js:397
+msgid "Enable Caching"
+msgstr "Autoriser la mise en cache"
+
+#: frontend/src/metabase/admin/settings/selectors.js:402
+msgid "Minimum Query Duration"
+msgstr "Durée d'exécution minimum d'une requête"
+
+#: frontend/src/metabase/admin/settings/selectors.js:409
+msgid "Cache Time-To-Live (TTL) multiplier"
+msgstr "Multiplicateur TTL du cache"
+
+#. Erreur de compréhension dans ma première traduction
+#: frontend/src/metabase/admin/settings/selectors.js:416
+msgid "Max Cache Entry Size"
+msgstr "Taille maximum d'une entrée du cache"
+
+#: frontend/src/metabase/alert/alert.js:60
+msgid "Your alert is all set up."
+msgstr "Votre alerte est configurée."
+
+#: frontend/src/metabase/alert/alert.js:101
+msgid "Your alert was updated."
+msgstr "Votre alerte a été mise à jour"
+
+#: frontend/src/metabase/alert/alert.js:149
+msgid "The alert was successfully deleted."
+msgstr "L'alerte a été supprimer avec succès."
+
+#: frontend/src/metabase/auth/auth.js:33
+msgid "Please enter a valid formatted email address."
+msgstr "Veuillez saisir un adresse électronique valide."
+
+#: frontend/src/metabase/auth/auth.js:116
+#: frontend/src/metabase/setup/components/UserStep.jsx:110
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:69
+msgid "Passwords do not match"
+msgstr "Les mots de passe ne correspondent pas"
+
+#: frontend/src/metabase/auth/components/BackToLogin.jsx:6
+msgid "Back to login"
+msgstr "Retour à la page de connexion"
+
+#: frontend/src/metabase/auth/components/GoogleNoAccount.jsx:15
+msgid "No Metabase account exists for this Google account."
+msgstr "Aucun compte Metabase n'existe pour ce compte Google"
+
+#: frontend/src/metabase/auth/components/GoogleNoAccount.jsx:17
+msgid "You'll need an administrator to create a Metabase account before you can use Google to log in."
+msgstr "Vous aurez besoin qu'un administrateur Metabase crée un compte avant de pouvoir utiliser Google pour vous connecter."
+
+#: frontend/src/metabase/auth/components/SSOLoginButton.jsx:18
+msgid "Sign in with {0}"
+msgstr "Se connecter avec {0}"
+
+#: frontend/src/metabase/auth/containers/ForgotPasswordApp.jsx:56
+msgid "Please contact an administrator to have them reset your password"
+msgstr "S'il vous plaît, demandez à un administrateur Metabase de réinitialiser votre mot de passe"
+
+#: frontend/src/metabase/auth/containers/ForgotPasswordApp.jsx:69
+msgid "Forgot password"
+msgstr "Mot de passe oublié"
+
+#: frontend/src/metabase/auth/containers/ForgotPasswordApp.jsx:84
+msgid "The email you use for your Metabase account"
+msgstr "L'adresse électronique que vous utilisez pour votre compte Metabase"
+
+#: frontend/src/metabase/auth/containers/ForgotPasswordApp.jsx:99
+msgid "Send password reset email"
+msgstr "Envoyer un courriel de réinitialisation de mot de passe"
+
+#: frontend/src/metabase/auth/containers/ForgotPasswordApp.jsx:110
+msgid "Check your email for instructions on how to reset your password."
+msgstr "Vérifiez vos courriels pour obtenir les instructions de réinitialisation de votre mot de passe."
+
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:128
+msgid "Sign in to Metabase"
+msgstr "Se connecter à Metabase"
+
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:138
+msgid "OR"
+msgstr "OU"
+
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:157
+msgid "Username or email address"
+msgstr "Identifiant ou adresse électronique"
+
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:217
+msgid "Sign in"
+msgstr "Se connecter"
+
+#. faute d'accord
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:230
+msgid "I seem to have forgotten my password"
+msgstr "Il semble que j'aie oublié mon mot de passe"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:102
+msgid "request a new reset email"
+msgstr "demander un nouveau courriel de réinitialisation"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:120
+msgid "Whoops, that's an expired link"
+msgstr "Oups, ce lien a expiré"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:122
+msgid "For security reasons, password reset links expire after a little while. If you still need\n"
+"to reset your password, you can {0}."
+msgstr "Pour des raisons de sécurité, les liens de réinitialisation de mot de passe expirent après un petit moment. Si vous avez toujours besoin de réinitialiser votre mot de passe, vous pouvez {0}."
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:147
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:126
+msgid "New password"
+msgstr "Nouveau mot de passe"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:149
+msgid "To keep your data secure, passwords {0}"
+msgstr "Pour conserver vos données en sécurité, les mots de passe {0}"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:163
+msgid "Create a new password"
+msgstr "Créer un nouveau mot de passe"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:170
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:141
+msgid "Make sure its secure like the instructions above"
+msgstr "Assurez-vous que c'est sécurisé conformément aux instruction ci-dessus"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:184
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:150
+msgid "Confirm new password"
+msgstr "Confirmer le nouveau mot de passe"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:191
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:159
+msgid "Make sure it matches the one you just entered"
+msgstr "Assurez-vous qu'il correspond à celui que vous venez de saisir"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:216
+msgid "Your password has been reset."
+msgstr "Votre mot de passe a été réinitialisé."
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:222
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:227
+msgid "Sign in with your new password"
+msgstr "Vous connecter avec votre nouveau mot de passe"
+
+#: frontend/src/metabase/components/ActionButton.jsx:53
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:182
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:184
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:251
+msgid "Save failed"
+msgstr "La sauvegarde a échouée"
+
+#: frontend/src/metabase/components/ActionButton.jsx:54
+#: frontend/src/metabase/components/SaveStatus.jsx:60
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:183
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:124
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:185
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:225
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:252
+msgid "Saved"
+msgstr "Sauvegardé"
+
+#: frontend/src/metabase/components/Alert.jsx:12
+msgid "Ok"
+msgstr "C'est tout bon"
+
+#: frontend/src/metabase/components/ArchiveCollectionModal.jsx:40
+msgid "Archive this collection?"
+msgstr "Archiver cette collection ?"
+
+#: frontend/src/metabase/components/ArchiveCollectionModal.jsx:45
+msgid "The dashboards, collections, and pulses in this collection will also be archived."
+msgstr "Les tableaux de bord, collections et pulses de cette collection seront aussi archivés."
+
+#: frontend/src/metabase/components/ArchiveCollectionModal.jsx:49
+#: frontend/src/metabase/components/CollectionLanding.jsx:587
+#: frontend/src/metabase/components/EntityItem.jsx:54
+#: frontend/src/metabase/components/EntityMenu.info.js:31
+#: frontend/src/metabase/components/EntityMenu.info.js:87
+#: frontend/src/metabase/home/containers/ArchiveApp.jsx:48
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:195
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:200
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:201
+#: frontend/src/metabase/query_builder/containers/ArchiveQuestionModal.jsx:40
+#: frontend/src/metabase/query_builder/containers/ArchiveQuestionModal.jsx:53
+#: frontend/src/metabase/routes.jsx:199
+msgid "Archive"
+msgstr "Archiver"
+
+#: frontend/src/metabase/containers/ErrorPages.jsx:63
+msgid "This {0} has been archived"
+msgstr "Ce/cette {0} a été archivé(e)"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:679
+msgid "View the archive"
+msgstr "Voir l'archive"
+
+#: frontend/src/metabase/components/ArchivedItem.jsx:39
+msgid "Unarchive this {0}"
+msgstr "Annuler l'archivage de ce/cette {0}"
+
+#: frontend/src/metabase/components/BrowseApp.jsx:93
+#: frontend/src/metabase/components/BrowseApp.jsx:152
+#: frontend/src/metabase/components/BrowseApp.jsx:243
+#: frontend/src/metabase/containers/Overworld.jsx:222
+msgid "Our data"
+msgstr "Nos données"
+
+#: frontend/src/metabase/components/BrowseApp.jsx:188
+#: frontend/src/metabase/reference/databases/TableSidebar.jsx:51
+msgid "X-ray this table"
+msgstr "Radiographier cette table"
+
+#: frontend/src/metabase/components/BrowseApp.jsx:201
+#: frontend/src/metabase/containers/Overworld.jsx:249
+msgid "Learn about this table"
+msgstr "Apprendre au sujet de cette table"
+
+#: frontend/src/metabase/components/Button.info.js:11
+#: frontend/src/metabase/components/Button.info.js:12
+#: frontend/src/metabase/components/Button.info.js:13
+msgid "Clickity click"
+msgstr "Clickity click"
+
+#: frontend/src/metabase/components/ButtonWithStatus.jsx:9
+msgid "Saved!"
+msgstr "Sauvegardé !"
+
+#: frontend/src/metabase/components/ButtonWithStatus.jsx:10
+msgid "Saving failed."
+msgstr "La sauvegarde a échoué."
+
+#: frontend/src/metabase/components/Calendar.jsx:119
+msgid "Su"
+msgstr "dim."
+
+#: frontend/src/metabase/components/Calendar.jsx:119
+msgid "Mo"
+msgstr "lun."
+
+#: frontend/src/metabase/components/Calendar.jsx:119
+msgid "Tu"
+msgstr "mar."
+
+#: frontend/src/metabase/components/Calendar.jsx:119
+msgid "We"
+msgstr "mer."
+
+#: frontend/src/metabase/components/Calendar.jsx:119
+msgid "Th"
+msgstr "jeu."
+
+#: frontend/src/metabase/components/Calendar.jsx:119
+msgid "Fr"
+msgstr "ven."
+
+#: frontend/src/metabase/components/Calendar.jsx:119
+msgid "Sa"
+msgstr "sam."
+
+#: frontend/src/metabase/components/ChannelSetupMessage.jsx:41
+msgid "Your admin's email address"
+msgstr "L'adresse électronique de votre administrateur"
+
+#: frontend/src/metabase/components/ChannelSetupModal.jsx:37
+msgid "To send {0}, you'll need to set up {1} integration."
+msgstr "Pour envoyer un/des {0}, vous devez configurer l'intégration du/des {1}"
+
+#: frontend/src/metabase/components/ChannelSetupModal.jsx:38
+#: frontend/src/metabase/components/ChannelSetupModal.jsx:41
+msgid " or "
+msgstr " ou "
+
+#: frontend/src/metabase/components/ChannelSetupModal.jsx:40
+msgid "To send {0}, an admin needs to set up {1} integration."
+msgstr "Pour envoyer un/des {0}, un administrateur Metabase doit configurer l'intégration du/des {1}"
+
+#: frontend/src/metabase/components/CollectionEmptyState.jsx:15
+msgid "This collection is empty, like a blank canvas"
+msgstr "Cette collection est vide, comme une toile blanche"
+
+#: frontend/src/metabase/components/CollectionEmptyState.jsx:16
+msgid "You can use collections to organize and group dashboards, questions and pulses for your team or yourself"
+msgstr "Vous pouvez utiliser les collections pour organiser et grouper vos tableaux de bord, questions et pulses pour votre équipe ou vous même"
+
+#: frontend/src/metabase/components/CollectionEmptyState.jsx:28
+msgid "Create another collection"
+msgstr "Créer une autre collection"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:61
+msgid "Dashboards let you collect and share data in one place."
+msgstr "Les tableaux de bord vous permettent de rassembler et partager la donnée à un seul endroit."
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:70
+msgid "Pulses let you send out the latest data to your team on a schedule via email or slack."
+msgstr "Les Pulses vous permettent d'envoyer les plus récentes données à votre équipe de façon planifiée, par courriel ou via slack."
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:79
+msgid "Questions are a saved look at your data."
+msgstr "Une question est une vue sauvegardée de vos données."
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:275
+msgid "Pins"
+msgstr "Epingles"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:329
+msgid "Drag something here to pin it to the top"
+msgstr "Déposer quelque chose ici pour l'épingler en haut"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:733
+#: frontend/src/metabase/components/CollectionLanding.jsx:341
+#: frontend/src/metabase/home/containers/SearchApp.jsx:35
+#: frontend/src/metabase/home/containers/SearchApp.jsx:96
+msgid "Collections"
+msgstr "Collections"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:411
+#: frontend/src/metabase/components/CollectionLanding.jsx:434
+msgid "Drag here to un-pin"
+msgstr "Déposer ici pour ôter une épingle"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:469
+msgid "{0} item selected"
+msgid_plural "{0} items selected"
+msgstr[0] "{0} élément sélectionné"
+msgstr[1] "{0} éléments sélectionnés"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:487
+msgid "Move {0} items?"
+msgstr "Déplacer {0} éléments"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:488
+msgid "Move \"{0}\"?"
+msgstr "Déplacer \"{0}\" ?"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:594
+#: frontend/src/metabase/components/EntityMenu.info.js:29
+#: frontend/src/metabase/components/EntityMenu.info.js:85
+#: frontend/src/metabase/containers/CollectionMoveModal.jsx:78
+msgid "Move"
+msgstr "Déplacer"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:656
+msgid "Edit this collection"
+msgstr "Modifier cette collection"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:664
+msgid "Archive this collection"
+msgstr "Archiver cette collection"
+
+#: frontend/src/metabase/components/CollectionList.jsx:64
+#: frontend/src/metabase/entities/collections.js:148
+msgid "My personal collection"
+msgstr "Ma collection personnelle"
+
+#: frontend/src/metabase/components/CollectionList.jsx:106
+#: frontend/src/metabase/containers/CollectionForm.jsx:9
+msgid "New collection"
+msgstr "Nouvelle collection"
+
+#: frontend/src/metabase/components/CopyButton.jsx:35
+msgid "Copied!"
+msgstr "Copié(e)(s) !"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:216
+msgid "Use an SSH-tunnel for database connections"
+msgstr "Utiliser un tunnel SSH pour les connexions à la base de données"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:218
+msgid "Some database installations can only be accessed by connecting through an SSH bastion host.\n"
+"This option also provides an extra layer of security when a VPN is not available.\n"
+"Enabling this is usually slower than a direct connection."
+msgstr "Certaines bases de données ne sont accessibles qu'en SSH au travers d'un serveur bastion. Cette option ajoute aussi une couche de sécurité lors d'une connexion hors VPN. L'activer est plus lent qu'une connexion directe."
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:274
+msgid "This is a large database, so let me choose when Metabase syncs and scans"
+msgstr "C'est une grande base de données, laissez-moi donc choisir quand Metabase lance les synchronisations et les analyses"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:276
+msgid "By default, Metabase does a lightweight hourly sync and an intensive daily scan of field values.\n"
+"If you have a large database, we recommend turning this on and reviewing when and how often the field value scans happen."
+msgstr "Par défaut, Metabase effectue une synchronisation horaire légère et un analyse journalière intensive des valeurs de filtre.\n"
+"Si vous avez une grande base de données, nous vous recommandons d'activer cette option et de définir quand et à quelle fréquence l'analyse des valeurs de filtre s'effectue."
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:292
+msgid "{0} to generate a Client ID and Client Secret for your project."
+msgstr "{0} pour générer un ID et un secret client pour votre projet."
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:294
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:321
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:356
+msgid "Click here"
+msgstr "Cliquer ici"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:297
+msgid "Choose \"Other\" as the application type. Name it whatever you'd like."
+msgstr "Choisir \"Autre\" comme type d'application. La nommer comme bon vous semble."
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:319
+msgid "{0} to get an auth code"
+msgstr "{0} pour obtenir un code d'authentification"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:331
+msgid "with Google Drive permissions"
+msgstr "avec les permissions de Google Drive"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:351
+msgid "To use Metabase with this data you must enable API access in the Google Developers Console."
+msgstr "Pour pouvoir utiliser Metabase avec cette donnée, vous devez activer l'accès à l'API dans la Google Developers Console."
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:354
+msgid "{0} to go to the console if you haven't already done so."
+msgstr "{0} pour se rendre dans la console si ce n'est déjà fait."
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:403
+msgid "How would you like to refer to this database?"
+msgstr "Sous quel nom souhaitez-vous référencer cette base de données ?"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:430
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:97
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:240
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:188
+#: frontend/src/metabase/setup/components/DatabaseSchedulingStep.jsx:74
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:116
+#: frontend/src/metabase/setup/components/UserStep.jsx:308
+msgid "Next"
+msgstr "Suivant"
+
+#: frontend/src/metabase/components/DeleteModalWithConfirm.jsx:80
+msgid "Delete this {0}"
+msgstr "Supprimer ce/cette {0}"
+
+#: frontend/src/metabase/components/EntityItem.jsx:42
+msgid "Pin this item"
+msgstr "Epingler cet élément"
+
+#: frontend/src/metabase/components/EntityItem.jsx:48
+msgid "Move this item"
+msgstr "Déplacer cet élément"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:24
+#: frontend/src/metabase/components/EntityMenu.info.js:80
+msgid "Edit this question"
+msgstr "Modifier cette question"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:26
+#: frontend/src/metabase/components/EntityMenu.info.js:47
+#: frontend/src/metabase/components/EntityMenu.info.js:82
+#: frontend/src/metabase/components/EntityMenu.info.js:99
+msgid "Action type"
+msgstr "Type d'action"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:28
+#: frontend/src/metabase/components/EntityMenu.info.js:84
+msgid "View revision history"
+msgstr "Voir l'historique des modifications"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:29
+#: frontend/src/metabase/components/EntityMenu.info.js:85
+msgid "Move action"
+msgstr "Déplacement"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:33
+#: frontend/src/metabase/components/EntityMenu.info.js:89
+msgid "Archive action"
+msgstr "Archivage"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:45
+#: frontend/src/metabase/components/EntityMenu.info.js:97
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:329
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:342
+msgid "Add to dashboard"
+msgstr "Ajouter à un tableau de bord"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:49
+#: frontend/src/metabase/components/EntityMenu.info.js:101
+msgid "Download results"
+msgstr "Télécharger les résultats"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:51
+#: frontend/src/metabase/components/EntityMenu.info.js:103
+#: frontend/src/metabase/public/components/widgets/EmbedWidget.jsx:52
+msgid "Sharing and embedding"
+msgstr "Partager et intégrer"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:53
+#: frontend/src/metabase/components/EntityMenu.info.js:105
+msgid "Another action type"
+msgstr "Un autre type d'action"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:65
+#: frontend/src/metabase/components/EntityMenu.info.js:67
+#: frontend/src/metabase/components/EntityMenu.info.js:113
+#: frontend/src/metabase/components/EntityMenu.info.js:115
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:449
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:454
+msgid "Get alerts about this"
+msgstr "Obtenir des alertes sur ce sujet"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:69
+#: frontend/src/metabase/components/EntityMenu.info.js:117
+msgid "View the SQL"
+msgstr "Visualiser le SQL"
+
+#: frontend/src/metabase/components/EntitySegments.jsx:18
+msgid "Segments for this"
+msgstr "Segments associés"
+
+#: frontend/src/metabase/components/ErrorDetails.jsx:20
+msgid "Show error details"
+msgstr "Montrer l'erreur en détail"
+
+#: frontend/src/metabase/components/ErrorDetails.jsx:26
+msgid "Here's the full error message"
+msgstr "Voici le message d'erreur complet"
+
+#: frontend/src/metabase/components/ExplorePane.jsx:19
+msgid "Hi, Metabot here."
+msgstr "Salut, ici MetaBot."
+
+#: frontend/src/metabase/components/ExplorePane.jsx:95
+msgid "Based on the schema"
+msgstr "Selon le schéma"
+
+#: frontend/src/metabase/components/ExplorePane.jsx:174
+msgid "A look at your"
+msgstr "Jeter un œil à votre"
+
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:234
+msgid "Search the list"
+msgstr "Recherche dans la liste"
+
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:238
+msgid "Search by {0}"
+msgstr "Recherche par {0}"
+
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:240
+msgid " or enter an ID"
+msgstr " ou saisir un ID"
+
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:244
+msgid "Enter an ID"
+msgstr "Saisir un ID"
+
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:246
+msgid "Enter a number"
+msgstr "Saisir un nombre"
+
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:248
+msgid "Enter some text"
+msgstr "Saisir du texte"
+
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:355
+msgid "No matching {0} found."
+msgstr "Aucun(e) {0} correspondant trouvé(e)."
+
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:363
+msgid "Including every option in your filter probably won’t do much…"
+msgstr "Inclure chaque option dans votre filtre n'apportera probablement pas grand chose..."
+
+#: frontend/src/metabase/containers/ErrorPages.jsx:24
+msgid "Something's gone wrong"
+msgstr "Quelque chose s'est mal passé"
+
+#: frontend/src/metabase/containers/ErrorPages.jsx:25
+msgid "We've run into an error. You can try refreshing the page, or just go back."
+msgstr "Une erreur est survenue. Vous pouvez essayer de rafraîchir la page, ou juste revenir en arrière."
+
+#: frontend/src/metabase/components/Header.jsx:97
+#: frontend/src/metabase/components/HeaderBar.jsx:45
+#: frontend/src/metabase/components/ListItem.jsx:37
+#: frontend/src/metabase/reference/components/Detail.jsx:47
+#: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:158
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:213
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:191
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:205
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:209
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:209
+msgid "No description yet"
+msgstr "Aucune description"
+
+#: frontend/src/metabase/components/Header.jsx:112
+msgid "New {0}"
+msgstr "Nouveau/nouvelle {0}"
+
+#: frontend/src/metabase/components/Header.jsx:123
+msgid "Asked by {0}"
+msgstr "Demandé par {0}"
+
+#: frontend/src/metabase/components/HistoryModal.jsx:13
+msgid "Today, "
+msgstr "Aujourd'hui, "
+
+#: frontend/src/metabase/components/HistoryModal.jsx:15
+msgid "Yesterday, "
+msgstr "Hier, "
+
+#: frontend/src/metabase/components/HistoryModal.jsx:68
+msgid "First revision."
+msgstr "Première version."
+
+#: frontend/src/metabase/components/HistoryModal.jsx:70
+msgid "Reverted to an earlier revision and {0}"
+msgstr "Ramené(e) à une précédente version et {0}"
+
+#: frontend/src/metabase/components/HistoryModal.jsx:82
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:289
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:379
+#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:54
+msgid "Revision history"
+msgstr "Historique des modifications"
+
+#: frontend/src/metabase/components/HistoryModal.jsx:90
+msgid "When"
+msgstr "Quand"
+
+#: frontend/src/metabase/components/HistoryModal.jsx:91
+msgid "Who"
+msgstr "Qui"
+
+#: frontend/src/metabase/components/HistoryModal.jsx:92
+msgid "What"
+msgstr "Quoi"
+
+#: frontend/src/metabase/components/HistoryModal.jsx:113
+msgid "Revert"
+msgstr "Revenir à la version précédente"
+
+#: frontend/src/metabase/components/HistoryModal.jsx:114
+msgid "Reverting…"
+msgstr "Retour à la version précédente..."
+
+#: frontend/src/metabase/components/HistoryModal.jsx:115
+msgid "Revert failed"
+msgstr "Le retour à la version précédente a échoué"
+
+#: frontend/src/metabase/components/HistoryModal.jsx:116
+msgid "Reverted"
+msgstr "Revenu à la version précédente"
+
+#: frontend/src/metabase/components/ItemTypeFilterBar.jsx:13
+msgid "Everything"
+msgstr "Tout"
+
+#: frontend/src/metabase/components/ItemTypeFilterBar.jsx:18
+#: frontend/src/metabase/home/containers/SearchApp.jsx:73
+msgid "Dashboards"
+msgstr "Tableaux de bord"
+
+#: frontend/src/metabase/components/ItemTypeFilterBar.jsx:23
+#: frontend/src/metabase/home/containers/SearchApp.jsx:119
+msgid "Questions"
+msgstr "Questions"
+
+#: frontend/src/metabase/components/ItemTypeFilterBar.jsx:28
+#: frontend/src/metabase/routes.jsx:320
+msgid "Pulses"
+msgstr "Pulses"
+
+#: frontend/src/metabase/components/LeftNavPane.jsx:36
+#: frontend/src/metabase/query_builder/components/dataref/DataReference.jsx:86
+msgid "Back"
+msgstr "Retour"
+
+#: frontend/src/metabase/components/ListSearchField.jsx:18
+msgid "Find..."
+msgstr "Chercher..."
+
+#: frontend/src/metabase/components/LoadingAndErrorWrapper.jsx:48
+msgid "An error occured"
+msgstr "Une erreur est survenue"
+
+#: frontend/src/metabase/components/LoadingAndErrorWrapper.jsx:35
+msgid "Loading..."
+msgstr "Chargement..."
+
+#: frontend/src/metabase/components/NewsletterForm.jsx:71
+msgid "Metabase Newsletter"
+msgstr "Bulletin d'information de Metabase"
+
+#: frontend/src/metabase/components/NewsletterForm.jsx:81
+msgid "Get infrequent emails about new releases and feature updates."
+msgstr "Obtenir des courriel ponctuels au sujet des nouvelles versions et des mises à jour de fonctionnalités."
+
+#: frontend/src/metabase/components/NewsletterForm.jsx:99
+msgid "Subscribe"
+msgstr "S'inscrire"
+
+#: frontend/src/metabase/components/NewsletterForm.jsx:106
+msgid "You're subscribed. Thanks for using Metabase!"
+msgstr "Vous êtes inscrit. Merci d'utiliser Metabase !"
+
+#: frontend/src/metabase/containers/ErrorPages.jsx:44
+msgid "We're a little lost..."
+msgstr "Nous sommes un peu perdus..."
+
+#: frontend/src/metabase/components/PasswordReveal.jsx:27
+msgid "Temporary Password"
+msgstr "Mot de passe temporaire"
+
+#: frontend/src/metabase/components/PasswordReveal.jsx:68
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:334
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:349
+msgid "Hide"
+msgstr "Masquer"
+
+#: frontend/src/metabase/components/PasswordReveal.jsx:68
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:335
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:350
+msgid "Show"
+msgstr "Montrer"
+
+#: frontend/src/metabase/components/QuestionSavedModal.jsx:17
+msgid "Saved! Add this to a dashboard?"
+msgstr "Sauvegardé ! Ajouter cela à un tableau de bord ?"
+
+#: frontend/src/metabase/components/QuestionSavedModal.jsx:25
+msgid "Yes please!"
+msgstr "Oui s'il vous plaît !"
+
+#: frontend/src/metabase/components/QuestionSavedModal.jsx:29
+msgid "Not now"
+msgstr "Pas maintenant"
+
+#: frontend/src/metabase/components/SaveStatus.jsx:53
+msgid "Error:"
+msgstr "Erreur:"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:23
+msgid "Sunday"
+msgstr "Dimanche"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:24
+msgid "Monday"
+msgstr "Lundi"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:25
+msgid "Tuesday"
+msgstr "Mardi"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:26
+msgid "Wednesday"
+msgstr "Mercredi"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:27
+msgid "Thursday"
+msgstr "Jeudi"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:28
+msgid "Friday"
+msgstr "Vendredi"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:29
+msgid "Saturday"
+msgstr "Samedi"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:33
+msgid "First"
+msgstr "Premier"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:34
+msgid "Last"
+msgstr "Dernier"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:35
+msgid "15th (Midpoint)"
+msgstr "Le quinze du mois"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:125
+msgid "Calendar Day"
+msgstr "Jour calendaire"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:210
+msgid "your Metabase timezone"
+msgstr "Fuseau horaire de Metabase"
+
+#: frontend/src/metabase/components/SearchHeader.jsx:21
+msgid "Filter this list..."
+msgstr "Filtrer cette liste..."
+
+#: frontend/src/metabase/components/Select.info.js:8
+msgid "Blue"
+msgstr "Bleu"
+
+#: frontend/src/metabase/components/Select.info.js:9
+msgid "Green"
+msgstr "Vert"
+
+#: frontend/src/metabase/components/Select.info.js:10
+msgid "Red"
+msgstr "Rouge"
+
+#: frontend/src/metabase/components/Select.info.js:11
+msgid "Yellow"
+msgstr "Jaune"
+
+#: frontend/src/metabase/components/Select.info.js:14
+msgid "A component used to make a selection"
+msgstr "Un composant utilisé pour effectuer un choix"
+
+#: frontend/src/metabase/components/Select.info.js:20
+#: frontend/src/metabase/components/Select.info.js:28
+msgid "Selected"
+msgstr "Sélectionné(e)(s)"
+
+#: frontend/src/metabase/components/Select.jsx:281
+msgid "Nothing to select"
+msgstr "Rien à sélectionner"
+
+#: frontend/src/metabase/containers/ErrorPages.jsx:54
+msgid "Sorry, you don’t have permission to see that."
+msgstr "Désolé, vous n'avez pas la permission de voir cela."
+
+#: frontend/src/metabase/components/form/FormMessage.jsx:5
+msgid "Unknown error encountered"
+msgstr "Un erreur inconnue a été rencontrée"
+
+#: frontend/src/metabase/components/form/StandardForm.jsx:63
+#: frontend/src/metabase/nav/containers/Navbar.jsx:300
+msgid "Create"
+msgstr "Créer"
+
+#: frontend/src/metabase/containers/DashboardForm.jsx:9
+msgid "Create dashboard"
+msgstr "Créer un tableau de bord"
+
+#: frontend/src/metabase/containers/EntitySearch.jsx:35
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:324
+#: frontend/src/metabase/visualizations/visualizations/Table.jsx:44
+msgid "Table"
+msgstr "Table"
+
+#: frontend/src/metabase/containers/EntitySearch.jsx:42
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:299
+msgid "Database"
+msgstr "Base de données"
+
+#: frontend/src/metabase/containers/EntitySearch.jsx:49
+msgid "Creator"
+msgstr "Créateur"
+
+#: frontend/src/metabase/containers/EntitySearch.jsx:238
+msgid "No results found"
+msgstr "Aucun résultat trouvé"
+
+#: frontend/src/metabase/containers/EntitySearch.jsx:239
+msgid "Try adjusting your filter to find what you’re looking for."
+msgstr "Essayez d'ajuster votre filtre pour trouver ce que vous recherchez"
+
+#: frontend/src/metabase/containers/EntitySearch.jsx:258
+msgid "View by"
+msgstr "Voir par"
+
+#: frontend/src/metabase/containers/EntitySearch.jsx:494
+#: frontend/src/metabase/query_builder/components/AggregationWidget.jsx:69
+#: frontend/src/metabase/tutorial/TutorialModal.jsx:34
+msgid "of"
+msgstr "de"
+
+#: frontend/src/metabase/containers/Overworld.jsx:78
+msgid "Don't tell anyone, but you're my favorite."
+msgstr "Ne le dites à personne, mais vous êtes mon favori."
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:85
+msgid "Once you connect your own data, I can show you some automatic explorations called x-rays. Here are some examples with sample data."
+msgstr "Une fois connecté à vos propres données, je peux vous montrer quelques explorations automatiquement générées, appelées radiographies. En voici quelques exemples basés sur des données de démonstration."
+
+#: frontend/src/metabase/containers/Overworld.jsx:131
+#: frontend/src/metabase/containers/Overworld.jsx:302
+#: frontend/src/metabase/reference/components/GuideHeader.jsx:12
+msgid "Start here"
+msgstr "Commencez ici"
+
+#: frontend/src/metabase/containers/Overworld.jsx:297
+#: frontend/src/metabase/entities/collections.js:140
+#: src/metabase/models/collection.clj
+msgid "Our analytics"
+msgstr "Notre décisionnel"
+
+#: frontend/src/metabase/containers/Overworld.jsx:206
+msgid "Browse all items"
+msgstr "Parcourir tous les éléments"
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:165
+msgid "Replace or save as new?"
+msgstr "Remplacer ou sauvegarder en tant que nouvel élément ?"
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:173
+msgid "Replace original question, \"{0}\""
+msgstr "Remplacer la question originale. \"{0}\""
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:178
+msgid "Save as new question"
+msgstr "Sauvegarder en tant que nouvelle question"
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:187
+msgid "First, save your question"
+msgstr "Avant toute chose, sauvegardez votre question"
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:188
+msgid "Save question"
+msgstr "Sauvegarder la question"
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:224
+msgid "What is the name of your card?"
+msgstr "Quel est le nom de votre carte ?"
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:232
+#: frontend/src/metabase/entities/collections.js:94
+#: frontend/src/metabase/entities/dashboards.js:102
+#: frontend/src/metabase/lib/core.js:45 frontend/src/metabase/lib/core.js:200
+#: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:156
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:211
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:189
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:203
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:207
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:207
+#: frontend/src/metabase/visualizations/lib/settings.js:163
+msgid "Description"
+msgstr "Description"
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:238
+#: frontend/src/metabase/entities/dashboards.js:104
+msgid "It's optional but oh, so helpful"
+msgstr "C'est facultatif, mais ô combien utile"
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:245
+#: frontend/src/metabase/entities/dashboards.js:108
+msgid "Which collection should this go in?"
+msgstr "Dans quelle collection cela devrait-il aller ?"
+
+#: frontend/src/metabase/containers/UndoListing.jsx:34
+msgid "modified"
+msgstr "modifié"
+
+#: frontend/src/metabase/containers/UndoListing.jsx:34
+msgid "item"
+msgstr "élément"
+
+#: frontend/src/metabase/containers/UndoListing.jsx:81
+msgid "Undo"
+msgstr "Annuler la modification"
+
+#: frontend/src/metabase/dashboard/components/AddSeriesModal.jsx:270
+msgid "Applying Question"
+msgstr "Poser la question"
+
+#: frontend/src/metabase/dashboard/components/AddSeriesModal.jsx:274
+msgid "That question isn't compatible"
+msgstr "Cette question n'est pas compatible"
+
+#: frontend/src/metabase/dashboard/components/AddSeriesModal.jsx:310
+msgid "Search for a question"
+msgstr "Rechercher une question"
+
+#: frontend/src/metabase/dashboard/components/AddSeriesModal.jsx:339
+msgid "We're not sure if this question is compatible"
+msgstr "Nous ne sommes pas sûr que cette question soit compatible"
+
+#: frontend/src/metabase/dashboard/components/ArchiveDashboardModal.jsx:43
+msgid "Archive Dashboard"
+msgstr "Archiver le tableau de bord"
+
+#: frontend/src/metabase/dashboard/components/DashCardParameterMapper.jsx:20
+msgid "Make sure to make a selection for each series, or the filter won't work on this card."
+msgstr "Assurez-vous de faire un choix pour chaque série de données, ou le filtre ne fonctionnera pas avec cette carte."
+
+#: frontend/src/metabase/dashboard/components/Dashboard.jsx:284
+msgid "This dashboard is looking empty."
+msgstr "Ce tableau de bord semble vide."
+
+#: frontend/src/metabase/dashboard/components/Dashboard.jsx:287
+msgid "Add a question to start making it useful!"
+msgstr "Ajoutez une question pour commencer à le rendre utile !"
+
+#: frontend/src/metabase/dashboard/components/DashboardActions.jsx:36
+msgid "Daytime mode"
+msgstr "Mode jour"
+
+#: frontend/src/metabase/dashboard/components/DashboardActions.jsx:36
+msgid "Nighttime mode"
+msgstr "Mode nuit"
+
+#: frontend/src/metabase/dashboard/components/DashboardActions.jsx:53
+msgid "Exit fullscreen"
+msgstr "Sortir du mode plein écran"
+
+#: frontend/src/metabase/dashboard/components/DashboardActions.jsx:53
+msgid "Enter fullscreen"
+msgstr "Passer en mode plein écran"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:181
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:183
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:250
+msgid "Saving…"
+msgstr "Sauvegarde en cours..."
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:216
+msgid "Add a question"
+msgstr "Ajouter une question"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:219
+msgid "Add a question to this dashboard"
+msgstr "Ajouter une question à ce tableau de bord"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:248
+msgid "Add a filter"
+msgstr "Ajouter un filtre"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:254
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:78
+msgid "Parameters"
+msgstr "Paramètres"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:275
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:279
+msgid "Add a text box"
+msgstr "Ajouter une zone de texte"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:301
+msgid "Move dashboard"
+msgstr "Déplacer le tableau de bord"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:313
+msgid "Edit dashboard"
+msgstr "Modifier le tableau de bord"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:317
+msgid "Edit Dashboard Layout"
+msgstr "Modifier l'agencement du tableau de bord"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:352
+msgid "You are editing a dashboard"
+msgstr "Vous modifiez un tableau de bord"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:357
+msgid "Select the field that should be filtered for each card"
+msgstr "Sélectionner le champ qui devrait être filtré pour chaque carte"
+
+#: frontend/src/metabase/dashboard/components/DashboardMoveModal.jsx:28
+msgid "Move dashboard to..."
+msgstr "Déplacer le tableau de bord vers..."
+
+#: frontend/src/metabase/dashboard/components/DashboardMoveModal.jsx:52
+msgid "Dashboard moved to {0}"
+msgstr "Tableau de bord déplacé vers {0}"
+
+#: frontend/src/metabase/dashboard/components/ParametersPopover.jsx:82
+msgid "What do you want to filter?"
+msgstr "Que voulez-vous filtrer ?"
+
+#: frontend/src/metabase/dashboard/components/ParametersPopover.jsx:115
+msgid "What kind of filter?"
+msgstr "Quel type de filtre ?"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:13
+msgid "Off"
+msgstr "Inactif"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:14
+msgid "1 minute"
+msgstr "1 minute"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:15
+msgid "5 minutes"
+msgstr "5 minutes"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:16
+msgid "10 minutes"
+msgstr "10 minutes"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:17
+msgid "15 minutes"
+msgstr "15 minutes"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:18
+msgid "30 minutes"
+msgstr "30 minutes"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:19
+msgid "60 minutes"
+msgstr "60 minutes"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:31
+msgid "Auto-refresh"
+msgstr "Rafraîchissement automatique"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:37
+msgid "Refreshing in"
+msgstr "Rafraîchissement dans"
+
+#: frontend/src/metabase/dashboard/components/RemoveFromDashboardModal.jsx:37
+msgid "Remove this question?"
+msgstr "Enlever cette question ?"
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:70
+msgid "Your dashboard was saved"
+msgstr "Votre tableau de bord a été sauvegardé"
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:75
+msgid "See it"
+msgstr "Voir le résultat"
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:132
+msgid "Save this"
+msgstr "Sauvegarder tout cela"
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:165
+msgid "Show more about this"
+msgstr "Afficher plus d'information à ce propos"
+
+#: frontend/src/metabase/dashboard/containers/DashCardCardParameterMapper.jsx:140
+msgid "This card doesn't have any fields or parameters that can be mapped to this parameter type."
+msgstr "Cette carte n'a aucun champ ou paramètre qui pourrait être mis en correspondance avec ce type de paramètre."
+
+#: frontend/src/metabase/dashboard/containers/DashCardCardParameterMapper.jsx:142
+msgid "The values in this field don't overlap with the values of any other fields you've chosen."
+msgstr "Les valeurs de ce champ ne chevauchent les valeurs d'aucun autre champ sélectionné."
+
+#: frontend/src/metabase/dashboard/containers/DashCardCardParameterMapper.jsx:186
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingFieldPicker.jsx:15
+msgid "No valid fields"
+msgstr "Aucun champ valide"
+
+#: frontend/src/metabase/entities/collections.js:90
+msgid "Name must be 100 characters or less"
+msgstr "Le nom ne pas contenir plus de 100 caractères"
+
+#: frontend/src/metabase/entities/collections.js:104
+msgid "Color is required"
+msgstr "Une couleur est exigée"
+
+#: frontend/src/metabase/entities/dashboards.js:97
+msgid "What is the name of your dashboard?"
+msgstr "Quel est le nom de votre tableau de bord"
+
+#: frontend/src/metabase/home/components/Activity.jsx:92
+msgid "did some super awesome stuff that's hard to describe"
+msgstr "a fait des merveilles difficiles à expliquer"
+
+#: frontend/src/metabase/home/components/Activity.jsx:101
+#: frontend/src/metabase/home/components/Activity.jsx:116
+msgid "created an alert about - "
+msgstr "a créé une alerte concernant - "
+
+#: frontend/src/metabase/home/components/Activity.jsx:126
+#: frontend/src/metabase/home/components/Activity.jsx:141
+msgid "deleted an alert about - "
+msgstr "a supprimé une alerte concernant - "
+
+#: frontend/src/metabase/home/components/Activity.jsx:152
+msgid "saved a question about "
+msgstr "a sauvegardé une question concernant "
+
+#: frontend/src/metabase/home/components/Activity.jsx:165
+msgid "saved a question"
+msgstr "a sauvegardé une question"
+
+#: frontend/src/metabase/home/components/Activity.jsx:169
+msgid "deleted a question"
+msgstr "a supprimé une question"
+
+#. correction de l'impératif au passé composé (historique des modifications)
+#: frontend/src/metabase/home/components/Activity.jsx:172
+msgid "created a dashboard"
+msgstr "a créé un tableau de bord"
+
+#: frontend/src/metabase/home/components/Activity.jsx:175
+msgid "deleted a dashboard"
+msgstr "supprimer un tableau de bord"
+
+#: frontend/src/metabase/home/components/Activity.jsx:181
+#: frontend/src/metabase/home/components/Activity.jsx:196
+msgid "added a question to the dashboard - "
+msgstr "a ajouté une question au tableau de bord "
+
+#: frontend/src/metabase/home/components/Activity.jsx:206
+#: frontend/src/metabase/home/components/Activity.jsx:221
+msgid "removed a question from the dashboard - "
+msgstr "a enlevé une question du tableau de bord - "
+
+#: frontend/src/metabase/home/components/Activity.jsx:231
+#: frontend/src/metabase/home/components/Activity.jsx:238
+msgid "received the latest data from"
+msgstr "a reçu les dernières données de"
+
+#: frontend/src/metabase/home/components/Activity.jsx:244
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:273
+msgid "Unknown"
+msgstr "Inconnu"
+
+#: frontend/src/metabase/home/components/Activity.jsx:251
+msgid "Hello World!"
+msgstr "Salut la compagnie !"
+
+#: frontend/src/metabase/home/components/Activity.jsx:252
+msgid "Metabase is up and running."
+msgstr "Metabase est démarré"
+
+#: frontend/src/metabase/home/components/Activity.jsx:258
+#: frontend/src/metabase/home/components/Activity.jsx:288
+msgid "added the metric "
+msgstr "a ajouté la métrique "
+
+#: frontend/src/metabase/home/components/Activity.jsx:272
+#: frontend/src/metabase/home/components/Activity.jsx:362
+msgid " to the "
+msgstr " au "
+
+#: frontend/src/metabase/home/components/Activity.jsx:282
+#: frontend/src/metabase/home/components/Activity.jsx:322
+#: frontend/src/metabase/home/components/Activity.jsx:372
+#: frontend/src/metabase/home/components/Activity.jsx:413
+msgid " table"
+msgstr " table"
+
+#: frontend/src/metabase/home/components/Activity.jsx:298
+#: frontend/src/metabase/home/components/Activity.jsx:328
+msgid "made changes to the metric "
+msgstr "a fait des changements sur la métrique "
+
+#: frontend/src/metabase/home/components/Activity.jsx:312
+#: frontend/src/metabase/home/components/Activity.jsx:403
+msgid " in the "
+msgstr " dans le/la "
+
+#: frontend/src/metabase/home/components/Activity.jsx:335
+msgid "removed the metric "
+msgstr "a supprimé la métrique "
+
+#: frontend/src/metabase/home/components/Activity.jsx:338
+msgid "created a pulse"
+msgstr "a créé un pulse"
+
+#: frontend/src/metabase/home/components/Activity.jsx:341
+msgid "deleted a pulse"
+msgstr "a supprimé un pulse"
+
+#: frontend/src/metabase/home/components/Activity.jsx:347
+#: frontend/src/metabase/home/components/Activity.jsx:378
+msgid "added the filter"
+msgstr "a a jouté le filtre"
+
+#: frontend/src/metabase/home/components/Activity.jsx:388
+#: frontend/src/metabase/home/components/Activity.jsx:419
+msgid "made changes to the filter"
+msgstr "a fait des modifications au filtre"
+
+#: frontend/src/metabase/home/components/Activity.jsx:426
+msgid "removed the filter {0}"
+msgstr "a supprimé le filtre {0}"
+
+#: frontend/src/metabase/home/components/Activity.jsx:429
+msgid "joined!"
+msgstr "nous a rejoint !"
+
+#: frontend/src/metabase/home/components/Activity.jsx:529
+msgid "Hmmm, looks like nothing has happened yet."
+msgstr "Humm, il semble que rien ne se soit encore passé"
+
+#: frontend/src/metabase/home/components/Activity.jsx:532
+msgid "Save a question and get this baby going!"
+msgstr "Sauvegardez une question et en avant !"
+
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:19
+msgid "Ask questions and explore"
+msgstr "Poser des questions et explorer"
+
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:20
+msgid "Click on charts or tables to explore, or ask a new question using the easy interface or the powerful SQL editor."
+msgstr "Cliquer sur les graphiques ou les tables pour les explorer, ou poser une nouvelle question en utilisant le générateur intuitif ou le puissant éditeur SQL."
+
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:30
+msgid "Make your own charts"
+msgstr "Construire vos propres graphiques"
+
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:31
+msgid "Create line charts, scatter plots, maps, and more."
+msgstr "Créer des graphiques, nuages de points, cartes, et bien plus."
+
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:41
+msgid "Share what you find"
+msgstr "Partagez vos découvertes"
+
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:42
+msgid "Create powerful and flexible dashboards, and send regular updates via email or Slack."
+msgstr "Créer des tableaux de bord puissants et flexibles, et envoyer des mises à jour régulières par courriel ou via Slack."
+
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:97
+msgid "Let's go"
+msgstr "Allons-y"
+
+#: frontend/src/metabase/home/components/NextStep.jsx:34
+msgid "Setup Tip"
+msgstr "Astuce de réglage"
+
+#: frontend/src/metabase/home/components/NextStep.jsx:40
+msgid "View all"
+msgstr "Tout voir"
+
+#: frontend/src/metabase/home/components/RecentViews.jsx:40
+msgid "Recently Viewed"
+msgstr "Consulté récemment"
+
+#: frontend/src/metabase/home/components/RecentViews.jsx:75
+msgid "You haven't looked at any dashboards or questions recently"
+msgstr "Vous n'avez consulté aucun tableau de bord ou question récemment"
+
+#: frontend/src/metabase/home/containers/ArchiveApp.jsx:82
+msgid "{0} items selected"
+msgstr "{0} élément(s) sélectionné(s)"
+
+#: frontend/src/metabase/home/containers/ArchiveApp.jsx:102
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:172
+msgid "Unarchive"
+msgstr "Désarchiver"
+
+#: frontend/src/metabase/home/containers/HomepageApp.jsx:74
+#: frontend/src/metabase/nav/containers/Navbar.jsx:327
+msgid "Activity"
+msgstr "Activité"
+
+#: frontend/src/metabase/home/containers/SearchApp.jsx:28
+msgid "Results for \"{0}\""
+msgstr "Résultats pour \"{0}\""
+
+#. Le terme "Pulse" désigne une fonctionnalité bien spécifique de Metabase, je pense qu'on ne devrait pas le traduire. Surtout qu'en Français "Pulse" est très compréhensible.
+#: frontend/src/metabase/home/containers/SearchApp.jsx:142
+msgid "Pulse"
+msgstr "Pulse"
+
+#: frontend/src/metabase/lib/core.js:7
+msgid "Entity Key"
+msgstr "Clé d'entité (PK)"
+
+#: frontend/src/metabase/lib/core.js:8 frontend/src/metabase/lib/core.js:14
+#: frontend/src/metabase/lib/core.js:20
+msgid "Overall Row"
+msgstr "Toutes les lignes"
+
+#: frontend/src/metabase/lib/core.js:9
+msgid "The primary key for this table."
+msgstr "La clé primaire de cette table"
+
+#: frontend/src/metabase/lib/core.js:13
+msgid "Entity Name"
+msgstr "Nom de l'entité"
+
+#: frontend/src/metabase/lib/core.js:15
+msgid "The \"name\" of each record. Usually a column called \"name\", \"title\", etc."
+msgstr "Nom, libellé, clé candidate de chaque enregistrement. Habituellement une colonne nommée \"nom\", \"titre\", \"libellé\", etc. (UK)"
+
+#: frontend/src/metabase/lib/core.js:19
+msgid "Foreign Key"
+msgstr "Clé étrangère (FK)"
+
+#: frontend/src/metabase/lib/core.js:21
+msgid "Points to another table to make a connection."
+msgstr "Référence la clé primaire d'une autre table et établit une connexion."
+
+#: frontend/src/metabase/lib/core.js:25
+msgid "Avatar Image URL"
+msgstr "URL de l'image d'avatar"
+
+#: frontend/src/metabase/lib/core.js:26 frontend/src/metabase/lib/core.js:31
+#: frontend/src/metabase/lib/core.js:36 frontend/src/metabase/lib/core.js:41
+#: frontend/src/metabase/lib/core.js:46 frontend/src/metabase/lib/core.js:51
+#: frontend/src/metabase/lib/core.js:56 frontend/src/metabase/lib/core.js:61
+#: frontend/src/metabase/lib/core.js:66 frontend/src/metabase/lib/core.js:71
+#: frontend/src/metabase/lib/core.js:76 frontend/src/metabase/lib/core.js:81
+#: frontend/src/metabase/lib/core.js:86 frontend/src/metabase/lib/core.js:91
+#: frontend/src/metabase/lib/core.js:96 frontend/src/metabase/lib/core.js:101
+#: frontend/src/metabase/lib/core.js:106 frontend/src/metabase/lib/core.js:111
+#: frontend/src/metabase/lib/core.js:116 frontend/src/metabase/lib/core.js:121
+#: frontend/src/metabase/lib/core.js:126 frontend/src/metabase/lib/core.js:131
+#: frontend/src/metabase/lib/core.js:136 frontend/src/metabase/lib/core.js:141
+#: frontend/src/metabase/lib/core.js:146 frontend/src/metabase/lib/core.js:151
+#: frontend/src/metabase/lib/core.js:156 frontend/src/metabase/lib/core.js:161
+#: frontend/src/metabase/lib/core.js:166 frontend/src/metabase/lib/core.js:171
+#: frontend/src/metabase/lib/core.js:176 frontend/src/metabase/lib/core.js:181
+#: frontend/src/metabase/lib/core.js:186 frontend/src/metabase/lib/core.js:191
+#: frontend/src/metabase/lib/core.js:196 frontend/src/metabase/lib/core.js:201
+#: frontend/src/metabase/lib/core.js:206 frontend/src/metabase/lib/core.js:211
+#: frontend/src/metabase/lib/core.js:216 frontend/src/metabase/lib/core.js:221
+#: frontend/src/metabase/lib/core.js:226
+msgid "Common"
+msgstr "Commun"
+
+#: frontend/src/metabase/lib/core.js:30
+#: frontend/src/metabase/meta/Dashboard.js:82
+#: frontend/src/metabase/qb/components/actions/PivotByCategoryAction.jsx:9
+msgid "Category"
+msgstr "Catégorie"
+
+#: frontend/src/metabase/lib/core.js:35
+#: frontend/src/metabase/meta/Dashboard.js:62
+msgid "City"
+msgstr "Ville/Commune"
+
+#: frontend/src/metabase/lib/core.js:40
+#: frontend/src/metabase/meta/Dashboard.js:74
+msgid "Country"
+msgstr "Pays"
+
+#: frontend/src/metabase/lib/core.js:55
+msgid "Enum"
+msgstr "Enumération"
+
+#: frontend/src/metabase/lib/core.js:60
+msgid "Image URL"
+msgstr "URL d'une image"
+
+#: frontend/src/metabase/lib/core.js:65
+msgid "Field containing JSON"
+msgstr "Champ JSON"
+
+#: frontend/src/metabase/lib/core.js:70
+msgid "Latitude"
+msgstr "Latitude"
+
+#: frontend/src/metabase/lib/core.js:75
+msgid "Longitude"
+msgstr "Longitude"
+
+#: frontend/src/metabase/lib/core.js:80
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:146
+#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:22
+msgid "Number"
+msgstr "Numérique"
+
+#: frontend/src/metabase/lib/core.js:85
+#: frontend/src/metabase/meta/Dashboard.js:66
+msgid "State"
+msgstr "Etat d'un pays"
+
+#: frontend/src/metabase/lib/core.js:90
+msgid "UNIX Timestamp (Seconds)"
+msgstr "Horodatage UNIX (Secondes)"
+
+#: frontend/src/metabase/lib/core.js:95
+msgid "UNIX Timestamp (Milliseconds)"
+msgstr "Horodatage UNIX (Millisecondes)"
+
+#: frontend/src/metabase/lib/core.js:105
+msgid "Zip Code"
+msgstr "Code postal"
+
+#: frontend/src/metabase/lib/core.js:110
+msgid "Quantity"
+msgstr "Quantité"
+
+#: frontend/src/metabase/lib/core.js:115
+msgid "Income"
+msgstr "Revenu"
+
+#: frontend/src/metabase/lib/core.js:120
+msgid "Discount"
+msgstr "Remise"
+
+#: frontend/src/metabase/lib/core.js:125
+msgid "Creation timestamp"
+msgstr "Horodatage de création"
+
+#: frontend/src/metabase/lib/core.js:130
+msgid "Creation time"
+msgstr "Heure de création"
+
+#: frontend/src/metabase/lib/core.js:135
+msgid "Creation date"
+msgstr "Date de création"
+
+#: frontend/src/metabase/lib/core.js:140
+msgid "Product"
+msgstr "Produit"
+
+#: frontend/src/metabase/lib/core.js:145
+msgid "User"
+msgstr "Utilisateur"
+
+#: frontend/src/metabase/lib/core.js:150
+msgid "Source"
+msgstr "Origine"
+
+#: frontend/src/metabase/lib/core.js:155
+msgid "Price"
+msgstr "Prix"
+
+#: frontend/src/metabase/lib/core.js:160
+msgid "Join timestamp"
+msgstr "Joindre l'horodatage"
+
+#: frontend/src/metabase/lib/core.js:165
+msgid "Join time"
+msgstr "Joindre l'heure"
+
+#: frontend/src/metabase/lib/core.js:170
+msgid "Join date"
+msgstr "Joindre la date"
+
+#. Il semble que ce soit plus dans un context économique ou financier
+#: frontend/src/metabase/lib/core.js:175
+msgid "Share"
+msgstr "Action/Part"
+
+#: frontend/src/metabase/lib/core.js:180
+msgid "Owner"
+msgstr "Propriétaire"
+
+#: frontend/src/metabase/lib/core.js:185
+msgid "Company"
+msgstr "Entreprise"
+
+#: frontend/src/metabase/lib/core.js:190
+msgid "Subscription"
+msgstr "Inscription"
+
+#: frontend/src/metabase/lib/core.js:195
+msgid "Score"
+msgstr "Score"
+
+#: frontend/src/metabase/lib/core.js:205
+#: frontend/src/metabase/public/components/widgets/DisplayOptionsPane.jsx:49
+#: frontend/src/metabase/visualizations/lib/settings.js:156
+msgid "Title"
+msgstr "Titre"
+
+#: frontend/src/metabase/lib/core.js:210
+msgid "Comment"
+msgstr "Commentaire"
+
+#: frontend/src/metabase/lib/core.js:215
+msgid "Cost"
+msgstr "Coût"
+
+#: frontend/src/metabase/lib/core.js:220
+msgid "Gross margin"
+msgstr "Marge brute"
+
+#: frontend/src/metabase/lib/core.js:225
+msgid "Birthday"
+msgstr "Date de naissance"
+
+#: frontend/src/metabase/lib/core.js:236
+msgid "Search box"
+msgstr "Zone de recherche avec autocomplétion"
+
+#: frontend/src/metabase/lib/core.js:237
+msgid "A list of all values"
+msgstr "Liste de toutes les valeurs"
+
+#: frontend/src/metabase/lib/core.js:238
+msgid "Plain input box"
+msgstr "Zone de texte"
+
+#: frontend/src/metabase/lib/core.js:244
+msgid "Everywhere"
+msgstr "Partout"
+
+#: frontend/src/metabase/lib/core.js:245
+msgid "The default setting. This field will be displayed normally in tables and charts."
+msgstr "Réglage par défaut. Ce champ sera affiché normalement dans les tables et graphiques."
+
+#: frontend/src/metabase/lib/core.js:249
+msgid "Only in Detail Views"
+msgstr "Seulement dans les vues détaillées"
+
+#: frontend/src/metabase/lib/core.js:250
+msgid "This field will only be displayed when viewing the details of a single record. Use this for information that's lengthy or that isn't useful in a table or chart."
+msgstr "Ce champ sera affiché seulement dans la vue détaillée d'un enregistrement. Utilisez ce réglage pour les informations verbeuses ou non pertinentes dans une table ou un graphique."
+
+#: frontend/src/metabase/lib/core.js:254
+msgid "Do Not Include"
+msgstr "Nulle part"
+
+#: frontend/src/metabase/lib/core.js:255
+msgid "Metabase will never retrieve this field. Use this for sensitive or irrelevant information."
+msgstr "Metabase ne récupèrera jamais ce champ. Utilisez ce réglage pour les informations sensibles ou non pertinentes."
+
+#: frontend/src/metabase/lib/expressions/config.js:7
+#: frontend/src/metabase/lib/query.js:615
+#: frontend/src/metabase/visualizations/lib/utils.js:113
+msgid "Count"
+msgstr "Nombre de lignes"
+
+#: frontend/src/metabase/lib/expressions/config.js:8
+msgid "CumulativeCount"
+msgstr "Nombre cumulé de lignes"
+
+#: frontend/src/metabase/lib/expressions/config.js:9
+#: frontend/src/metabase/qb/components/drill/SummarizeColumnDrill.js:17
+#: frontend/src/metabase/visualizations/lib/utils.js:114
+msgid "Sum"
+msgstr "Somme"
+
+#: frontend/src/metabase/lib/expressions/config.js:10
+msgid "CumulativeSum"
+msgstr "Somme cumulée"
+
+#: frontend/src/metabase/lib/expressions/config.js:11
+#: frontend/src/metabase/visualizations/lib/utils.js:115
+msgid "Distinct"
+msgstr "Nombre de valeurs distinctes"
+
+#: frontend/src/metabase/lib/expressions/config.js:12
+msgid "StandardDeviation"
+msgstr "Ecart-type"
+
+#: frontend/src/metabase/lib/expressions/config.js:13
+#: frontend/src/metabase/visualizations/lib/utils.js:112
+msgid "Average"
+msgstr "Moyenne"
+
+#: frontend/src/metabase/lib/expressions/config.js:14
+#: frontend/src/metabase/qb/components/drill/SummarizeColumnDrill.js:25
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:363
+msgid "Min"
+msgstr "Minimum"
+
+#: frontend/src/metabase/lib/expressions/config.js:15
+#: frontend/src/metabase/qb/components/drill/SummarizeColumnDrill.js:29
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:371
+msgid "Max"
+msgstr "Maximum"
+
+#: frontend/src/metabase/lib/expressions/parser.js:384
+msgid "sad sad panda, lexing errors detected"
+msgstr "Des ereurs syntaxiques ou lexicales ont été détectées"
+
+#: frontend/src/metabase/lib/formatting.js:447
+msgid "{0} second"
+msgid_plural "{0} seconds"
+msgstr[0] "{0} seconde"
+msgstr[1] "{0} secondes"
+
+#: frontend/src/metabase/lib/formatting.js:450
+msgid "{0} minute"
+msgid_plural "{0} minutes"
+msgstr[0] "{0} minute"
+msgstr[1] "{0} minutes"
+
+#: frontend/src/metabase/lib/greeting.js:4
+msgid "Hey there"
+msgstr "Héy"
+
+#: frontend/src/metabase/lib/greeting.js:5
+#: frontend/src/metabase/lib/greeting.js:29
+msgid "How's it going"
+msgstr "Comment ça va"
+
+#: frontend/src/metabase/lib/greeting.js:6
+msgid "Howdy"
+msgstr "Salut"
+
+#: frontend/src/metabase/lib/greeting.js:7
+msgid "Greetings"
+msgstr "Bienvenue"
+
+#: frontend/src/metabase/lib/greeting.js:8
+msgid "Good to see you"
+msgstr "Un plaisir de vous revoir"
+
+#: frontend/src/metabase/lib/greeting.js:12
+msgid "What do you want to know?"
+msgstr "Que voulez-vous savoir ?"
+
+#: frontend/src/metabase/lib/greeting.js:13
+msgid "What's on your mind?"
+msgstr "Qu'avez-vous en tête ?"
+
+#: frontend/src/metabase/lib/greeting.js:14
+msgid "What do you want to find out?"
+msgstr "Que voulez-vous déterminer ?"
+
+#: frontend/src/metabase/lib/query.js:613
+#: frontend/src/metabase/lib/schema_metadata.js:441
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:246
+msgid "Raw data"
+msgstr "Données brutes"
+
+#: frontend/src/metabase/lib/query.js:617
+msgid "Cumulative count"
+msgstr "Nombre cumulé"
+
+#: frontend/src/metabase/lib/query.js:620
+msgid "Average of "
+msgstr "Moyenne du/de la "
+
+#: frontend/src/metabase/lib/query.js:625
+msgid "Distinct values of "
+msgstr "Valeurs distinctes du/de la "
+
+#: frontend/src/metabase/lib/query.js:630
+msgid "Standard deviation of "
+msgstr "Ecart-type du/de la "
+
+#: frontend/src/metabase/lib/query.js:635
+msgid "Sum of "
+msgstr "Somme du/de la "
+
+#: frontend/src/metabase/lib/query.js:640
+msgid "Cumulative sum of "
+msgstr "Somme cumulée du/de la "
+
+#: frontend/src/metabase/lib/query.js:645
+msgid "Maximum of "
+msgstr "Maximum du/de la "
+
+#: frontend/src/metabase/lib/query.js:650
+msgid "Minimum of "
+msgstr "Minimum du/de la "
+
+#: frontend/src/metabase/lib/query.js:664
+msgid "Grouped by "
+msgstr "Aggrégé par "
+
+#: frontend/src/metabase/lib/query.js:678
+msgid "Filtered by "
+msgstr "Filtré(e) par "
+
+#: frontend/src/metabase/lib/query.js:706
+msgid "Sorted by "
+msgstr "Trié par "
+
+#: frontend/src/metabase/lib/schema_metadata.js:211
+msgid "True"
+msgstr "Vrai"
+
+#: frontend/src/metabase/lib/schema_metadata.js:211
+msgid "False"
+msgstr "Faux"
+
+#: frontend/src/metabase/lib/schema_metadata.js:295
+msgid "Select longitude field"
+msgstr "Sélectionner le champ longitude"
+
+#: frontend/src/metabase/lib/schema_metadata.js:296
+msgid "Enter upper latitude"
+msgstr "Saisir la latitude supérieure"
+
+#: frontend/src/metabase/lib/schema_metadata.js:297
+msgid "Enter left longitude"
+msgstr "Saisir la longitude inférieure"
+
+#: frontend/src/metabase/lib/schema_metadata.js:298
+msgid "Enter lower latitude"
+msgstr "Saisir la latitude inférieure"
+
+#: frontend/src/metabase/lib/schema_metadata.js:299
+msgid "Enter right longitude"
+msgstr "Saisir la longitude supérieure"
+
+#: frontend/src/metabase/lib/schema_metadata.js:335
+#: frontend/src/metabase/lib/schema_metadata.js:355
+#: frontend/src/metabase/lib/schema_metadata.js:365
+#: frontend/src/metabase/lib/schema_metadata.js:371
+#: frontend/src/metabase/lib/schema_metadata.js:379
+#: frontend/src/metabase/lib/schema_metadata.js:385
+#: frontend/src/metabase/lib/schema_metadata.js:390
+msgid "Is"
+msgstr "Est"
+
+#: frontend/src/metabase/lib/schema_metadata.js:336
+#: frontend/src/metabase/lib/schema_metadata.js:356
+#: frontend/src/metabase/lib/schema_metadata.js:366
+#: frontend/src/metabase/lib/schema_metadata.js:380
+#: frontend/src/metabase/lib/schema_metadata.js:386
+msgid "Is not"
+msgstr "N'est pas"
+
+#: frontend/src/metabase/lib/schema_metadata.js:337
+#: frontend/src/metabase/lib/schema_metadata.js:351
+#: frontend/src/metabase/lib/schema_metadata.js:359
+#: frontend/src/metabase/lib/schema_metadata.js:367
+#: frontend/src/metabase/lib/schema_metadata.js:375
+#: frontend/src/metabase/lib/schema_metadata.js:381
+#: frontend/src/metabase/lib/schema_metadata.js:391
+msgid "Is empty"
+msgstr "Est vide"
+
+#: frontend/src/metabase/lib/schema_metadata.js:338
+#: frontend/src/metabase/lib/schema_metadata.js:352
+#: frontend/src/metabase/lib/schema_metadata.js:360
+#: frontend/src/metabase/lib/schema_metadata.js:368
+#: frontend/src/metabase/lib/schema_metadata.js:376
+#: frontend/src/metabase/lib/schema_metadata.js:382
+#: frontend/src/metabase/lib/schema_metadata.js:392
+msgid "Not empty"
+msgstr "Non vide"
+
+#: frontend/src/metabase/lib/schema_metadata.js:344
+msgid "Equal to"
+msgstr "Egal(e) à"
+
+#: frontend/src/metabase/lib/schema_metadata.js:345
+msgid "Not equal to"
+msgstr "Différent(e) de"
+
+#: frontend/src/metabase/lib/schema_metadata.js:346
+msgid "Greater than"
+msgstr "Supérieur à"
+
+#: frontend/src/metabase/lib/schema_metadata.js:347
+msgid "Less than"
+msgstr "Inférieur à"
+
+#: frontend/src/metabase/lib/schema_metadata.js:348
+#: frontend/src/metabase/lib/schema_metadata.js:374
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:289
+#: frontend/src/metabase/query_builder/components/filters/pickers/TimePicker.jsx:96
+msgid "Between"
+msgstr "Entre"
+
+#: frontend/src/metabase/lib/schema_metadata.js:349
+msgid "Greater than or equal to"
+msgstr "Supérieur ou égal à"
+
+#: frontend/src/metabase/lib/schema_metadata.js:350
+msgid "Less than or equal to"
+msgstr "Inférieur ou égal à"
+
+#: frontend/src/metabase/lib/schema_metadata.js:357
+msgid "Contains"
+msgstr "Contient"
+
+#: frontend/src/metabase/lib/schema_metadata.js:358
+msgid "Does not contain"
+msgstr "Ne contient pas"
+
+#: frontend/src/metabase/lib/schema_metadata.js:361
+msgid "Starts with"
+msgstr "Commence par"
+
+#: frontend/src/metabase/lib/schema_metadata.js:362
+msgid "Ends with"
+msgstr "Finit par"
+
+#: frontend/src/metabase/lib/schema_metadata.js:372
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:268
+#: frontend/src/metabase/query_builder/components/filters/pickers/TimePicker.jsx:74
+msgid "Before"
+msgstr "Avant"
+
+#: frontend/src/metabase/lib/schema_metadata.js:373
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:275
+#: frontend/src/metabase/query_builder/components/filters/pickers/TimePicker.jsx:85
+msgid "After"
+msgstr "Après"
+
+#: frontend/src/metabase/lib/schema_metadata.js:387
+msgid "Inside"
+msgstr "Dans"
+
+#: frontend/src/metabase/lib/schema_metadata.js:443
+msgid "Just a table with the rows in the answer, no additional operations."
+msgstr "Juste une table avec ces lignes dans la réponse, aucune opération additionnelle."
+
+#: frontend/src/metabase/lib/schema_metadata.js:449
+msgid "Count of rows"
+msgstr "Nombre de lignes"
+
+#: frontend/src/metabase/lib/schema_metadata.js:451
+msgid "Total number of rows in the answer."
+msgstr "Nombre total de lignes dans la réponse."
+
+#: frontend/src/metabase/lib/schema_metadata.js:457
+msgid "Sum of ..."
+msgstr "Somme de ..."
+
+#: frontend/src/metabase/lib/schema_metadata.js:459
+msgid "Sum of all the values of a column."
+msgstr "Somme de toutes les valeurs d'une colonne."
+
+#: frontend/src/metabase/lib/schema_metadata.js:465
+msgid "Average of ..."
+msgstr "Moyenne de ..."
+
+#: frontend/src/metabase/lib/schema_metadata.js:467
+msgid "Average of all the values of a column"
+msgstr "Moyenne de toutes les valeurs d'une colonne"
+
+#: frontend/src/metabase/lib/schema_metadata.js:473
+msgid "Number of distinct values of ..."
+msgstr "Nombre de valeurs distinctes de ..."
+
+#: frontend/src/metabase/lib/schema_metadata.js:475
+msgid "Number of unique values of a column among all the rows in the answer."
+msgstr "Nombre de valeurs uniques d'une colonne parmi toutes les lignes dans la réponse."
+
+#: frontend/src/metabase/lib/schema_metadata.js:481
+msgid "Cumulative sum of ..."
+msgstr "Somme cumulée de ..."
+
+#: frontend/src/metabase/lib/schema_metadata.js:483
+msgid "Additive sum of all the values of a column.\\ne.x. total revenue over time."
+msgstr "Somme cumulée de toutes les valeurs d'une colonne. Ex : Gain total au cours du temps."
+
+#: frontend/src/metabase/lib/schema_metadata.js:489
+msgid "Cumulative count of rows"
+msgstr "Nombre cumulé de lignes"
+
+#: frontend/src/metabase/lib/schema_metadata.js:491
+msgid "Additive count of the number of rows.\\ne.x. total number of sales over time."
+msgstr "Nombre cumulé de lignes. Ex : Nombre total de ventes au cours du temps."
+
+#: frontend/src/metabase/lib/schema_metadata.js:497
+msgid "Standard deviation of ..."
+msgstr "Ecart-type de ..."
+
+#: frontend/src/metabase/lib/schema_metadata.js:499
+msgid "Number which expresses how much the values of a column vary among all rows in the answer."
+msgstr "Nombre qui exprime la dispersion des valeurs d'une colonne parmi toutes les lignes dans la réponse."
+
+#: frontend/src/metabase/lib/schema_metadata.js:505
+msgid "Minimum of ..."
+msgstr "Minimum de ..."
+
+#: frontend/src/metabase/lib/schema_metadata.js:507
+msgid "Minimum value of a column"
+msgstr "Valeur minimum d'une colonne"
+
+#: frontend/src/metabase/lib/schema_metadata.js:513
+msgid "Maximum of ..."
+msgstr "Maximum de ..."
+
+#: frontend/src/metabase/lib/schema_metadata.js:515
+msgid "Maximum value of a column"
+msgstr "Valeur maximum d'une colonne"
+
+#: frontend/src/metabase/lib/schema_metadata.js:523
+msgid "Break out by dimension"
+msgstr "Eclater par dimension"
+
+#: frontend/src/metabase/lib/settings.js:93
+msgid "lower case letter"
+msgstr "minuscule"
+
+#: frontend/src/metabase/lib/settings.js:95
+msgid "upper case letter"
+msgstr "majuscule"
+
+#: frontend/src/metabase/lib/settings.js:97
+#: src/metabase/automagic_dashboards/core.clj
+msgid "number"
+msgstr "nombre"
+
+#: frontend/src/metabase/lib/settings.js:99
+msgid "special character"
+msgstr "caractère spécial"
+
+#: frontend/src/metabase/lib/settings.js:105
+msgid "must be"
+msgstr "doit avoir une taille de"
+
+#: frontend/src/metabase/lib/settings.js:105
+#: frontend/src/metabase/lib/settings.js:106
+msgid "characters long"
+msgstr "caractères"
+
+#: frontend/src/metabase/lib/settings.js:106
+msgid "Must be"
+msgstr "Doit être"
+
+#: frontend/src/metabase/lib/settings.js:122
+msgid "and include"
+msgstr "et inclure"
+
+#: frontend/src/metabase/lib/utils.js:92
+msgid "zero"
+msgstr "zéro"
+
+#: frontend/src/metabase/lib/utils.js:93
+msgid "one"
+msgstr "un"
+
+#: frontend/src/metabase/lib/utils.js:94
+msgid "two"
+msgstr "deux"
+
+#: frontend/src/metabase/lib/utils.js:95
+msgid "three"
+msgstr "trois"
+
+#: frontend/src/metabase/lib/utils.js:96
+msgid "four"
+msgstr "quatre"
+
+#: frontend/src/metabase/lib/utils.js:97
+msgid "five"
+msgstr "cinq"
+
+#: frontend/src/metabase/lib/utils.js:98
+msgid "six"
+msgstr "six"
+
+#: frontend/src/metabase/lib/utils.js:99
+msgid "seven"
+msgstr "sept"
+
+#: frontend/src/metabase/lib/utils.js:100
+msgid "eight"
+msgstr "huit"
+
+#: frontend/src/metabase/lib/utils.js:101
+msgid "nine"
+msgstr "neuf"
+
+#: frontend/src/metabase/meta/Dashboard.js:31
+msgid "Month and Year"
+msgstr "Mois et Année"
+
+#: frontend/src/metabase/meta/Dashboard.js:32
+msgid "Like January, 2016"
+msgstr "Par exemple Janvier 2016"
+
+#: frontend/src/metabase/meta/Dashboard.js:36
+msgid "Quarter and Year"
+msgstr "Semestre et Année"
+
+#: frontend/src/metabase/meta/Dashboard.js:37
+msgid "Like Q1, 2016"
+msgstr "Par exemple Q1, 2016"
+
+#: frontend/src/metabase/meta/Dashboard.js:41
+msgid "Single Date"
+msgstr "Date simple"
+
+#: frontend/src/metabase/meta/Dashboard.js:42
+msgid "Like January 31, 2016"
+msgstr "Par exemple 31 Janvier 2016"
+
+#: frontend/src/metabase/meta/Dashboard.js:46
+msgid "Date Range"
+msgstr "Intervalle de dates"
+
+#: frontend/src/metabase/meta/Dashboard.js:47
+msgid "Like December 25, 2015 - February 14, 2016"
+msgstr "Par exemple 25 Décembre 2015 - 14 Février 2016"
+
+#: frontend/src/metabase/meta/Dashboard.js:51
+msgid "Relative Date"
+msgstr "Date relative"
+
+#: frontend/src/metabase/meta/Dashboard.js:52
+msgid "Like \"the last 7 days\" or \"this month\""
+msgstr "Par exemple \"les 7 derniers jours\" ou \"ce mois-ci\""
+
+#: frontend/src/metabase/meta/Dashboard.js:56
+msgid "Date Filter"
+msgstr "Filtre de date"
+
+#: frontend/src/metabase/meta/Dashboard.js:57
+msgid "All Options"
+msgstr "Toutes les options"
+
+#: frontend/src/metabase/meta/Dashboard.js:58
+msgid "Contains all of the above"
+msgstr "Contient tout ce qui précède"
+
+#: frontend/src/metabase/meta/Dashboard.js:70
+msgid "ZIP or Postal Code"
+msgstr "Code postal"
+
+#: frontend/src/metabase/meta/Dashboard.js:78
+#: frontend/src/metabase/meta/Dashboard.js:108
+msgid "ID"
+msgstr "ID"
+
+#: frontend/src/metabase/meta/Dashboard.js:96
+#: frontend/src/metabase/qb/components/actions/PivotByTimeAction.jsx:8
+msgid "Time"
+msgstr "Heure"
+
+#: frontend/src/metabase/meta/Dashboard.js:97
+msgid "Date range, relative date, time of day, etc."
+msgstr "Intervalle de date, date relative, heure du jour, etc."
+
+#: frontend/src/metabase/meta/Dashboard.js:102
+#: frontend/src/metabase/qb/components/actions/PivotByLocationAction.jsx:8
+msgid "Location"
+msgstr "Lieu"
+
+#: frontend/src/metabase/meta/Dashboard.js:103
+msgid "City, State, Country, ZIP code."
+msgstr "Ville, Région, Pays, Code postal."
+
+#: frontend/src/metabase/meta/Dashboard.js:109
+msgid "User ID, product ID, event ID, etc."
+msgstr "ID Utilisateur, produit, évènement, etc."
+
+#: frontend/src/metabase/meta/Dashboard.js:114
+msgid "Other Categories"
+msgstr "Autres catégories"
+
+#: frontend/src/metabase/meta/Dashboard.js:115
+msgid "Category, Type, Model, Rating, etc."
+msgstr "Catégorie, Type, Modèle, Classement"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:43
+#: frontend/src/metabase/user/components/UserSettings.jsx:54
+msgid "Account settings"
+msgstr "Réglages du compte utilisateur"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:50
+msgid "Exit admin"
+msgstr "Quitter le panneau d'administration"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:60
+msgid "Logs"
+msgstr "Journaux"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:67
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorSidebar.jsx:105
+msgid "Help"
+msgstr "Aide"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:76
+msgid "About Metabase"
+msgstr "A propos de Metabase"
+
+#. Les items du menu sont tous des noms...
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:82
+msgid "Sign out"
+msgstr "Déconnexion"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:107
+msgid "Thanks for using"
+msgstr "Merci d'utiliser"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:111
+msgid "You're on version"
+msgstr "Vous êtes en version"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:114
+msgid "Built on"
+msgstr "Sortie le"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:133
+msgid "is a Trademark of"
+msgstr "est une marque de"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:135
+msgid "and is built with care in San Francisco, CA"
+msgstr "et est fabriqué avec soin à San Francisco, CA"
+
+#: frontend/src/metabase/nav/containers/Navbar.jsx:195
+msgid "Metabase Admin"
+msgstr "Admin Metabase"
+
+#: frontend/src/metabase/nav/containers/Navbar.jsx:296
+#: frontend/src/metabase/reference/databases/TableQuestions.jsx:36
+#: frontend/src/metabase/reference/metrics/MetricQuestions.jsx:37
+#: frontend/src/metabase/reference/segments/SegmentQuestions.jsx:37
+msgid "Ask a question"
+msgstr "Poser une question"
+
+#: frontend/src/metabase/nav/containers/Navbar.jsx:305
+msgid "New dashboard"
+msgstr "Nouveau tableau de bord"
+
+#: frontend/src/metabase/nav/containers/Navbar.jsx:311
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:127
+msgid "New pulse"
+msgstr "Nouveau pulse"
+
+#: frontend/src/metabase/nav/containers/Navbar.jsx:319
+msgid "Reference"
+msgstr "Référentiel"
+
+#: frontend/src/metabase/new_query/containers/MetricSearch.jsx:83
+msgid "Which metric?"
+msgstr "Quelle métrique ?"
+
+#: frontend/src/metabase/new_query/containers/MetricSearch.jsx:110
+#: frontend/src/metabase/reference/metrics/MetricList.jsx:24
+msgid "Defining common metrics for your team makes it even easier to ask questions"
+msgstr "Définissez des métriques pour aider votre équipe à poser des questions plus simplement"
+
+#: frontend/src/metabase/new_query/containers/MetricSearch.jsx:113
+msgid "How to create metrics"
+msgstr "Comment créer des métriques"
+
+#. correction d'accord
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:138
+msgid "See data over time, as a map, or pivoted to help you understand trends or changes."
+msgstr "Voir la donnée comme série chronologique, carte, ou tableau croisé pour vous aider à comprendre les tendances ou les changements."
+
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:149
+msgid "Custom"
+msgstr "Personnalisée"
+
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:150
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:517
+msgid "New question"
+msgstr "Nouvelle question"
+
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:152
+msgid "Use the simple question builder to see trends, lists of things, or to create your own metrics."
+msgstr "Utilisez le générateur de questions pour voir des tendances, afficher des listes ou créer vos propres métriques."
+
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:161
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Native query"
+msgstr "Requête native"
+
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:162
+msgid "For more complicated questions, you can write your own SQL or native query."
+msgstr "Pour des questions plus complexes, vous pouvez écrire votre propre requête SQL"
+
+#: frontend/src/metabase/parameters/components/ParameterValueWidget.jsx:240
+msgid "Select a default value…"
+msgstr "Choisir une valeur par défaut"
+
+#: frontend/src/metabase/parameters/components/widgets/DateAllOptionsWidget.jsx:149
+#: frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx:390
+msgid "Update filter"
+msgstr "Mettre à jour le filtre"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:9
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:144
+msgid "Today"
+msgstr "Aujourd'hui"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:14
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:148
+msgid "Yesterday"
+msgstr "Hier"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:18
+msgid "Past 7 days"
+msgstr "Les 7 derniers jours"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:19
+msgid "Past 30 days"
+msgstr "Les 30 derniers jours"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:24
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:29
+#: src/metabase/api/table.clj
+msgid "Week"
+msgstr "Semaine"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:25
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:30
+#: src/metabase/api/table.clj
+msgid "Month"
+msgstr "Mois"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:26
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:31
+#: src/metabase/api/table.clj
+msgid "Year"
+msgstr "Année"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:152
+msgid "Past 7 Days"
+msgstr "Les 7 derniers jours"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:156
+msgid "Past 30 Days"
+msgstr "Les 30 derniers jours"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:160
+msgid "Last Week"
+msgstr "La semaine dernière"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:164
+msgid "Last Month"
+msgstr "Le mois dernier"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:168
+msgid "Last Year"
+msgstr "L'année dernière"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:172
+msgid "This Week"
+msgstr "Cette semaine"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:176
+msgid "This Month"
+msgstr "Ce mois-ci"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:180
+msgid "This Year"
+msgstr "Cette année"
+
+#: frontend/src/metabase/parameters/components/widgets/ParameterFieldWidget.jsx:88
+#: frontend/src/metabase/parameters/components/widgets/TextWidget.jsx:54
+msgid "Enter a value..."
+msgstr "Saisir une valeur..."
+
+#: frontend/src/metabase/parameters/components/widgets/TextWidget.jsx:90
+msgid "Enter a default value..."
+msgstr "Saisir une valeur par défaut..."
+
+#: frontend/src/metabase/public/components/PublicError.jsx:18
+msgid "An error occurred"
+msgstr "Une erreur est survenue"
+
+#: frontend/src/metabase/public/components/PublicNotFound.jsx:11
+msgid "Not found"
+msgstr "Non trouvé"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:82
+msgid "You’ve made changes that need to be published before they will be reflected in your application embed."
+msgstr "Vous avez fait des modifications qui doivent être publiées avant d'être visibles dans l'intégration de votre application."
+
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:83
+msgid "You will need to publish this {0} before you can embed it in another application."
+msgstr "Vous devrez publier ce/cette {0} avant de pouvoir l'intégrer dans une autre application."
+
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:92
+msgid "Discard Changes"
+msgstr "Annuler les modifications"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:99
+msgid "Updating..."
+msgstr "Mise à jour..."
+
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:100
+msgid "Updated"
+msgstr "Mis à jour"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:101
+msgid "Failed!"
+msgstr "Echec !"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:102
+msgid "Publish"
+msgstr "Publier"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:111
+msgid "Code"
+msgstr "Code"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:70
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:157
+msgid "Style"
+msgstr "Style"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:80
+msgid "Which parameters can users of this embed use?"
+msgstr "Quels paramètres les utilisateurs de cette intégration peuvent-ils utiliser ?"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:83
+msgid "This {0} doesn't have any parameters to configure yet."
+msgstr "Le/la {0} n'a encore aucun paramètre à configurer."
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:104
+msgid "Editable"
+msgstr "Modifiable"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:105
+msgid "Locked"
+msgstr "Vérouillé"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:113
+msgid "Preview Locked Parameters"
+msgstr "Aperçu des paramètres verrouillés"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:115
+msgid "Try passing some values to your locked parameters here. Your server will have to provide the actual values in the signed token when using this for real."
+msgstr "Essayez de passer quelques valeurs à vos paramètres verrouillés ici. Votre serveur devra fournir les valeurs réelles dans le jeton signé en situation réelle."
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:126
+msgid "Danger zone"
+msgstr "Zone dangereuse"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:127
+msgid "This will disable embedding for this {0}."
+msgstr "Cela désactivera l'intégration pour ce/cette {0}."
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:128
+msgid "Unpublish"
+msgstr "Retirer la publication"
+
+#: frontend/src/metabase/public/components/widgets/DisplayOptionsPane.jsx:17
+msgid "Light"
+msgstr "Clair"
+
+#: frontend/src/metabase/public/components/widgets/DisplayOptionsPane.jsx:18
+msgid "Dark"
+msgstr "Sombre"
+
+#: frontend/src/metabase/public/components/widgets/DisplayOptionsPane.jsx:37
+msgid "Border"
+msgstr "Bordure"
+
+#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:62
+msgid "To embed this {0} in your application:"
+msgstr "Pour intégrer ce/cette {0} dans votre application:"
+
+#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:64
+msgid "Insert this code snippet in your server code to generate the signed embedding URL "
+msgstr "Insérez cet extrait  dans le code de votre serveur pour générer l'URL d'intégration signée ␣"
+
+#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:87
+msgid "Then insert this code snippet in your HTML template or single page app."
+msgstr "Ensuite, insérez cet extrait de code dans votre modèle HTML ou votre application à page unique."
+
+#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:94
+msgid "Embed code snippet for your HTML or Frontend Application"
+msgstr "Extrait de code pour intégration dans votre HTML ou votre application d'IHM"
+
+#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:101
+msgid "More {0}"
+msgstr "Plus de {0}"
+
+#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:103
+msgid "examples on GitHub"
+msgstr "exemples sur GitHub"
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:72
+msgid "Enable sharing"
+msgstr "Autoriser le partage"
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:76
+msgid "Disable this public link?"
+msgstr "Désactiver ce lien public ?"
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:77
+msgid "This will cause the existing link to stop working. You can re-enable it, but when you do it will be a different link."
+msgstr "Cela empêchera le lien existant de fonctionner. Vous pouvez le réactiver, mais si vous le faites, le lien sera différent."
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:117
+msgid "Public link"
+msgstr "Lien public"
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:118
+msgid "Share this {0} with people who don't have a Metabase account using the URL below:"
+msgstr "Partagez ce/cette {0} avec des personnes qui ne possèdent pas de compte Metabase en utilisant l'URL ci-dessous:"
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:158
+msgid "Public embed"
+msgstr "Intégration publique"
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:159
+msgid "Embed this {0} in blog posts or web pages by copying and pasting this snippet:"
+msgstr "Intégrez ce/cette {0} dans les posts de blog ou les pages web en copiant/collant ce bout de code."
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:176
+msgid "Embed this {0} in an application"
+msgstr "Intégrer ce/cette {0} dans une application"
+
+#. incompréhension dans ma première traduction
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:177
+msgid "By integrating with your application server code, you can provide a secure stats {0} limited to a specific user, customer, organization, etc."
+msgstr "En vous intégrant dans le code de votre serveur d'application, vous pouvez fournir un(e) {0} sécurisé(e) limité(e) à un utilisateur, client, organisation, etc. spécifique."
+
+#: frontend/src/metabase/pulse/components/PulseCardPreview.jsx:94
+msgid "Remove attachment"
+msgstr "Supprimer la pièce jointe"
+
+#: frontend/src/metabase/pulse/components/PulseCardPreview.jsx:95
+msgid "Attach file with results"
+msgstr "Joindre le fichier des résultats"
+
+#: frontend/src/metabase/pulse/components/PulseCardPreview.jsx:127
+msgid "This question will be added as a file attachment"
+msgstr "Cette question sera ajoutée en pièce jointe"
+
+#: frontend/src/metabase/pulse/components/PulseCardPreview.jsx:128
+msgid "This question won't be included in your Pulse"
+msgstr "Cette question ne sera pas incluse dans votre pulse"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:92
+msgid "This pulse will no longer be emailed to {0} {1}"
+msgstr "Ce pulse ne sera plus envoyée par courriel à {0} {1}"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:94
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:374
+msgid "{0} address"
+msgid_plural "{0} addresses"
+msgstr[0] "{0} adresse"
+msgstr[1] "{0} adresses"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:102
+msgid "Slack channel {0} will no longer get this pulse {1}"
+msgstr "Le canal Slack {0} ne recevra plus ce pulse {1}"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:110
+msgid "Channel {0} will no longer receive this pulse {1}"
+msgstr "Le canal {0} ne recevra plus ce pulse {1}"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:127
+msgid "Edit pulse"
+msgstr "Modifier le pulse"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:131
+msgid "What's a Pulse?"
+msgstr "Qu'est ce qu'un Pulse ?"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:141
+msgid "Got it"
+msgstr "J'ai compris"
+
+#. pour accord avec 'Sélectionner vos données'
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:157
+msgid "Where should this data go?"
+msgstr "Où devraient aller ces données ?"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:173
+msgid "Unarchiving…"
+msgstr "Désarchivage en cours..."
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:174
+msgid "Unarchive failed"
+msgstr "Le désarchivage à échoué"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:175
+msgid "Unarchived"
+msgstr "Désarchivé"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:182
+msgid "Create pulse"
+msgstr "Créer un pulse"
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:90
+msgid "Attachment"
+msgstr "Pièce jointe"
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:104
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:111
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:671
+msgid "Heads up"
+msgstr "Avertissement"
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:105
+msgid "We'll show the first 10 columns and 20 rows of this table in your Pulse. If you email this, we'll add a file attachment with all columns and up to 2,000 rows."
+msgstr "Nous afficherons les 10 premières colonnes et 20 premières lignes de cette table dans votre Pulse. Si vous envoyez un courriel, nous ajouterons un fichier contenant toutes les colonnes et jusqu'à 2 000 lignes."
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:112
+msgid "Raw data questions can only be included as email attachments"
+msgstr "Les questions de données brutes ne peuvent être incluses que sous forme de pièces jointes."
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:119
+msgid "Looks like this pulse is getting big"
+msgstr "Il semble que ce pulse devienne trop grand"
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:120
+msgid "We recommend keeping pulses small and focused to help keep them digestible and useful to the whole team."
+msgstr "Nous recommandons de garder les pulses petits et ciblés pour aider à les conserver compréhensibles et utiles pour l'ensemble de l'équipe"
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:160
+msgid "Pick your data"
+msgstr "Sélectionner vos données"
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:162
+msgid "Choose questions you'd like to send in this pulse"
+msgstr "Choisissez les questions que vous voudriez envoyer dans ce pulse"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:29
+msgid "Emails"
+msgstr "Courriels"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:30
+msgid "Slack messages"
+msgstr "Messages Slack"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:218
+msgid "Sent"
+msgstr "Envoyé"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:219
+msgid "{0} will be sent at"
+msgstr "{0} sera envoyé à"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:221
+msgid "Messages"
+msgstr "Messages"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:232
+msgid "Send email now"
+msgstr "Envoyer par courriel maintenant"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:233
+msgid "Send to {0} now"
+msgstr "Envoyer à {0} maintenant"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:235
+msgid "Sending…"
+msgstr "Envoi en cours..."
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:236
+msgid "Sending failed"
+msgstr "L'envoi a échoué"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:239
+msgid "Didn’t send because the pulse has no results."
+msgstr "L'envoi n'a pas été fait car le pulse n'a aucun résultat."
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:240
+msgid "Pulse sent"
+msgstr "Pulse envoyé"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:279
+msgid "{0} needs to be set up by an administrator."
+msgstr "{0} a besoin d'être configuré(e) par un administrateur."
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:294
+msgid "Slack"
+msgstr "Slack"
+
+#: frontend/src/metabase/pulse/components/PulseEditCollection.jsx:12
+msgid "Which collection should this pulse live in?"
+msgstr "Dans quelle collection ce pulse devrait-il être ?"
+
+#: frontend/src/metabase/pulse/components/PulseEditName.jsx:35
+msgid "Name your pulse"
+msgstr "Nommer votre pulse"
+
+#: frontend/src/metabase/pulse/components/PulseEditName.jsx:37
+msgid "Give your pulse a name to help others understand what it's about"
+msgstr "Donnez un nom à votre pulse pour aider les autres utilisateurs à comprendre son intérêt"
+
+#: frontend/src/metabase/pulse/components/PulseEditName.jsx:49
+msgid "Important metrics"
+msgstr "Métriques importantes"
+
+#: frontend/src/metabase/pulse/components/PulseEditSkip.jsx:22
+msgid "Skip if no results"
+msgstr "Ignorer si aucun résultat"
+
+#: frontend/src/metabase/pulse/components/PulseEditSkip.jsx:24
+msgid "Skip a scheduled Pulse if none of its questions have any results"
+msgstr "Ignorer un Pulse planifié si aucune de ses questions n'a de résultat"
+
+#: frontend/src/metabase/pulse/components/RecipientPicker.jsx:65
+msgid "Enter email addresses you'd like this data to go to"
+msgstr "Saisissez les adresses électroniques des destinataires des données"
+
+#: frontend/src/metabase/pulse/components/WhatsAPulse.jsx:16
+msgid "Help everyone on your team stay in sync with your data."
+msgstr "Aidez chacun de vos utilisateurs à rester en phase avec vos données."
+
+#: frontend/src/metabase/pulse/components/WhatsAPulse.jsx:30
+msgid "Pulses let you send data from Metabase to email or Slack on the schedule of your choice."
+msgstr "Les Pulses vous permettent d'envoyer vos données de façon planifiée, par e-mail ou via slack."
+
+#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:100
+msgid "After {0}"
+msgstr "Après {0}"
+
+#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:102
+msgid "Before {0}"
+msgstr "Avant {0}"
+
+#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:104
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:299
+msgid "Is Empty"
+msgstr "Est vide"
+
+#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:106
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:305
+msgid "Not Empty"
+msgstr "N'est pas vide"
+
+#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:109
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:216
+msgid "All Time"
+msgstr "Tout le temps"
+
+#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:154
+msgid "Apply"
+msgstr "Appliquer les modifications"
+
+#: frontend/src/metabase/qb/components/actions/CommonMetricsAction.jsx:21
+msgid "View {0}"
+msgstr "Vue {0}"
+
+#: frontend/src/metabase/qb/components/actions/CompareWithTable.jsx:29
+msgid "Compare this with all rows in the table"
+msgstr "Comparer cela avec toutes les lignes de la table"
+
+#: frontend/src/metabase/qb/components/actions/CompoundQueryAction.jsx:14
+msgid "Analyze the results of this Query"
+msgstr "Analyser les résultats de cette requête"
+
+#: frontend/src/metabase/qb/components/actions/CountByTimeAction.jsx:29
+msgid "Count of rows by time"
+msgstr "Nombre de lignes au fil du temps"
+
+#: frontend/src/metabase/qb/components/actions/PivotByAction.jsx:55
+msgid "Break out by {0}"
+msgstr "Eclater par {0}"
+
+#: frontend/src/metabase/qb/components/actions/SummarizeBySegmentMetricAction.jsx:34
+msgid "Summarize this segment"
+msgstr "Résumez ce segment"
+
+#: frontend/src/metabase/qb/components/actions/UnderlyingDataAction.jsx:14
+msgid "View this as a table"
+msgstr "Voir ceci comme un tableau"
+
+#: frontend/src/metabase/qb/components/actions/UnderlyingRecordsAction.jsx:22
+msgid "View the underlying {0} records"
+msgstr "Afficher les enregistrements sous-jacents {0}"
+
+#: frontend/src/metabase/qb/components/actions/XRayCard.jsx:15
+msgid "X-Ray this question"
+msgstr "Radiographier cette question"
+
+#: frontend/src/metabase/qb/components/drill/AutomaticDashboardDrill.jsx:32
+msgid "X-ray {0} {1}"
+msgstr "Radiographier {0} {1}"
+
+#: frontend/src/metabase/qb/components/drill/AutomaticDashboardDrill.jsx:32
+#: frontend/src/metabase/qb/components/drill/CompareToRestDrill.js:32
+msgid "these"
+msgstr "ce/cette/ces"
+
+#: frontend/src/metabase/qb/components/drill/AutomaticDashboardDrill.jsx:32
+#: frontend/src/metabase/qb/components/drill/CompareToRestDrill.js:32
+msgid "this"
+msgstr "ce/cette"
+
+#: frontend/src/metabase/qb/components/drill/CompareToRestDrill.js:32
+msgid "Compare {0} {1} to the rest"
+msgstr "Comparer {0} {1} au reste"
+
+#: frontend/src/metabase/qb/components/drill/DistributionDrill.jsx:35
+msgid "Distribution"
+msgstr "Distribution"
+
+#: frontend/src/metabase/qb/components/drill/ObjectDetailDrill.jsx:38
+msgid "View details"
+msgstr "Voir les détails"
+
+#: frontend/src/metabase/qb/components/drill/QuickFilterDrill.jsx:54
+msgid "View this {0}'s {1}"
+msgstr "Voir les {1} de ce/cette {0}"
+
+#: frontend/src/metabase/qb/components/drill/SortAction.jsx:45
+msgid "Ascending"
+msgstr "Croissant"
+
+#: frontend/src/metabase/qb/components/drill/SortAction.jsx:57
+msgid "Descending"
+msgstr "Décroissant"
+
+#: frontend/src/metabase/qb/components/drill/SummarizeColumnByTimeDrill.js:47
+msgid "over time"
+msgstr "au cours du temps"
+
+#: frontend/src/metabase/qb/components/drill/SummarizeColumnDrill.js:21
+msgid "Avg"
+msgstr "Moyenne"
+
+#: frontend/src/metabase/qb/components/drill/SummarizeColumnDrill.js:33
+msgid "Distincts"
+msgstr "Valeurs distinctes"
+
+#: frontend/src/metabase/qb/components/drill/UnderlyingRecordsDrill.jsx:32
+msgid "View this {0}"
+msgid_plural "View these {0}"
+msgstr[0] "Visualiser ce/cette {0}"
+msgstr[1] "Visualiser ces {0}"
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:220
+#: frontend/src/metabase/qb/components/drill/ZoomDrill.jsx:26
+msgid "Zoom in"
+msgstr "Agrandir"
+
+#: frontend/src/metabase/query_builder/components/AggregationPopover.jsx:19
+msgid "Custom Expression"
+msgstr "Expression personnalisée"
+
+#: frontend/src/metabase/query_builder/components/AggregationPopover.jsx:20
+msgid "Common Metrics"
+msgstr "Métriques communes"
+
+#: frontend/src/metabase/query_builder/components/AggregationPopover.jsx:182
+msgid "Metabasics"
+msgstr "Metabasiques"
+
+#: frontend/src/metabase/query_builder/components/AggregationPopover.jsx:290
+msgid "Name (optional)"
+msgstr "Nom (optionnel)"
+
+#: frontend/src/metabase/query_builder/components/AggregationWidget.jsx:153
+msgid "Choose an aggregation"
+msgstr "Choisir un agrégat"
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:115
+msgid "Set up your own alert"
+msgstr "Configurez votre propre alerte"
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:155
+msgid "Unsubscribing..."
+msgstr "En cours de désinscription..."
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:160
+msgid "Failed to unsubscribe"
+msgstr "Echec de la désinscription"
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:216
+msgid "Unsubscribe"
+msgstr "Se désinscrire"
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:247
+msgid "No channel"
+msgstr "Aucun canal"
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:274
+msgid "Okay, you're unsubscribed"
+msgstr "Voilà, vous êtes désinscrit"
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:346
+msgid "You're receiving {0}'s alerts"
+msgstr "Vous recevez les alertes de {0}"
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:347
+msgid "{0} set up an alert"
+msgstr "{0} a configuré une alerte"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:161
+msgid "alerts"
+msgstr "alertes"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:184
+msgid "Let's set up your alert"
+msgstr "Configurons votre alerte"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:215
+msgid "The wide world of alerts"
+msgstr "Le vaste monde des alertes"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:216
+msgid "There are a few different kinds of alerts you can get"
+msgstr "Il y a une poignée de types d'alerte que vous pouvez recevoir"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:229
+msgid "When a raw data question {0}"
+msgstr "Quand une question de données brutes {0}"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:230
+msgid "returns any results"
+msgstr "ramène un résultat"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:240
+msgid "When a line or bar {0}"
+msgstr "Quand une ligne de graphique ou une barre d'histogramme {0}"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:241
+msgid "crosses a goal line"
+msgstr "traverse une ligne d'objectif"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:251
+msgid "When a progress bar {0}"
+msgstr "Quand une barre de progression {0}"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:252
+msgid "reaches its goal"
+msgstr "objectif atteint"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:260
+msgid "Set up an alert"
+msgstr "Configurer une alerte"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:329
+msgid "Edit your alert"
+msgstr "Modifier votre alerte"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:329
+msgid "Edit alert"
+msgstr "Modifier l'alerte"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:372
+msgid "This alert will no longer be emailed to {0}."
+msgstr "Cette alerte ne sera plus envoyée par e-mail à {0}."
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:380
+msgid "Slack channel {0} will no longer get this alert."
+msgstr "Le channel Slack {0} ne recevra plus cette alerte."
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:384
+msgid "Channel {0} will no longer receive this alert."
+msgstr "Le canal {0} ne recevra plus cette alerte."
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:401
+msgid "Delete this alert"
+msgstr "Supprimer cette alerte"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:403
+msgid "Stop delivery and delete this alert. There's no undo, so be careful."
+msgstr "Arrêter la génération et supprimer cette alerte. Il n'y a aucun retour arrière, soyez donc prudent."
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:411
+msgid "Delete this alert?"
+msgstr "Supprimer cette alerte ?"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:495
+msgid "Alert me when the line…"
+msgstr "Prévenez moi quand la ligne ..."
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:496
+msgid "Alert me when the progress bar…"
+msgstr "Me prévenir quand la barre de progression"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:499
+msgid "Goes above the goal line"
+msgstr "Passe au dessus de la ligne d'objectif"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:499
+msgid "Reaches the goal"
+msgstr "Objectif atteint"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:502
+msgid "Goes below the goal line"
+msgstr "Passe en dessous de la ligne d'objectif"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:502
+msgid "Goes below the goal"
+msgstr "Passe sous l'objectif"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:510
+msgid "The first time it crosses, or every time?"
+msgstr "Au premier croisement, ou à chaque fois ?"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:511
+msgid "The first time it reaches the goal, or every time?"
+msgstr "La première fois que l'objectif est atteint, ou à chaque fois ?"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:513
+msgid "The first time"
+msgstr "La première fois"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:514
+msgid "Every time"
+msgstr "Chaque fois"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:617
+msgid "Where do you want to send these alerts?"
+msgstr "Où voulez-vous envoyer ces alertes ?"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:628
+msgid "Email alerts to:"
+msgstr "Envoyer par courriel à :"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:670
+msgid "{0} Goal-based alerts aren't yet supported for charts with more than one line, so this alert will be sent whenever the chart has {1}."
+msgstr "{0} Les alertes basées sur l'objectif ne prennent pas encore en charge les graphiques multi-lignes, cette alerte sera donc envoyée chaque fois que le graphique a {1}."
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:673
+msgid "results"
+msgstr "résultats"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:677
+msgid "{0} This kind of alert is most useful when your saved question doesn’t {1} return any results, but you want to know when it does."
+msgstr "{0} ce type d'alerte est surtout utile quand votre question sauvegardée ne retourne {1} pas de résutat, mais que vous voulez savoir quand elle en retourne un."
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:678
+msgid "Tip"
+msgstr "Astuce"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:680
+msgid "usually"
+msgstr "habituellement"
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:57
+msgid "Pick a segment or table"
+msgstr "Choisir un segment ou une table"
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:73
+msgid "Select a database"
+msgstr "Sélectionner une base de données"
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:88
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:87
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:187
+#: frontend/src/metabase/reference/components/MetricImportantFieldsDetail.jsx:35
+msgid "Select..."
+msgstr "Sélectionner..."
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:128
+msgid "Select a table"
+msgstr "Sélectionner une table"
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:785
+msgid "No tables found in this database."
+msgstr "Aucune table trouvé dans cette base de données"
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:822
+msgid "Is a question missing?"
+msgstr "Manque-t'il une question ?"
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:826
+msgid "Learn more about nested queries"
+msgstr "En savoir plus sur les questions imbriquées"
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:860
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:30
+msgid "Fields"
+msgstr "Champs"
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:938
+msgid "No segments were found."
+msgstr "Aucun segment trouvé."
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:961
+msgid "Find a segment"
+msgstr "Trouver un segment"
+
+#: frontend/src/metabase/query_builder/components/ExpandableString.jsx:46
+msgid "View less"
+msgstr "Voir l'essentiel"
+
+#: frontend/src/metabase/query_builder/components/ExpandableString.jsx:56
+msgid "View more"
+msgstr "Voir en détails"
+
+#: frontend/src/metabase/query_builder/components/ExtendedOptions.jsx:111
+msgid "Pick a field to sort by"
+msgstr "Choisir un champ selon lequel trier"
+
+#: frontend/src/metabase/query_builder/components/ExtendedOptions.jsx:124
+msgid "Sort"
+msgstr "Trier"
+
+#: frontend/src/metabase/query_builder/components/ExtendedOptions.jsx:193
+msgid "Row limit"
+msgstr "Nombre de lignes maximum"
+
+#: frontend/src/metabase/query_builder/components/FieldName.jsx:76
+msgid "Unknown Field"
+msgstr "Champ inconnu"
+
+#: frontend/src/metabase/query_builder/components/FieldName.jsx:79
+msgid "field"
+msgstr "champ"
+
+#: frontend/src/metabase/query_builder/components/Filter.jsx:113
+msgid "Matches"
+msgstr "Correspondances"
+
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:152
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:160
+msgid "Add filters to narrow your answer"
+msgstr "Ajouter des filtres pour préciser votre réponse"
+
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:284
+msgid "Add a grouping"
+msgstr "Ajouter une agrégation"
+
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:322
+#: frontend/src/metabase/visualizations/components/LineAreaBarChart.jsx:102
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:55
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:92
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:131
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:58
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:66
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:73
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:54
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:61
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:66
+#: frontend/src/metabase/visualizations/visualizations/Table.jsx:60
+#: frontend/src/metabase/visualizations/visualizations/Table.jsx:72
+msgid "Data"
+msgstr "Données"
+
+#. Si l'on tient à l'enchaînement Données/Fitré par/Vue/Groupé par, le non accord des participes passés jure un peu... Je propose Données/Fitre/Vue/Aggrégation
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:352
+msgid "Filtered by"
+msgstr "Filtre"
+
+#. Bonjour Fabrice, je propose de conserver "Vue" comme tu l'avais indiqué la première fois dans le générateur de requête on aurait alors : "Données" / "Filtrées par" / "Vue" / "Groupées par".
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:369
+msgid "View"
+msgstr "Vue"
+
+#. Si l'on tient à l'enchaînement Données/Fitré par/Vue/Groupé par, le non accord des participes passés jure un peu... Je propose Données/Fitre/Vue/Aggrégat
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:386
+msgid "Grouped By"
+msgstr "Agrégat"
+
+#: frontend/src/metabase/query_builder/components/LimitWidget.jsx:27
+msgid "None"
+msgstr "Aucun"
+
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:338
+msgid "This question is written in {0}."
+msgstr "Cette question est écrite en {0}."
+
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:346
+msgid "Hide Editor"
+msgstr "Masquer l'éditeur"
+
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:347
+msgid "Hide Query"
+msgstr "Masquer la requête"
+
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:352
+msgid "Open Editor"
+msgstr "Ouvrir l'éditeur"
+
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:353
+msgid "Show Query"
+msgstr "Montrer la requête"
+
+#: frontend/src/metabase/query_builder/components/QueryDefinitionTooltip.jsx:25
+msgid "This metric has been retired.  It's no longer available for use."
+msgstr "La métrique a été retirée. Elle n'est plus utilisable."
+
+#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:34
+#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:46
+msgid "Download full results"
+msgstr "Télécharger le résultat complet"
+
+#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:35
+msgid "Download this data"
+msgstr "Télécharger ces données"
+
+#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:46
+msgid "Warning"
+msgstr "Avertissement"
+
+#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:52
+msgid "Your answer has a large number of rows so it could take a while to download."
+msgstr "Votre réponse a un grand nombre de lignes et pourrait prendre un certain temps à télécharger."
+
+#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:53
+msgid "The maximum download size is 1 million rows."
+msgstr "La taille maximum de téléchargement est d'un million de lignes."
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:232
+msgid "Edit question"
+msgstr "Modifier la question"
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:249
+msgid "SAVE CHANGES"
+msgstr "SAUVEGARDER LES MODIFICATIONS"
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:263
+msgid "CANCEL"
+msgstr "SUPPRIMER"
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:276
+msgid "Move question"
+msgstr "Déplacer la question"
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:283
+msgid "Which collection should this be in?"
+msgstr "Dans quelle collection cela devrait-il aller ?"
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:313
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:110
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorSidebar.jsx:83
+msgid "Variables"
+msgstr "Variables"
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:432
+msgid "Learn about your data"
+msgstr "En savoir plus sur vos données"
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:460
+msgid "Alerts are on"
+msgstr "Les alertes sont actives"
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:522
+msgid "started from"
+msgstr "démarré depuis"
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:48
+msgid "SQL"
+msgstr "SQL"
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:48
+msgid "native query"
+msgstr "question native"
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:52
+msgid "Not Supported"
+msgstr "Non pris(e) en charge"
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:58
+msgid "View the {0}"
+msgstr "Voir le {0}"
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:59
+msgid "Switch to {0}"
+msgstr "Passer à/au {0}"
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:62
+msgid "Switch to Builder"
+msgstr "Passer au générateur de questions"
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:87
+msgid "{0} for this question"
+msgstr "{0} pour cette question"
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:111
+msgid "Convert this question to {0}"
+msgstr "Convertir cette question en {0}"
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:122
+msgid "This question will take approximately {0} to refresh"
+msgstr "Cette question prendra approximativement {0} à se rafraîchir"
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:131
+msgid "Updated {0}"
+msgstr "{0} Mis à jour"
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:141
+msgid "row"
+msgid_plural "rows"
+msgstr[0] "ligne"
+msgstr[1] "lignes"
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:148
+msgid "Showing first {0} {1}"
+msgstr "Affiche les {0} premières {1}"
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:151
+msgid "Showing {0} {1}"
+msgstr "Affiche {0} {1}"
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:281
+msgid "Doing science"
+msgstr "Calcul en cours"
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:294
+msgid "If you give me some data I can show you something cool. Run a Query!"
+msgstr "Si vous me donnez du grain à moudre, je peux vous montrer quelque chose de sympa. Lancez une requête !"
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:299
+msgid "How do I use this thing?"
+msgstr "Comment utiliser ceci ?"
+
+#: frontend/src/metabase/query_builder/components/RunButton.jsx:28
+msgid "Get Answer"
+msgstr "Obtenir la réponse"
+
+#: frontend/src/metabase/query_builder/components/SavedQuestionIntroModal.jsx:12
+msgid "It's okay to play around with saved questions"
+msgstr "Il n'y aucun souci à s'amuser avec les questions sauvegardées"
+
+#: frontend/src/metabase/query_builder/components/SavedQuestionIntroModal.jsx:14
+msgid "You won't make any permanent changes to a saved question unless you click the edit icon in the top-right."
+msgstr "Vous ne ferez aucune modification permanente à une question sauvegardée tant que vous ne cliquez pas sur le bouton de modification en haut à droite."
+
+#: frontend/src/metabase/query_builder/components/SearchBar.jsx:28
+msgid "Search for"
+msgstr "Rechercher"
+
+#: frontend/src/metabase/query_builder/components/SelectionModule.jsx:158
+msgid "Advanced..."
+msgstr "Avancé ..."
+
+#: frontend/src/metabase/query_builder/components/SelectionModule.jsx:167
+msgid "Sorry. Something went wrong."
+msgstr "Désolé, quelque chose s'est mal passé."
+
+#: frontend/src/metabase/query_builder/components/TimeGroupingPopover.jsx:40
+msgid "Group time by"
+msgstr "Aggréger le temps par"
+
+#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:46
+msgid "Your question took too long"
+msgstr "Votre question a pris trop longtemps"
+
+#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:47
+msgid "We didn't get an answer back from your database in time, so we had to stop. You can try again in a minute, or if the problem persists, you can email an admin to let them know."
+msgstr "Nous n'avons pas eu de réponse de votre base de données à temps, nous avons donc arrêté le traitement. Vous pouvez réessayer dans une minute, ou si le problème persiste, envoyez un courriel à un administrateur."
+
+#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:55
+msgid "We're experiencing server issues"
+msgstr "Nous rencontrons des problèmes sur le serveur"
+
+#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:56
+msgid "Try refreshing the page after waiting a minute or two. If the problem persists we'd recommend you contact an admin."
+msgstr "Essayez de rafraîchir la page après une ou deux minutes d'attente. Si le problème persiste, nous vous recommandons de contacter une administrateur."
+
+#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:88
+msgid "There was a problem with your question"
+msgstr "Il y a eu un problème avec votre question"
+
+#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:89
+msgid "Most of the time this is caused by an invalid selection or bad input value. Double check your inputs and retry your query."
+msgstr "Cela est causé la plupart du temps par une sélection invalide ou une mauvaise valeur saisie. Vérifiez à nouveau vos entrées et réessayez."
+
+#: frontend/src/metabase/query_builder/components/VisualizationResult.jsx:60
+msgid "This may be the answer you’re looking for. If not, try removing or changing your filters to make them less specific."
+msgstr "C'est peut-être la réponse que vous recherchez. Sinon, essayez d'enlever ou changer vos filtres pour les rendre moins restrictifs."
+
+#: frontend/src/metabase/query_builder/components/VisualizationResult.jsx:66
+msgid "You can also {0} when there are some results."
+msgstr "Vous pouvez aussi {0} quand il y a des résultats."
+
+#: frontend/src/metabase/query_builder/components/VisualizationResult.jsx:68
+msgid "get an alert"
+msgstr "Obtenir une alerte"
+
+#: frontend/src/metabase/query_builder/components/VisualizationResult.jsx:77
+msgid "Back to last run"
+msgstr "Retourner à la dernière exécution"
+
+#: frontend/src/metabase/query_builder/components/VisualizationSettings.jsx:53
+msgid "Visualization"
+msgstr "Visualisation"
+
+#: frontend/src/metabase/query_builder/components/dataref/DetailPane.jsx:17
+#: frontend/src/metabase/query_builder/components/dataref/TablePane.jsx:151
+msgid "No description set."
+msgstr "Aucune description."
+
+#: frontend/src/metabase/query_builder/components/dataref/DetailPane.jsx:21
+msgid "Use for current question"
+msgstr "Utiliser pour la question actuelle"
+
+#: frontend/src/metabase/query_builder/components/dataref/DetailPane.jsx:33
+#: frontend/src/metabase/reference/components/UsefulQuestions.jsx:16
+msgid "Potentially useful questions"
+msgstr "Questions potentiellement utiles"
+
+#: frontend/src/metabase/query_builder/components/dataref/FieldPane.jsx:156
+msgid "Group by {0}"
+msgstr "Aggréger par {0}"
+
+#: frontend/src/metabase/query_builder/components/dataref/FieldPane.jsx:165
+msgid "Sum of all values of {0}"
+msgstr "Somme de toutes les valeurs de {0}"
+
+#: frontend/src/metabase/query_builder/components/dataref/FieldPane.jsx:173
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:63
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:51
+msgid "All distinct values of {0}"
+msgstr "Toutes les valeurs distinctes de {0}"
+
+#: frontend/src/metabase/query_builder/components/dataref/FieldPane.jsx:177
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:39
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:51
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:39
+msgid "Number of {0} grouped by {1}"
+msgstr "Nombre de {0} aggrégés par {1}"
+
+#: frontend/src/metabase/query_builder/components/dataref/MainPane.jsx:11
+#: frontend/src/metabase/reference/databases/DatabaseSidebar.jsx:20
+#: frontend/src/metabase/reference/databases/FieldSidebar.jsx:27
+#: frontend/src/metabase/reference/databases/TableSidebar.jsx:23
+#: frontend/src/metabase/reference/guide/BaseSidebar.jsx:17
+#: frontend/src/metabase/reference/guide/BaseSidebar.jsx:19
+#: frontend/src/metabase/reference/metrics/MetricSidebar.jsx:20
+#: frontend/src/metabase/reference/segments/SegmentFieldSidebar.jsx:24
+#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:20
+msgid "Data Reference"
+msgstr "Référentiel des données"
+
+#: frontend/src/metabase/query_builder/components/dataref/MainPane.jsx:13
+msgid "Learn more about your data structure to ask more useful questions"
+msgstr "En savoir plus sur la structure des données pour poser des questions plus utiles"
+
+#: frontend/src/metabase/query_builder/components/dataref/MetricPane.jsx:58
+#: frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx:84
+msgid "Could not find the table metadata prior to creating a new question"
+msgstr "Impossible de trouver les métadonnées de la table avant de créer une nouvelle question"
+
+#: frontend/src/metabase/query_builder/components/dataref/MetricPane.jsx:80
+msgid "See {0}"
+msgstr "Voir {0}"
+
+#: frontend/src/metabase/query_builder/components/dataref/MetricPane.jsx:94
+msgid "Metric Definition"
+msgstr "Règles de la métrique"
+
+#: frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx:118
+msgid "Filter by {0}"
+msgstr "Filtrer par {0}"
+
+#: frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx:127
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:36
+msgid "Number of {0}"
+msgstr "Nombre de {0}"
+
+#: frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx:134
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:46
+msgid "See all {0}"
+msgstr "Voir tous les {0}"
+
+#: frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx:148
+msgid "Segment Definition"
+msgstr "Définition du segment"
+
+#: frontend/src/metabase/query_builder/components/dataref/TablePane.jsx:50
+msgid "An error occurred loading the table"
+msgstr "Une erreur est survenue au chargement de la table"
+
+#: frontend/src/metabase/query_builder/components/dataref/TablePane.jsx:74
+msgid "See the raw data for {0}"
+msgstr "Voir les données brutes de {0}"
+
+#: frontend/src/metabase/query_builder/components/dataref/TablePane.jsx:205
+msgid "More"
+msgstr "Plus"
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:200
+msgid "Invalid expression"
+msgstr "Expression invalide"
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:275
+msgid "unknown error"
+msgstr "erreur inconnue"
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:46
+msgid "Field formula"
+msgstr "Formule du champ"
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:57
+msgid "Think of this as being kind of like writing a formula in a spreadsheet program: you can use numbers, fields in this table, mathematical symbols like +, and some functions. So you could type something like Subtotal - Cost."
+msgstr "Imaginez cela comme étant une sorte de formule de tableur : vous pouvez utiliser des numbres, champs dans la table, opérations mathématiques comme +, et quelques fonctions. Vous pourriez donc écrire quelque chose comme Soustotal - Cout."
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:62
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:126
+msgid "Learn more"
+msgstr "En savoir plus"
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:66
+msgid "Give it a name"
+msgstr "Donner un nom"
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:72
+msgid "Something nice and descriptive"
+msgstr "Quelque chose de sympa et descriptif"
+
+#: frontend/src/metabase/query_builder/components/expressions/Expressions.jsx:60
+msgid "Add a custom field"
+msgstr "Ajouter un champ personnalisé"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:17
+msgid "Include {0}"
+msgstr "Inclure {0}"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:19
+msgid "Case sensitive"
+msgstr "Sensible à la casse"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:23
+msgid "today"
+msgstr "aujourd'hui"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:24
+msgid "this week"
+msgstr "cette semaine"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:25
+msgid "this month"
+msgstr "ce mois"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:26
+msgid "this year"
+msgstr "cette année"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:27
+msgid "this minute"
+msgstr "cette minute"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:28
+msgid "this hour"
+msgstr "cette heure"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx:285
+msgid "not implemented {0}"
+msgstr "{0} non implémenté {0"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx:286
+msgid "true"
+msgstr "vrai"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx:286
+msgid "false"
+msgstr "faux"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx:390
+msgid "Add filter"
+msgstr "Ajouter un filtre"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterWidgetList.jsx:64
+msgid "Item"
+msgstr "Elément"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:224
+msgid "Previous"
+msgstr "Précédent"
+
+#. C'est le 'Current' du widget de sélection des dates
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:255
+msgid "Current"
+msgstr "Période en cours"
+
+#. C'est le 'On' du widget de filtrage des dates
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:282
+msgid "On"
+msgstr "Le"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/NumberPicker.jsx:47
+msgid "Enter desired number"
+msgstr "Saisir le nombre désiré"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/SelectPicker.jsx:83
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:100
+msgid "Empty"
+msgstr "Vide"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/SelectPicker.jsx:116
+msgid "Find a value"
+msgstr "Trouver une valeur"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/SpecificDatePicker.jsx:113
+msgid "Hide calendar"
+msgstr "Masquer le calendrier"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/SpecificDatePicker.jsx:113
+msgid "Show calendar"
+msgstr "Afficher le calendrier"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/TextPicker.jsx:97
+msgid "You can enter multiple values separated by commas"
+msgstr "Vous pouvez saisir plusieurs valeurs séparées par des virgules"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/TextPicker.jsx:38
+msgid "Enter desired text"
+msgstr "Saisir le texte désiré"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:83
+msgid "Try it"
+msgstr "L'essayer"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:105
+msgid "What's this for?"
+msgstr "A quoi ça sert ?"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:107
+msgid "Variables in native queries let you dynamically replace values in your queries using filter widgets or through the URL."
+msgstr "Les variables dans les requêtes natives vous permettent de remplacer dynamiquement les valeurs dans vos requêtes à l'aide de widgets de filtrage ou depuis l'URL."
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:112
+msgid "{0} creates a variable in this SQL template called \"variable_name\". Variables can be given types in the side panel, which changes their behavior. All variable types other than \"Field Filter\" will automatically cause a filter widget to be placed on this question; with Field Filters, this is optional. When this filter widget is filled in, that value replaces the variable in the SQL template."
+msgstr "{0} crée une variable dans ce patron SQL nommée \"variable_name\". Les variables peuvent être typées dans le panneau latéral, ce qui change leur comportement. Tout type de variable différent de \"Filtre de champ\" provoquera automatiquement le placement d'un élément visuel filtre sur cette question; c'est optionnel pour les filtres de champ. Quand ce filtre est renseigné, sa valeur remplace la variable dans le patron SQL."
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:121
+msgid "Field Filters"
+msgstr "Filtres de champ"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:123
+msgid "Giving a variable the \"Field Filter\" type allows you to link SQL cards to dashboard filter widgets or use more types of filter widgets on your SQL question. A Field Filter variable inserts SQL similar to that generated by the GUI query builder when adding filters on existing columns."
+msgstr "Donner à une variable le type \"Filtre de champ\" vous permet :\n"
+"- dans un tableau de bord, de lier une carte contenant la question SQL à un filtre\n"
+"- dans la question, d'élargir le choix des types de filtre. \n"
+"Une variable de type filtre de champ insère du SQL similaire à celui du générateur de requête lors de l'ajout d'un filtre sur une colonne existante."
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:126
+msgid "When adding a Field Filter variable, you'll need to map it to a specific field. You can then choose to display a filter widget on your question, but even if you don't, you can now map your Field Filter variable to a dashboard filter when adding this question to a dashboard. Field Filters should be used inside of a \"WHERE\" clause."
+msgstr "Une variable de type \"Filtre de champ\" doit être liée à une colonne de table spécifique. Vous pouvez alors choisir d'afficher un filtre dans votre question, mais même si vous ne faites pas, vous pouvez lier votre variable \"Filtre de champ\" à un filtre de tableau de bord contenant une carte avec cette question. les Filtres de champ devraient être utilisés dans le bloc WHERE du patron SQL."
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:130
+msgid "Optional Clauses"
+msgstr "Clauses optionnelles"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:132
+msgid "brackets around a {0} create an optional clause in the template. If \"variable\" is set, then the entire clause is placed into the template. If not, then the entire clause is ignored."
+msgstr "des crochets carrés [] autour d'un(e) {0} rendent optionnelle une clause dans le patron SQL. Si la variable est valuée, alors la clause est insérée dans la requête. Sinon, elle est ignorée."
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:142
+msgid "To use multiple optional clauses you can include at least one non-optional WHERE clause followed by optional clauses starting with \"AND\"."
+msgstr "Pour utiliser plusieurs clauses optionnelles, vous pouvez inclure au moins une clause dans le bloc WHERE suivie des clauses optionnelles préfixées par AND"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:154
+msgid "Read the full documentation"
+msgstr "Lire la documentation complète"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:124
+msgid "Filter label"
+msgstr "Libellé du filtre"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:136
+msgid "Variable type"
+msgstr "Type de variable"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:145
+msgid "Text"
+msgstr "Texte"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:147
+msgid "Date"
+msgstr "Date"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:148
+msgid "Field Filter"
+msgstr "Filtre de champ"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:154
+msgid "Field to map to"
+msgstr "Champ lié à"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:176
+msgid "Filter widget type"
+msgstr "Type de filtre"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:199
+msgid "Required?"
+msgstr "Obligatoire ?"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:210
+msgid "Default filter widget value"
+msgstr "Valeur du filtre par défaut"
+
+#: frontend/src/metabase/query_builder/containers/ArchiveQuestionModal.jsx:46
+msgid "Archive this question?"
+msgstr "Archiver cette question?"
+
+#: frontend/src/metabase/query_builder/containers/ArchiveQuestionModal.jsx:57
+msgid "This question will be removed from any dashboards or pulses using it."
+msgstr "Cette question sera retirée de tous les tableaux de bords ou pulses qui l'utilisent."
+
+#: frontend/src/metabase/query_builder/containers/QueryBuilder.jsx:136
+msgid "Question"
+msgstr "Question"
+
+#: frontend/src/metabase/questions/containers/AddToDashboard.jsx:11
+msgid "Pick a question to add"
+msgstr "Choisissez une question à ajouter"
+
+#: frontend/src/metabase/reference/components/EditHeader.jsx:19
+msgid "You are editing this page"
+msgstr "Vous modifiez cette page"
+
+#: frontend/src/metabase/reference/components/EditableReferenceHeader.jsx:101
+#: frontend/src/metabase/reference/components/ReferenceHeader.jsx:63
+msgid "See this {0}"
+msgstr "Voir ce {0}"
+
+#: frontend/src/metabase/reference/components/EditableReferenceHeader.jsx:120
+msgid "A subset of"
+msgstr "Un sous ensemble de"
+
+#: frontend/src/metabase/reference/components/Field.jsx:47
+#: frontend/src/metabase/reference/components/Field.jsx:86
+#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:32
+#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:68
+msgid "Select a field type"
+msgstr "Choisir un type de champ"
+
+#: frontend/src/metabase/reference/components/Field.jsx:56
+#: frontend/src/metabase/reference/components/Field.jsx:71
+#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:41
+#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:57
+msgid "No field type"
+msgstr "Pas de type d echamp"
+
+#: frontend/src/metabase/reference/components/FieldToGroupBy.jsx:22
+msgid "by"
+msgstr "par"
+
+#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:25
+#: frontend/src/metabase/reference/databases/FieldList.jsx:152
+#: frontend/src/metabase/reference/segments/SegmentFieldList.jsx:153
+msgid "Field type"
+msgstr "Type de champ"
+
+#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:72
+msgid "Select a Foreign Key"
+msgstr "Choisir une clé étrangère"
+
+#: frontend/src/metabase/reference/components/Formula.jsx:53
+msgid "View the {0} formula"
+msgstr "Afficher la formule {0}"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:80
+msgid "Why this {0} is important"
+msgstr "Pourquoi cet {0} est important"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:81
+msgid "Why this {0} is interesting"
+msgstr "Pourquoi ce/cet/cette {0} est intéressant(e)"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:87
+msgid "Nothing important yet"
+msgstr "Rien d'important pour l'instant"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:88
+#: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:168
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:233
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:211
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:215
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:219
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:229
+msgid "Nothing interesting yet"
+msgstr "Rien d'intéressant pour l'instant"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:93
+msgid "Things to be aware of about this {0}"
+msgstr "Choses dont il faut être conscient à propos de ce/cette {0}"
+
+#. liaison
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:97
+#: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:178
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:243
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:221
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:225
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:229
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:239
+msgid "Nothing to be aware of yet"
+msgstr "Rien à savoir pour l'instant"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:103
+msgid "Explore this metric"
+msgstr "Explorer cette métrique"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:105
+msgid "View this metric"
+msgstr "Voir cette métrique"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:112
+msgid "By {0}"
+msgstr "Par {0}"
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:146
+msgid "Remove item"
+msgstr "Retirer l'élément"
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:155
+msgid "Why is this dashboard the most important?"
+msgstr "Pourquoi ce tableau de bord est-il le plus important ?"
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:156
+msgid "What is useful or interesting about this {0}?"
+msgstr "Quoi d'utile ou d'intéressant à propos de ce/cette {0} ?"
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:160
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:174
+msgid "Write something helpful here"
+msgstr "Ecrivez quelque chose d'utile ici"
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:169
+msgid "Is there anything users of this dashboard should be aware of?"
+msgstr "Y a t'il quelque chose dont les utilisateurs de ce tableau de bord devraient être conscients ?"
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:170
+msgid "Anything users should be aware of about this {0}?"
+msgstr "Quelque chose dont les utilisateurs devraient être conscient à propos de ce/cette {0} ?"
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:182
+#: frontend/src/metabase/reference/components/MetricImportantFieldsDetail.jsx:26
+msgid "Which 2-3 fields do you usually group this metric by?"
+msgstr "Quels champs (3 max.) regroupez-vous habituellement pour cette métrique ?"
+
+#: frontend/src/metabase/reference/components/GuideHeader.jsx:23
+msgid "This is the perfect place to start if you’re new to your company’s data, or if you just want to check in on what’s going on."
+msgstr "C'est l'endroit idéal où commencer si vous êtes novice dans les données de votre société, ou si vous voulez seulement voir ce qu'il s'y passe."
+
+#: frontend/src/metabase/reference/components/MetricImportantFieldsDetail.jsx:65
+msgid "Most useful fields to group this metric by"
+msgstr "Champs regroupés le plus souvent pour cette métrique"
+
+#: frontend/src/metabase/reference/components/RevisionMessageModal.jsx:32
+msgid "Reason for changes"
+msgstr "Raisons des modifications"
+
+#: frontend/src/metabase/reference/components/RevisionMessageModal.jsx:36
+msgid "Leave a note to explain what changes you made and why they were required"
+msgstr "Laisser un mot d'explication sur vos modifications et leurs motivations"
+
+#: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:166
+msgid "Why this database is interesting"
+msgstr "Pourquoi cette base de données est intéressante"
+
+#: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:176
+msgid "Things to be aware of about this database"
+msgstr "Choses dont on doit être conscient à propos de cette base de données"
+
+#: frontend/src/metabase/reference/databases/DatabaseList.jsx:46
+#: frontend/src/metabase/reference/guide/BaseSidebar.jsx:39
+msgid "Databases and tables"
+msgstr "Bases de données et tables"
+
+#: frontend/src/metabase/reference/databases/DatabaseSidebar.jsx:27
+#: frontend/src/metabase/reference/databases/FieldSidebar.jsx:38
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:170
+#: frontend/src/metabase/reference/databases/TableSidebar.jsx:31
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:184
+#: frontend/src/metabase/reference/metrics/MetricSidebar.jsx:27
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:188
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:187
+#: frontend/src/metabase/reference/segments/SegmentFieldSidebar.jsx:31
+#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:27
+msgid "Details"
+msgstr "Détails"
+
+#: frontend/src/metabase/reference/databases/DatabaseSidebar.jsx:33
+#: frontend/src/metabase/reference/databases/TableList.jsx:111
+msgid "Tables in {0}"
+msgstr "Tables dans {0}"
+
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:222
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:200
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:218
+msgid "Actual name in database"
+msgstr "Nom réel dans la base de données"
+
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:231
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:227
+msgid "Why this field is interesting"
+msgstr "Pourquoi ce champ est intéressant"
+
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:241
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:237
+msgid "Things to be aware of about this field"
+msgstr "Choses dont on doit être conscient à propos de ce champ"
+
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:253
+#: frontend/src/metabase/reference/databases/FieldList.jsx:155
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:249
+#: frontend/src/metabase/reference/segments/SegmentFieldList.jsx:156
+msgid "Data type"
+msgstr "Type de donnée"
+
+#: frontend/src/metabase/reference/databases/FieldList.jsx:39
+#: frontend/src/metabase/reference/segments/SegmentFieldList.jsx:39
+msgid "Fields in this table will appear here as they're added"
+msgstr "Les champs de cette table apparaîtront ici au fur et à mesure de leur ajout"
+
+#: frontend/src/metabase/reference/databases/FieldList.jsx:134
+#: frontend/src/metabase/reference/segments/SegmentFieldList.jsx:135
+msgid "Fields in {0}"
+msgstr "Champs dans {0}"
+
+#: frontend/src/metabase/reference/databases/FieldList.jsx:149
+#: frontend/src/metabase/reference/segments/SegmentFieldList.jsx:150
+msgid "Field name"
+msgstr "Nom du champ"
+
+#: frontend/src/metabase/reference/databases/FieldSidebar.jsx:46
+msgid "X-ray this field"
+msgstr "Radiographier ce champ"
+
+#: frontend/src/metabase/reference/databases/NoDatabasesEmptyState.jsx:8
+msgid "Metabase is no fun without any data"
+msgstr "Metabase n'est pas amusant sans aucune donnée"
+
+#: frontend/src/metabase/reference/databases/NoDatabasesEmptyState.jsx:9
+msgid "Your databases will appear here once you connect one"
+msgstr "Vos bases de données apparaîtront ici une fois connectées"
+
+#: frontend/src/metabase/reference/databases/NoDatabasesEmptyState.jsx:10
+msgid "Databases will appear here once your admins have added some"
+msgstr "Les bases de données apparaîtrons ici une fois que vos administrateurs en auront ajoutées"
+
+#: frontend/src/metabase/reference/databases/NoDatabasesEmptyState.jsx:12
+msgid "Connect a database"
+msgstr "Se connecter à une base de données"
+
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:38
+msgid "Count of {0}"
+msgstr "Nombre de {0}"
+
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:47
+msgid "See raw data for {0}"
+msgstr "Voir les données brutes pour {0}"
+
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:209
+msgid "Why this table is interesting"
+msgstr "Pourquoi cette table est intéressante"
+
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:219
+msgid "Things to be aware of about this table"
+msgstr "Choses dont on doit être conscient à propos de cette table"
+
+#: frontend/src/metabase/reference/databases/TableList.jsx:30
+msgid "Tables in this database will appear here as they're added"
+msgstr "Les tables de cette base apparaîtront ici au fur et à mesure de leur ajout"
+
+#: frontend/src/metabase/reference/databases/TableQuestions.jsx:34
+msgid "Questions about this table will appear here as they're added"
+msgstr "Les questions à propos de cette table apparaîtront ici au fur et à mesure de leur ajout"
+
+#: frontend/src/metabase/reference/databases/TableQuestions.jsx:71
+#: frontend/src/metabase/reference/metrics/MetricQuestions.jsx:75
+#: frontend/src/metabase/reference/metrics/MetricSidebar.jsx:33
+#: frontend/src/metabase/reference/segments/SegmentQuestions.jsx:74
+msgid "Questions about {0}"
+msgstr "Questions sur {0}"
+
+#: frontend/src/metabase/reference/databases/TableQuestions.jsx:95
+#: frontend/src/metabase/reference/metrics/MetricQuestions.jsx:99
+#: frontend/src/metabase/reference/segments/SegmentQuestions.jsx:98
+msgid "Created {0} by {1}"
+msgstr "{0} créé par {1}"
+
+#: frontend/src/metabase/reference/databases/TableSidebar.jsx:37
+msgid "Fields in this table"
+msgstr "Champs de cette table"
+
+#: frontend/src/metabase/reference/databases/TableSidebar.jsx:45
+msgid "Questions about this table"
+msgstr "Questions à propos de cette table"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:157
+msgid "Help your team get started with your data."
+msgstr "Aidez votre équipe à se familiariser avec vos données"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:159
+msgid "Show your team what’s most important by choosing your top dashboard, metrics, and segments."
+msgstr "Mettez en avant le plus important pour votre équipe en choisissant vos meilleurs tableaux de bords, métriques et segments."
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:165
+msgid "Get started"
+msgstr "Commencer"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:173
+msgid "Our most important dashboard"
+msgstr "Notre plus important tableau de bord"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:188
+msgid "Numbers that we pay attention to"
+msgstr "Chiffres auxquels nous payons attention"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:213
+msgid "Metrics are important numbers your company cares about. They often represent a core indicator of how the business is performing."
+msgstr "Les métriques sont les chiffres les plus importants pour votre entreprise. Elles représentent le plus souvent les indicateurs essentiels qui mesurent la performance de votre business."
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:221
+msgid "See all metrics"
+msgstr "Voir toutes les métriques"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:235
+msgid "Segments and tables"
+msgstr "Segments et tables"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:236
+msgid "Tables"
+msgstr "Tables"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:262
+msgid "Segments and tables are the building blocks of your company's data. Tables are collections of the raw information while segments are specific slices with specific meanings, like {0}"
+msgstr "Les segments et les table sont les éléments constitutifs des données de votre entreprise. Les tables sont des collections d'informations brutes tandis que les segments sont des tranches spécifiques avec des significations spécifiques, telles que {0}"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:267
+msgid "Tables are the building blocks of your company's data."
+msgstr "Les table sont les blocs de construction des données de votre entreprise."
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:277
+msgid "See all segments"
+msgstr "Voir tous les segments"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:293
+msgid "See all tables"
+msgstr "Voire toutes les tables"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:301
+msgid "Other things to know about our data"
+msgstr "Autres choses à savoir sur nos données"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:302
+msgid "Find out more"
+msgstr "Trouvez plus de choses"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:307
+msgid "A good way to get to know your data is by spending a bit of time exploring the different tables and other info available to you. It may take a while, but you'll start to recognize names and meanings over time."
+msgstr "Une bonne façon d'apprendre à connaître vos données est de passer un peu de temps à explorer les diverses tables et autres informations disponibles. Il se peut que cela prenne du temps, mais vous commencerez à reconnaître les noms et significations avec le temps."
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:313
+msgid "Explore our data"
+msgstr "Explorez vos données"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:321
+msgid "Have questions?"
+msgstr "Avez vous des questions?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:326
+msgid "Contact {0}"
+msgstr "Contacter {0}"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:248
+msgid "Help new Metabase users find their way around."
+msgstr "Aider les nouveaux utilisateurs de Metabase à trouver leur chemin."
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:251
+msgid "The Getting Started guide highlights the dashboard, metrics, segments, and tables that matter most, and informs your users of important things they should know before digging into the data."
+msgstr "Le guide de démarrage met en avant les tableaux de bord, métriques, segments et tables qui comptent le plus, et fournit des informations importantes aux utilisateurs avant qu'ils ne commencent à creuser les données."
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:258
+msgid "Is there an important dashboard for your team?"
+msgstr "Existe t'il un tableau de bord important pour votre équipe ?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:260
+msgid "Create a dashboard now"
+msgstr "Créer un nouveau tableau de bord maintenant"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:266
+msgid "What is your most important dashboard?"
+msgstr "Quel est votre plus important tableau de bord ?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:285
+msgid "Do you have any commonly referenced metrics?"
+msgstr "Avez-vous des métriques fréquemment référencées ?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:287
+msgid "Learn how to define a metric"
+msgstr "Apprenez comment définir une métrique"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:300
+msgid "What are your 3-5 most commonly referenced metrics?"
+msgstr "Quelles sont vos 3-5 métriques les plus fréquemment référencées ?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:344
+msgid "Add another metric"
+msgstr "Ajouter une autre métrique"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:357
+msgid "Do you have any commonly referenced segments or tables?"
+msgstr "Avez vous des segments fréquemment référencés ?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:359
+msgid "Learn how to create a segment"
+msgstr "Apprendre comment créer un segment"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:372
+msgid "What are 3-5 commonly referenced segments or tables that would be useful for this audience?"
+msgstr "Quelles sont vos 3-5 segments les plus fréquemment référencés ?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:418
+msgid "Add another segment or table"
+msgstr "Ajouter un nouveau segment ou une nouvelle table"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:427
+msgid "Is there anything your users should understand or know before they start accessing the data?"
+msgstr "Que devraient comprendre vos utilisateurs avant de commencer à accéder aux données ?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:433
+msgid "What should a user of this data know before they start accessing it?"
+msgstr "Que devrait savoir un utilisateur de cette donnée avant de commencer à y accéder ?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:437
+msgid "E.g., expectations around data privacy and use, common pitfalls or misunderstandings, information about data warehouse performance, legal notices, etc."
+msgstr "Par exemple : Règles d'usage et de confidentialité, erreurs et malentendus fréquents, temps de réponse de l'entrepôt, mentions légales, etc."
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:448
+msgid "Is there someone your users could contact for help if they're confused about this guide?"
+msgstr "Y a t'il une personne que vos utilisateurs pourraient contacter s'ils ont besoin d'éclaircissements à propos de ce guide ?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:457
+msgid "Who should users contact for help if they're confused about this data?"
+msgstr "A qui les utilisateurs pourraient demander de l'aide s'ils ont besoin d'éclaircissements sur ces données ?"
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:75
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:95
+msgid "Please enter a revision message"
+msgstr "Veuillez saisir un commentaire de révision"
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:213
+msgid "Why this Metric is interesting"
+msgstr "Pourquoi cette métrique est interessante ?"
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:223
+msgid "Things to be aware of about this Metric"
+msgstr "Tout ce qu'il faut savoir sur cette métrique"
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:233
+msgid "How this Metric is calculated"
+msgstr "Comment cette métrique est calculée"
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:235
+msgid "Nothing on how it's calculated yet"
+msgstr "Rien sur la façon dont c'est calculé pour le moment"
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:293
+msgid "Other fields you can group this metric by"
+msgstr "Autres champs d'agrégation possibles pour cette métrique"
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:294
+msgid "Fields you can group this metric by"
+msgstr "Champs sur lesquels vous pouvez aggréger cette métrique"
+
+#: frontend/src/metabase/reference/metrics/MetricList.jsx:23
+msgid "Metrics are the official numbers that your team cares about"
+msgstr "Les métriques sont les chiffres officiels dont s'occupe votre équipe"
+
+#: frontend/src/metabase/reference/metrics/MetricList.jsx:25
+msgid "Metrics will appear here once your admins have created some"
+msgstr "Les métriques apparaîtront ici une fois que les administrateurs en auront créé"
+
+#: frontend/src/metabase/reference/metrics/MetricList.jsx:27
+msgid "Learn how to create metrics"
+msgstr "Apprendre comment créer des métriques"
+
+#: frontend/src/metabase/reference/metrics/MetricQuestions.jsx:35
+msgid "Questions about this metric will appear here as they're added"
+msgstr "Les questions à propos de cette métrique apparaîtront ici au fil du temps"
+
+#: frontend/src/metabase/reference/metrics/MetricRevisions.jsx:29
+msgid "There are no revisions for this metric"
+msgstr "Il n'y pas de modifications pour cette métrique"
+
+#: frontend/src/metabase/reference/metrics/MetricRevisions.jsx:88
+#: frontend/src/metabase/reference/metrics/MetricSidebar.jsx:47
+#: frontend/src/metabase/reference/segments/SegmentRevisions.jsx:88
+msgid "Revision history for {0}"
+msgstr "Historique des modifications pour {0}"
+
+#: frontend/src/metabase/reference/metrics/MetricSidebar.jsx:39
+msgid "X-ray this metric"
+msgstr "Radiographier cette métrique"
+
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:217
+msgid "Why this Segment is interesting"
+msgstr "Pourquoi ce segment est intéressant"
+
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:227
+msgid "Things to be aware of about this Segment"
+msgstr "Choses dont on doit être conscient à propos de ce segment"
+
+#: frontend/src/metabase/reference/segments/SegmentList.jsx:23
+msgid "Segments are interesting subsets of tables"
+msgstr "Les segments sont des sous-ensembles remarquables de table"
+
+#: frontend/src/metabase/reference/segments/SegmentList.jsx:24
+msgid "Defining common segments for your team makes it even easier to ask questions"
+msgstr "Définir des segments communs à votre équipe rend les questions encore plus facile à poser"
+
+#: frontend/src/metabase/reference/segments/SegmentList.jsx:25
+msgid "Segments will appear here once your admins have created some"
+msgstr "Les segments apparaîtront ici une fois que les administrateurs en auront créé"
+
+#: frontend/src/metabase/reference/segments/SegmentList.jsx:27
+msgid "Learn how to create segments"
+msgstr "Apprendre comment créer des segments"
+
+#: frontend/src/metabase/reference/segments/SegmentQuestions.jsx:35
+msgid "Questions about this segment will appear here as they're added"
+msgstr "Les questions à propos de ce segment apparaîtront ici au fil du temps"
+
+#: frontend/src/metabase/reference/segments/SegmentRevisions.jsx:29
+msgid "There are no revisions for this segment"
+msgstr "Il n'y pas de modifications pour ce segment"
+
+#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:33
+msgid "Fields in this segment"
+msgstr "Champs utilisés dans ce segment"
+
+#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:39
+msgid "Questions about this segment"
+msgstr "Questions sur ce segment"
+
+#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:45
+msgid "X-ray this segment"
+msgstr "Radiographier ce segment"
+
+#: frontend/src/metabase/routes.jsx:182
+msgid "Login"
+msgstr "Se connecter"
+
+#: frontend/src/metabase/nav/containers/Navbar.jsx:130
+#: frontend/src/metabase/routes.jsx:198
+msgid "Search"
+msgstr "Rechercher"
+
+#: frontend/src/metabase/routes.jsx:217
+msgid "Dashboard"
+msgstr "Tableau de bord"
+
+#: frontend/src/metabase/routes.jsx:227
+msgid "New Question"
+msgstr "Nouvelle question"
+
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:125
+msgid "Select the type of Database you use"
+msgstr "Sélectionner le type de base de données que vous utilisez"
+
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:141
+msgid "Add your data"
+msgstr "Ajoutez vos données"
+
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:145
+msgid "I'll add my own data later"
+msgstr "J'ajouterai mes données plus tard"
+
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:146
+msgid "Connecting to {0}"
+msgstr "Connexion en cours à {0}"
+
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:165
+msgid "You’ll need some info about your database, like the username and password. If you don’t have that right now, Metabase also comes with a sample dataset you can get started with."
+msgstr "Vous aurez besoin de quelques informations sur votre base de données, comme le nom d'utilisateur et son mot de passe. Si vous ne les avez pas maintenant, Metabase est aussi fourni avec un jeu de données d'exemple avec lequel vous pouvez démarrer."
+
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:196
+msgid "I'll add my data later"
+msgstr "J'ajouterai mes données plus tard"
+
+#: frontend/src/metabase/setup/components/DatabaseSchedulingStep.jsx:41
+msgid "Control automatic scans"
+msgstr "Contrôler les analyses automatiques"
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:53
+msgid "Usage data preferences"
+msgstr "Préférences relatives aux données d'utilisation"
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:56
+msgid "Thanks for helping us improve"
+msgstr "Merci de nous aider à améliorer Metabase"
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:57
+msgid "We won't collect any usage events"
+msgstr "Nous ne collecterons aucun évènement d'utilisation"
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:76
+msgid "In order to help us improve Metabase, we'd like to collect certain data about usage through Google Analytics."
+msgstr "Pour nous aider à améliorer Metabase, nous voudrions collecter certaines données concernant son usage via Google Analytics."
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:85
+msgid "Here's a full list of everything we track and why."
+msgstr "Voici la liste complète de ce que nous collectons et pourquoi"
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:98
+msgid "Allow Metabase to anonymously collect usage events"
+msgstr "Permettre à Metabase de collecter anonymement les événements d'utilisation"
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:105
+msgid "Metabase {0} collects anything about your data or question results."
+msgstr "Metabase {0} ne collecte rien concernant vos données ou les réponses de vos questions."
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:106
+msgid "never"
+msgstr "jamais"
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:108
+msgid "All collection is completely anonymous."
+msgstr "Toute la collection est complètement anonyme"
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:110
+msgid "Collection can be turned off at any point in your admin settings."
+msgstr "La collection peut être désactivée à tout moment dans vos paramètres d'administration."
+
+#: frontend/src/metabase/setup/components/Setup.jsx:45
+msgid "If you feel stuck"
+msgstr "Si vous vous sentez coincé"
+
+#: frontend/src/metabase/setup/components/Setup.jsx:52
+msgid "our getting started guide"
+msgstr "notre guide de démarrage"
+
+#: frontend/src/metabase/setup/components/Setup.jsx:53
+msgid "is just a click away."
+msgstr "est à portée de clic."
+
+#: frontend/src/metabase/setup/components/Setup.jsx:95
+msgid "Welcome to Metabase"
+msgstr "Bienvenue sur Metabase"
+
+#: frontend/src/metabase/setup/components/Setup.jsx:96
+msgid "Looks like everything is working. Now let’s get to know you, connect to your data, and start finding you some answers!"
+msgstr "Tout semble fonctionner. À présent, dites-nous qui vous êtes, connectez vos données et commencez à trouver des réponses !"
+
+#: frontend/src/metabase/setup/components/Setup.jsx:100
+msgid "Let's get started"
+msgstr "C'est parti"
+
+#: frontend/src/metabase/setup/components/Setup.jsx:145
+msgid "You're all set up!"
+msgstr "Tout est configuré !"
+
+#: frontend/src/metabase/setup/components/Setup.jsx:156
+msgid "Take me to Metabase"
+msgstr "Emmenez-moi dans Metabase"
+
+#: frontend/src/metabase/setup/components/UserStep.jsx:155
+msgid "What should we call you?"
+msgstr "Comment devez-nous vous appeler ?"
+
+#: frontend/src/metabase/setup/components/UserStep.jsx:156
+msgid "Hi, {0}. nice to meet you!"
+msgstr "Bonjour, {0}, ravi de vous rencontrer !"
+
+#: frontend/src/metabase/setup/components/UserStep.jsx:243
+msgid "Create a password"
+msgstr "Créer un mot de passe"
+
+#: frontend/src/metabase/setup/components/UserStep.jsx:259
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:116
+msgid "Shhh..."
+msgstr "Chut ..."
+
+#: frontend/src/metabase/setup/components/UserStep.jsx:269
+msgid "Confirm password"
+msgstr "Confirmez le mot de passe"
+
+#: frontend/src/metabase/setup/components/UserStep.jsx:278
+msgid "Shhh... but one more time so we get it right"
+msgstr "Chut... une seconde fois de façon à s'en assurer"
+
+#: frontend/src/metabase/setup/components/UserStep.jsx:287
+msgid "Your company or team name"
+msgstr "Votre nom d'entreprise ou d'équipe"
+
+#: frontend/src/metabase/setup/components/UserStep.jsx:296
+msgid "Department of awesome"
+msgstr "Bureau des prodiges"
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:26
+msgid "Metabot is admiring your integers…"
+msgstr "MetaBot est en admiration devant vos chiffres..."
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:27
+msgid "Metabot is performing billions of differential equations…"
+msgstr "MetaBot effectue des milliards d'équations différentielles ..."
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:28
+msgid "Metabot is doing science…"
+msgstr "MetaBot fait de la science ..."
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:29
+msgid "Metabot is checking out your metrics…"
+msgstr "MetaBot vérifie vos métriques ..."
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:30
+msgid "Metabot is looking for trends and outliers…"
+msgstr "MetaBot recherche des tendances et des valeurs aberrantes ..."
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:31
+msgid "Metabot is consulting the quantum abacus…"
+msgstr "MetaBot consulte l'abaque quantique ..."
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:32
+msgid "Metabot is feeling pretty good about all this…"
+msgstr "MetaBot se sent bien dans tout ça ..."
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:52
+msgid "We’ll show you some interesting explorations of your data in\n"
+"just a few minutes."
+msgstr " Nous vous montrerons quelques explorations intéressantes de vos données en quelques minutes."
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:72
+msgid "This seems to be taking a while. In the meantime, you can check out one of these example explorations to see what Metabase can do for you."
+msgstr "Cela semble prendre un certain temps. En attendant, vous pouvez consulter l'une de ces explorations pour voir ce que Metabase peut faire pour vous."
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:86
+msgid "I took a look at the data you just connected, and I have some explorations of interesting things I found. Hope you like them!"
+msgstr "J'ai jeté un coup d'oeil aux données que vous venez de connecter et j'ai des explorations  intéressantes à vous montrer. J'espère que vous les aimerez!"
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:98
+msgid "I'm done exploring for now"
+msgstr "J'ai fini d'explorer pour l'instant"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:20
+msgid "Welcome to the Query Builder!"
+msgstr "Bienvenue dans le générateur de requêtes!"
+
+#. proposition pour fluidifier la formulation
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:22
+msgid "The Query Builder lets you assemble questions (or \"queries\") to ask about your data."
+msgstr "Le Générateur de requêtes vous permet d'assembler des questions (ou des «requêtes») pour interroger vos données."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:26
+msgid "Tell me more"
+msgstr "Dites-m'en plus"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:43
+msgid "Start by picking the table with the data that you have a question about."
+msgstr "Commencez par choisir la table contenant les données à questionner."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:45
+msgid "Go ahead and select the \"Orders\" table from the dropdown menu."
+msgstr "Lancez vous et sélectionnez la table \"Orders\" dans le menu déroulant."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:78
+msgid "Filter your data to get just what you want."
+msgstr "Filtrez vos données pour obtenir exactement ce que vous voulez."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:79
+msgid "Click the plus button and select the \"Created At\" field."
+msgstr "Cliquez sur le bouton plus et sélectionnez le champ \"Created at\"."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:93
+msgid "Here we can pick how many days we want to see data for, try 10"
+msgstr "Ici nous pouvons choisir sur combien de jours passés nous voulons voir les données, essayez 10"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:116
+msgid "Here's where you can choose to add or average your data, count the number of rows in the table, or just view the raw data."
+msgstr "Ici, vous pouvez choisir d'ajouter ou agréger vos données, compter le nombre de lignes dans la table ou simplement afficher les données brutes."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:118
+msgid "Try it: click on <strong>Raw Data</strong> to change it to <strong>Count of rows</strong> so we can count how many orders there are in this table."
+msgstr "Essayez : cliquez sur \"Données brutes\" pour le changer en \"Nombre de lignes\" de façon à compter combien de commandes il y a dans cette table."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:142
+msgid "Add a grouping to break out your results by category, day, month, and more."
+msgstr "Ajoutez une agrégation pour éclater vos résultats par catégorie, jour, mois et plus."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:144
+msgid "Let's do it: click on <strong>Add a grouping</strong>, and choose <strong>Created At: by Week</strong>."
+msgstr "Faisons cela : cliquons sur \"Ajouter une agrégation\", et choisissons \"Created At: par Semaine\""
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:152
+msgid "Click on \"by day\" to change it to \"Week.\""
+msgstr "Cliquez sur \"par jour\" pour le changer en \"Semaine\"."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:173
+msgid "Run Your Query."
+msgstr "Exécuter votre requête."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:175
+msgid "You're doing so well! Click <strong>Run query</strong> to get your results!"
+msgstr "Vous faites si bien ! Cliquez sur <strong>Lancer la requête</strong> pour obtenir vos résultats !"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:192
+msgid "You can view your results as a chart instead of a table."
+msgstr "Vous pouvez afficher vos résultats sous forme de graphique au lieu d'un tableau."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:194
+msgid "Everbody likes charts! Click the <strong>Visualization</strong> dropdown and select <strong>Line</strong>."
+msgstr "Tout le monde aime les graphiques ! Cliquez sur le menu déroulant <strong>Visualisation</strong> et sélectionnez <strong>Courbe</strong>"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:216
+msgid "Well done!"
+msgstr "Bien joué!"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:218
+msgid "That's all! If you still have questions, check out our"
+msgstr "C'est tout! Si vous avez encore des questions, consultez notre"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:223
+msgid "User's Guide"
+msgstr "Guide de l'utilisateur"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:223
+msgid "Have fun exploring your data!"
+msgstr "Amusez-vous à explorer vos données!"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:226
+msgid "Thanks"
+msgstr "Merci"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:235
+msgid "Save Your Questions"
+msgstr "Sauvegardez vos questions"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:237
+msgid "By the way, you can save your questions so you can refer to them later. Saved Questions can also be put into dashboards or Pulses."
+msgstr "Par ailleurs, vous pouvez sauvegarder votre question pour la retrouver plus tard. Les questions sauvegardées peuvent aussi être aujoutées aux tableaux de bords ou aux Pulses."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:241
+msgid "Sounds good"
+msgstr "Ça m'a l'air bien"
+
+#: frontend/src/metabase/tutorial/Tutorial.jsx:248
+msgid "Whoops!"
+msgstr "Oups!"
+
+#: frontend/src/metabase/tutorial/Tutorial.jsx:249
+msgid "Sorry, it looks like something went wrong. Please try restarting the tutorial in a minute."
+msgstr "Désolé, il semble que quelque chose se soit mal passé. Essayez s'il vous plaît de redémarrer cette présentation dans une minute."
+
+#: frontend/src/metabase/user/actions.js:34
+msgid "Password updated successfully!"
+msgstr "Mot de passe mis à jour avec succès!"
+
+#: frontend/src/metabase/user/actions.js:53
+msgid "Account updated successfully!"
+msgstr "Compte mis à jour avec succès!"
+
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:107
+msgid "Current password"
+msgstr "Mot de passe actuel"
+
+#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:137
+msgid "Sign in with Google Email address"
+msgstr "Se connecter avec Google"
+
+#: frontend/src/metabase/user/components/UserSettings.jsx:65
+msgid "User Details"
+msgstr "Détails de l'utilisateur"
+
+#: frontend/src/metabase/visualizations/components/ChartSettings.jsx:225
+msgid "Reset to defaults"
+msgstr "Réinitialiser"
+
+#: frontend/src/metabase/visualizations/components/ChoroplethMap.jsx:123
+msgid "unknown map"
+msgstr "carte inconnue"
+
+#: frontend/src/metabase/visualizations/components/LeafletGridHeatMap.jsx:26
+msgid "Grid map requires binned longitude/latitude."
+msgstr "Une carte à quadrillage nécessite un regroupement (binning) de la latitude et de la longitude."
+
+#: frontend/src/metabase/visualizations/components/LegendVertical.jsx:112
+msgid "more"
+msgstr "plus"
+
+#: frontend/src/metabase/visualizations/components/LineAreaBarChart.jsx:101
+msgid "Which fields do you want to use for the X and Y axes?"
+msgstr "Quels champs voulez-vous utiliser pour les axes X et Y ?"
+
+#: frontend/src/metabase/visualizations/components/LineAreaBarChart.jsx:103
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:59
+msgid "Choose fields"
+msgstr "Choisissez les champs"
+
+#: frontend/src/metabase/visualizations/components/PinMap.jsx:204
+msgid "Save as default view"
+msgstr "Sauvegarder comme vue par défaut"
+
+#: frontend/src/metabase/visualizations/components/PinMap.jsx:226
+msgid "Draw box to filter"
+msgstr "Dessiner un rectangle d'intérêt pour filtrer"
+
+#: frontend/src/metabase/visualizations/components/PinMap.jsx:226
+msgid "Cancel filter"
+msgstr "Annuler le filtre"
+
+#: frontend/src/metabase/visualizations/components/PinMap.jsx:47
+msgid "Pin Map"
+msgstr "Carte à épîngles"
+
+#: frontend/src/metabase/visualizations/components/TableInteractive.jsx:423
+msgid "Unset"
+msgstr "Désactiver"
+
+#: frontend/src/metabase/visualizations/components/TableSimple.jsx:227
+msgid "Rows {0}-{1} of {2}"
+msgstr "Lignes {0}-{1} parmi {2}"
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:184
+msgid "Data truncated to {0} rows."
+msgstr "Données tronquées à {0} lignes."
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:349
+msgid "Could not find visualization"
+msgstr "Impossible de trouver la visualisation"
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:356
+msgid "Could not display this chart with this data."
+msgstr "Impossible d'afficher cet graphique avec ces données."
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:454
+msgid "No results!"
+msgstr "Pas de résultat!"
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:475
+msgid "Still Waiting..."
+msgstr "Toujours en attente..."
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:478
+msgid "This usually takes an average of {0}."
+msgstr "Cela prend habituellement en moyenne {0}."
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:484
+msgid "(This is a bit long for a dashboard)"
+msgstr "(C'est un peu long pour un tableau de bord)"
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:488
+msgid "This is usually pretty fast but seems to be taking awhile right now."
+msgstr "C'est habituellement rapide mais il semble que cela commence à durer maintenant."
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingFieldPicker.jsx:14
+msgid "Select a field"
+msgstr "Sélectionner un champ"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingFieldsPicker.jsx:42
+msgid "error"
+msgstr "erreur"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingOrderedColumns.jsx:107
+msgid "Click and drag to change their order"
+msgstr "Cliquer et Glisser pour changer leur ordre"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingOrderedColumns.jsx:119
+msgid "Add fields from the list below"
+msgstr "Ajouter des champs depuis la liste ci-dessous"
+
+#. Quand une cellule de cette colonne est...
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:24
+msgid "less than"
+msgstr "inférieure"
+
+#. Quand une cellule de cette colonne est...
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:25
+msgid "greater than"
+msgstr "supérieure"
+
+#. Quand une cellule de cette colonne est...
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:26
+msgid "less than or equal to"
+msgstr "inférieure ou égale"
+
+#. Quand une cellule de cette colonne est...
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:27
+msgid "greater than or equal to"
+msgstr "supérieure ou égale"
+
+#. Quand une cellule de cette colonne est...
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:28
+msgid "equal to"
+msgstr "égale à"
+
+#. Quand une cellule de cette colonne est...
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:29
+msgid "not equal to"
+msgstr "différente de"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:169
+msgid "Conditional formatting"
+msgstr "Formattage conditionnel"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:171
+msgid "You can add rules to make the cells in this table change color if\n"
+"they meet certain conditions."
+msgstr "Vous pouvez ajouter des règles pour faire changer les cellules de couleur dans cette table, si elles remplissent certaines conditions."
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:181
+msgid "Add a rule"
+msgstr "Ajouter une règle"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:186
+msgid "Rules will be applied in this order"
+msgstr "Les règles seront appliquées dans cet ordre"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:187
+msgid "Click and drag to reorder."
+msgstr "Cliquer et glisser pour changer l'ordre"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:220
+msgid "No columns selected"
+msgstr "Pas de colonne sélectionnée"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:277
+msgid "Cells in this column will be tinted based on their values."
+msgstr "Les cellules de cette colonne seront teintées selon leur valeur."
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:279
+msgid "When a cell in these columns is {0} it will be tinted this color."
+msgstr "Quand une cellule dans ces colonnes est {0} elle sera teinté de cette couleur."
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:290
+msgid "Which columns should be affected?"
+msgstr "Quelles colonnes devraient être affectées ?"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:300
+msgid "Formatting style"
+msgstr "Style de formattage"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:304
+msgid "Single color"
+msgstr "Couleur unique"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:305
+msgid "Color range"
+msgstr "Plage de couleur"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:312
+msgid "When a cell in this column is…"
+msgstr "Quand une cellule de cette colonne est ..."
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:325
+msgid "…turn its background this color:"
+msgstr "... changer son arrière-plan dans cette couleur :"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:331
+msgid "Highlight the whole row"
+msgstr "Surligner toute la ligne"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:339
+msgid "Colors"
+msgstr "Couleurs"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:344
+msgid "Start the range at"
+msgstr "Commencer la plage à"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:349
+msgid "Smallest value in this column"
+msgstr "Plus petite valeur dans cette colonne"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:351
+msgid "Smallest value in each column"
+msgstr "Plus petite valeur dans chaque colonne"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:353
+msgid "Smallest value in all of these columns"
+msgstr "Plus petite valeur dans toutes ces colonnes"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:357
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:379
+msgid "Custom value"
+msgstr "Valeur personnalisée"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:366
+msgid "End the range at"
+msgstr "Arrêter la plage à"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:371
+msgid "Largest value in this column"
+msgstr "Plus grande valeur dans cette colonne"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:373
+msgid "Largest value in each column"
+msgstr "Plus grande valeur dans chaque colonne"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:375
+msgid "Largest value in all of these columns"
+msgstr "Plus grande valeur dans toutes ces colonnes"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:407
+msgid "Add rule"
+msgstr "Ajouter une règle"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:407
+msgid "Update rule"
+msgstr "Mettre à jour la règle"
+
+#: frontend/src/metabase/visualizations/index.js:30
+msgid "Visualization is null"
+msgstr "La visualisation est nulle"
+
+#: frontend/src/metabase/visualizations/index.js:35
+msgid "Visualization must define an 'identifier' static variable: "
+msgstr "La visualisation doit définir une variable statique d'identifiant : "
+
+#: frontend/src/metabase/visualizations/index.js:41
+msgid "Visualization with that identifier is already registered: "
+msgstr "Une visualisation avec cet identifiant est déjà enregistrée : "
+
+#: frontend/src/metabase/visualizations/index.js:69
+msgid "No visualization for {0}"
+msgstr "Aucune visualisation pour {0}"
+
+#: frontend/src/metabase/visualizations/lib/LineAreaBarRenderer.js:71
+msgid "\"{0}\" is an unaggregated field: if it has more than one value at a point on the x-axis, the values will be summed."
+msgstr "\"{0}\" est un champ non agrégé : S'il a plus d'une valeur pour une abscisse donnée, les valeurs seront additionnées."
+
+#: frontend/src/metabase/visualizations/lib/LineAreaBarRenderer.js:87
+msgid "This chart type requires at least 2 columns."
+msgstr "Ce type de graphique nécessite au moins 2 colonnes."
+
+#: frontend/src/metabase/visualizations/lib/LineAreaBarRenderer.js:92
+msgid "This chart type doesn't support more than {0} series of data."
+msgstr "Ce type de graphique ne prend pas en charge plus de {0} séries de données."
+
+#: frontend/src/metabase/visualizations/lib/LineAreaBarRenderer.js:509
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:42
+msgid "Goal"
+msgstr "Objectif"
+
+#: frontend/src/metabase/visualizations/lib/errors.js:9
+msgid "Doh! The data from your query doesn't fit the chosen display choice. This visualization requires at least {0} {1} of data."
+msgstr "Le résultat de votre requête n'est pas adapté au rendu choisi. Cette visualisation nécessite au moins {0} {1} de données."
+
+#: frontend/src/metabase/visualizations/lib/errors.js:9
+msgid "column"
+msgid_plural "columns"
+msgstr[0] "colonne"
+msgstr[1] "colonnes"
+
+#: frontend/src/metabase/visualizations/lib/errors.js:21
+msgid "No dice. We have {0} data {1} to show and that's not enough for this visualization."
+msgstr "Peine perdue. Nous avons {0} donnée {1} à présenter et ce n'est pas assez pour cette visualisation."
+
+#: frontend/src/metabase/visualizations/lib/errors.js:21
+msgid "point"
+msgid_plural "points"
+msgstr[0] "point"
+msgstr[1] "points"
+
+#: frontend/src/metabase/visualizations/lib/errors.js:33
+msgid "Bummer. We can't actually do a pin map for this data because we require both a latitude and longitude column."
+msgstr "Nous ne pouvons pas construire une carte à épingles pour cette donnée car cela nécessite à la fois une colonne de latitude et une colonne de longitude."
+
+#: frontend/src/metabase/visualizations/lib/errors.js:42
+msgid "Please configure this chart in the chart settings"
+msgstr "Configurez s'il vous plaît ce graphique"
+
+#: frontend/src/metabase/visualizations/lib/errors.js:44
+msgid "Edit Settings"
+msgstr "Modifier les paramètres de configuration"
+
+#: frontend/src/metabase/visualizations/lib/fill_data.js:38
+msgid "xValues missing!"
+msgstr "Des valeurs sont manquantes !"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:56
+#: frontend/src/metabase/visualizations/visualizations/RowChart.jsx:31
+msgid "X-axis"
+msgstr "Abscisses"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:82
+msgid "Add a series breakout..."
+msgstr "Eclater en plusieurs séries..."
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:93
+#: frontend/src/metabase/visualizations/visualizations/RowChart.jsx:35
+msgid "Y-axis"
+msgstr "Ordonnées"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:118
+msgid "Add another series..."
+msgstr "Ajouter une autre série..."
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:132
+msgid "Bubble size"
+msgstr "Taille des bulles"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:161
+#: frontend/src/metabase/visualizations/visualizations/LineChart.jsx:17
+msgid "Line"
+msgstr "Ligne"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:162
+msgid "Curve"
+msgstr "Courbe"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:163
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:67
+msgid "Step"
+msgstr "Etape"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:170
+msgid "Show point markers on lines"
+msgstr "Afficher des puces sur les lignes"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:178
+msgid "Stacking"
+msgstr "Empilement"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:182
+msgid "Don't stack"
+msgstr "Ne pas empiler"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:183
+msgid "Stack"
+msgstr "Empiler (Diagramme à bandes en effectifs)"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:184
+msgid "Stack - 100%"
+msgstr "Empiler - 100% (Diagramme à bandes en fréquence)"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:201
+msgid "Show goal"
+msgstr "Afficher l'objectif"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:207
+msgid "Goal value"
+msgstr "Valeur de l'objectif"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:218
+msgid "Replace missing values with"
+msgstr "Remplacer les valeurs manquantes par"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:223
+msgid "Zero"
+msgstr "Zéro"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:224
+msgid "Nothing"
+msgstr "Rien"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:225
+msgid "Linear Interpolated"
+msgstr "Interpolation linéaire"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:284
+msgid "X-axis scale"
+msgstr "Echelle des abscisses"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:301
+msgid "Timeseries"
+msgstr "Séries temporelles"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:304
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:322
+msgid "Linear"
+msgstr "Linéaire"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:306
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:323
+msgid "Power"
+msgstr "Exponentielle"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:307
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:324
+msgid "Log"
+msgstr "Logarithmique"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:309
+msgid "Histogram"
+msgstr "Histogramme"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:311
+msgid "Ordinal"
+msgstr "Ordinal"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:317
+msgid "Y-axis scale"
+msgstr "Echelle des ordonnées"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:330
+msgid "Show x-axis line and marks"
+msgstr "Afficher l'axe des abscisses et ses graduations"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:336
+msgid "Compact"
+msgstr "Compacte"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:337
+msgid "Rotate 45°"
+msgstr "Tourner de 45°"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:338
+msgid "Rotate 90°"
+msgstr "Tourner de 90°"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:345
+msgid "Show y-axis line and marks"
+msgstr "Afficher l'axe des ordonnées et ses graduations"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:357
+msgid "Auto y-axis range"
+msgstr "Mise à l'échelle automatique des ordonnées"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:401
+msgid "Use a split y-axis when necessary"
+msgstr "Utiliser deux axes d'ordonnées si nécessaire"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:408
+msgid "Show label on x-axis"
+msgstr "Afficher le libellé des abscisses"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:414
+msgid "X-axis label"
+msgstr "Libellé des abscisses"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:423
+msgid "Show label on y-axis"
+msgstr "Afficher le libellé des ordonnées"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:429
+msgid "Y-axis label"
+msgstr "Libellé des ordonnées"
+
+#: frontend/src/metabase/visualizations/lib/utils.js:116
+msgid "Standard Deviation"
+msgstr "Ecart-type"
+
+#: frontend/src/metabase/visualizations/visualizations/AreaChart.jsx:18
+msgid "Area"
+msgstr "Surface"
+
+#: frontend/src/metabase/visualizations/visualizations/AreaChart.jsx:21
+msgid "area chart"
+msgstr "Histogramme"
+
+#: frontend/src/metabase/visualizations/visualizations/BarChart.jsx:16
+msgid "Bar"
+msgstr "Bâton"
+
+#: frontend/src/metabase/visualizations/visualizations/BarChart.jsx:19
+msgid "bar chart"
+msgstr "diagramme en bâtons"
+
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:57
+msgid "Which fields do you want to use?"
+msgstr "Quels champs voulez-vous utiliser ?"
+
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:31
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:85
+msgid "Funnel"
+msgstr "Entonnoir"
+
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:74
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:67
+msgid "Measure"
+msgstr "Mesure"
+
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:80
+msgid "Funnel type"
+msgstr "Type d'entonnoir"
+
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:86
+msgid "Bar chart"
+msgstr "Diagramme en bâtons"
+
+#: frontend/src/metabase/visualizations/visualizations/LineChart.jsx:20
+msgid "line chart"
+msgstr "Courbe"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:211
+msgid "Please select longitude and latitude columns in the chart settings."
+msgstr "Choisissez s'il vous plaît les colonnes de longitude et latitude."
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:217
+msgid "Please select a region map."
+msgstr "Choisissez s'il vous plaît une carte régionale."
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:221
+msgid "Please select region and metric columns in the chart settings."
+msgstr "Choisissez s'il vous plaît les colonnes de métrique et de région."
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:31
+msgid "Map"
+msgstr "Carte"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:45
+msgid "Map type"
+msgstr "Type de carte"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:49
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:146
+msgid "Region map"
+msgstr "Carte régionale"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:50
+msgid "Pin map"
+msgstr "Carte à épingles"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:96
+msgid "Pin type"
+msgstr "Type d'épingle"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:101
+msgid "Tiles"
+msgstr "Tuiles"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:102
+msgid "Markers"
+msgstr "Puces"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:118
+msgid "Latitude field"
+msgstr "Champ latitude"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:128
+msgid "Longitude field"
+msgstr "Champ longitude"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:138
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:165
+msgid "Metric field"
+msgstr "Champ de métrique"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:170
+msgid "Region field"
+msgstr "Champ de région"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:179
+msgid "Radius"
+msgstr "Rayon"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:185
+msgid "Blur"
+msgstr "Flouter"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:191
+msgid "Min Opacity"
+msgstr "Opacité minimale"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:197
+msgid "Max Zoom"
+msgstr "Zoom maximum"
+
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:175
+msgid "No relationships found."
+msgstr "Aucune relation trouvée."
+
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:213
+msgid "via {0}"
+msgstr "via {0}"
+
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:290
+msgid "This {0} is connected to:"
+msgstr "Ce/cette {0} est relié(e) à :"
+
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:47
+msgid "Object Detail"
+msgstr "Détail de l'objet"
+
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:50
+msgid "object"
+msgstr "objet"
+
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:254
+msgid "Total"
+msgstr "Total"
+
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:53
+msgid "Which columns do you want to use?"
+msgstr "Quelles colonnes voulez-vous utiliser ?"
+
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:40
+msgid "Pie"
+msgstr "Pie"
+
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:62
+msgid "Dimension"
+msgstr "Dimension"
+
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:72
+msgid "Show legend"
+msgstr "Montrer la légende"
+
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:77
+msgid "Show percentages in legend"
+msgstr "Montrer les pourcentages en légende"
+
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:83
+msgid "Minimum slice percentage"
+msgstr "Pourcentage minimum d'une part"
+
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:136
+msgid "Goal met"
+msgstr "Objectif atteint"
+
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:138
+msgid "Goal exceeded"
+msgstr "Objectif dépassé"
+
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:208
+msgid "Goal {0}"
+msgstr "Objectif {0}"
+
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:35
+msgid "Progress visualization requires a number."
+msgstr "La visualisation en barre de progession nécessite un nombre."
+
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:23
+msgid "Progress"
+msgstr "Barre de progession"
+
+#: frontend/src/metabase/entities/collections.js:101
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:48
+msgid "Color"
+msgstr "Couleur"
+
+#: frontend/src/metabase/visualizations/visualizations/RowChart.jsx:13
+msgid "Row Chart"
+msgstr "Graphique en lignes"
+
+#: frontend/src/metabase/visualizations/visualizations/RowChart.jsx:16
+msgid "row chart"
+msgstr "graphique en lignes"
+
+#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:75
+msgid "Separator style"
+msgstr "Style de séparateur"
+
+#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:88
+msgid "Number of decimal places"
+msgstr "Nombre de décimales"
+
+#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:92
+msgid "Add a prefix"
+msgstr "Ajouter un préfixe"
+
+#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:96
+msgid "Add a suffix"
+msgstr "Ajouter un suffixe"
+
+#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:100
+msgid "Multiply by a number"
+msgstr "Coefficient multiplicateur"
+
+#: frontend/src/metabase/visualizations/visualizations/ScatterPlot.jsx:16
+msgid "Scatter"
+msgstr "Nuage de points"
+
+#: frontend/src/metabase/visualizations/visualizations/ScatterPlot.jsx:19
+msgid "scatter plot"
+msgstr "nuage de points"
+
+#: frontend/src/metabase/visualizations/visualizations/Table.jsx:61
+msgid "Pivot the table"
+msgstr "Pivoter cette table"
+
+#: frontend/src/metabase/visualizations/visualizations/Table.jsx:73
+msgid "Visible fields"
+msgstr "Champs visibles"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:167
+msgid "Write here, and use Markdown if you''d like"
+msgstr "Saisissez du texte ici, et utiliser Markdown si vous le souhaitez"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:73
+msgid "Vertical Alignment"
+msgstr "Alignement vertical"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:77
+msgid "Top"
+msgstr "Haut"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:78
+msgid "Middle"
+msgstr "Milieu"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:79
+msgid "Bottom"
+msgstr "Bas"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:86
+msgid "Horizontal Alignment"
+msgstr "Alignement horizontal"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:90
+msgid "Left"
+msgstr "Gauche"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:91
+msgid "Center"
+msgstr "Centrer"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:92
+msgid "Right"
+msgstr "Droite"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:99
+msgid "Show background"
+msgstr "Montrer l'arrière plan"
+
+#: frontend/src/metabase-lib/lib/Dimension.js:497
+msgid "{0} bin"
+msgid_plural "{0} bins"
+msgstr[0] "{0} cellule"
+msgstr[1] "{0} cellules"
+
+#: frontend/src/metabase-lib/lib/Dimension.js:503
+msgid "Auto binned"
+msgstr "Binnage automatique"
+
+#: src/metabase/api/alert.clj
+msgid "DELETE /api/alert/:id is deprecated. Instead, change its `archived` value via PUT /api/alert/:id."
+msgstr "l'usage de DELETE /api/alert/:id n'est plus encouragé. A la place, changez sa valeur à `archived` via PUT /api/alert/:id."
+
+#: src/metabase/api/automagic_dashboards.clj
+msgid "invalid show value"
+msgstr "Valeur de présentation invalide"
+
+#: src/metabase/api/automagic_dashboards.clj
+msgid "invalid value for prefix"
+msgstr "Valeur de préfixe invalide"
+
+#: src/metabase/api/automagic_dashboards.clj
+msgid "invalid value for rule name"
+msgstr "Valeur de nom de règle invalide"
+
+#: src/metabase/api/automagic_dashboards.clj
+msgid "value couldn''t be parsed as base64 encoded JSON"
+msgstr "la valeur n'a pas pu être analysée comme du JSON encodé en base64"
+
+#: src/metabase/api/automagic_dashboards.clj
+msgid "Invalid entity type"
+msgstr "Type d'entité invalide"
+
+#: src/metabase/api/automagic_dashboards.clj
+msgid "Invalid comparison entity type. Can only be one of \"table\", \"segment\", or \"adhoc\""
+msgstr "Type d'entité de comparaison invalide. Peut seulement prendre ses valeurs dans \"table\", \"segment\" ou \"adhoc\""
+
+#: src/metabase/api/card.clj
+msgid "Error running query to determine Card result metadata:"
+msgstr "Erreur lors de la détermination des métadonnées du résultat de la Carte :"
+
+#: src/metabase/api/card.clj
+msgid "DELETE /api/card/:id is deprecated. Instead, change its `archived` value via PUT /api/card/:id."
+msgstr "DELETE /api/card/:id is deprecated. Instead, change its `archived` value via PUT /api/card/:id."
+
+#: src/metabase/api/common.clj src/metabase/api/common/internal.clj
+msgid "Invalid field: {0}"
+msgstr "Champ invalide : {0}"
+
+#: src/metabase/api/common.clj
+msgid "Invalid value ''{0}'' for ''{1}'': {2}"
+msgstr "Valeur invalide \"{0}\" pour \"{1}\" : {2}"
+
+#: src/metabase/api/common.clj
+msgid "must be one of: {0}"
+msgstr "doit prendre ses valeurs parmi : {0}"
+
+#: src/metabase/api/common.clj
+msgid "Invalid Request."
+msgstr "Requête invalide"
+
+#: src/metabase/api/common.clj
+msgid "Not found."
+msgstr "Non trouvé."
+
+#: src/metabase/api/common.clj
+msgid "You don''t have permissions to do that."
+msgstr "Vous n'avez pas les permissions requises."
+
+#: src/metabase/api/common.clj
+msgid "Internal server error."
+msgstr "Erreur interne du serveur."
+
+#: src/metabase/api/common.clj
+msgid "Warning: endpoint {0}/{1} does not have a docstring."
+msgstr "Avertissement : l'endpoint {0}/{1} n'a pas de docstring."
+
+#: src/metabase/api/common.clj
+msgid "starting streaming request"
+msgstr "démarrage de la requête de flux"
+
+#: src/metabase/api/common.clj
+msgid "connection closed, canceling request"
+msgstr "connexion fermée, requête en cours d'annulation"
+
+#. a newline padding character as it's harmless and will allow us to check if the client is connected. If
+#. sending this character fails because the connection is closed, the chan will then close.  Newlines are
+#. no-ops when reading JSON which this depends upon.
+#: src/metabase/api/common.clj
+msgid "Response not ready, writing one byte & sleeping..."
+msgstr "Réponse pas encore prête, écriture d'un octet & mise en sommeil..."
+
+#: src/metabase/api/common.clj
+msgid "Public sharing is not enabled."
+msgstr "Le partage public n'est pas activé."
+
+#: src/metabase/api/common.clj
+msgid "Embedding is not enabled."
+msgstr "L'intégration n'est pas activé."
+
+#: src/metabase/api/common.clj
+msgid "The object has been archived."
+msgstr "L' objet a été archivé."
+
+#: src/metabase/api/common/internal.clj
+msgid "Attempted to return a boolean as an API response. This is not allowed!"
+msgstr "Tentative de retour d'un booléen comme réponse d'API. Cela est interdit !"
+
+#: src/metabase/api/dataset.clj
+msgid "Source query for this query is Card {0}"
+msgstr "La requête source pour cette requête est la Carte {0}"
+
+#: src/metabase/api/dataset.clj
+msgid "Invalid export format: {0}"
+msgstr "Format d'exportation invalide : {0}"
+
+#: src/metabase/api/geojson.clj
+msgid "Invalid JSON URL or resource: {0}"
+msgstr "Le contenu JSON de cette URL ou de cette ressource est invalide : {0}"
+
+#: src/metabase/api/geojson.clj
+msgid "JSON containing information about custom GeoJSON files for use in map visualizations instead of the default US State or World GeoJSON."
+msgstr "Fichiers GeoJSON personnalisés à utiliser dans les visualisations de type Carte à la place des GeoJSON des états d'Amérique ou du monde fournis par défaut."
+
+#: src/metabase/api/geojson.clj
+msgid "Invalid custom GeoJSON key: {0}"
+msgstr "Clé du GeoJSON personnalisé invalide : {0}"
+
+#. ...but if we *still* couldn't find a match, throw an Exception, because we don't want people
+#. trying to inject new params
+#: src/metabase/api/public.clj
+msgid "Invalid param: {0}"
+msgstr "Paramètre invalide : {0}"
+
+#: src/metabase/api/pulse.clj
+msgid "DELETE /api/pulse/:id is deprecated. Instead, change its `archived` value via PUT /api/pulse/:id."
+msgstr "DELETE /api/pulse/:id is deprecated. Instead, change its `archived` value via PUT /api/pulse/:id."
+
+#: src/metabase/api/routes.clj
+msgid "API endpoint does not exist."
+msgstr "l'endpoint d'API n'existe pas."
+
+#: src/metabase/api/session.clj
+msgid "Password did not match stored password."
+msgstr "Le mot de passe ne correspondait pas à celui sauvegardé."
+
+#: src/metabase/api/session.clj
+msgid "did not match stored password"
+msgstr "ne correspondait pas au mot de passe sauvegardé"
+
+#: src/metabase/api/session.clj
+msgid "Problem connecting to LDAP server, will fallback to local authentication {0}"
+msgstr "Problème à la connexion au serveur LDAP, procédure de repli vers l'authentification locale {0}"
+
+#: src/metabase/api/session.clj
+msgid "Invalid reset token"
+msgstr "Token de réinitialisation invalide"
+
+#: src/metabase/api/session.clj
+msgid "Client ID for Google Auth SSO. If this is set, Google Auth is considered to be enabled."
+msgstr "ID Client pour Google Auth SSO. S'il est renseigné, Google Auth est supposé être activé."
+
+#: src/metabase/api/session.clj
+msgid "When set, allow users to sign up on their own if their Google account email address is from this domain."
+msgstr "Si indiqué, autorise les utilisateurs à se connecter avec leur compte Google si leur adresse correspond à ce domaine."
+
+#: src/metabase/api/session.clj
+msgid "Invalid Google Auth token."
+msgstr "Jeton Google Auth invalide."
+
+#: src/metabase/api/session.clj
+msgid "Email is not verified."
+msgstr "L' adresse électronique n'est pas vérifié."
+
+#: src/metabase/api/session.clj
+msgid "You''ll need an administrator to create a Metabase account before you can use Google to log in."
+msgstr "Vous aurez besoin qu'un administrateur crée un compte Metabase avant de puvoir utiliser Google pour vous connecter."
+
+#: src/metabase/api/session.clj
+msgid "Successfully authenticated Google Auth token for: {0} {1}"
+msgstr "Réussite de l'authentification du jeton Google Auth pour : {0} {1}"
+
+#: src/metabase/api/setup.clj
+msgid "Add a database"
+msgstr "Ajouter une base de données"
+
+#: src/metabase/api/setup.clj
+msgid "Get connected"
+msgstr "Connectez-vous"
+
+#: src/metabase/api/setup.clj
+msgid "Connect to your data so your whole team can start to explore."
+msgstr "Connectez-vous à vos données pour que l'ensemble de votre équipe puisse commencer à les explorer."
+
+#: src/metabase/api/setup.clj
+msgid "Set up email"
+msgstr "Configurer la messagerie électronique"
+
+#: src/metabase/api/setup.clj
+msgid "Add email credentials so you can more easily invite team members and get updates via Pulses."
+msgstr "Configurez la messagerie électronique pour inviter plus facilement des membres de votre équipe et obtenir des mises à jour via les Pulses."
+
+#: src/metabase/api/setup.clj
+msgid "Set Slack credentials"
+msgstr "Régler les identifiants Slack"
+
+#: src/metabase/api/setup.clj
+msgid "Does your team use Slack? If so, you can send automated updates via pulses and ask questions with MetaBot."
+msgstr "Votre équipe utilise Slack ? Vous pouvez envoyer des mises à jour automatiques via les pulses et poser des questions avec MetaBot."
+
+#: src/metabase/api/setup.clj
+msgid "Invite team members"
+msgstr "Inviter des membres de l'équipe"
+
+#: src/metabase/api/setup.clj
+msgid "Share answers and data with the rest of your team."
+msgstr "Partagez des réponses et des données avec le reste de votre équipe."
+
+#: src/metabase/api/setup.clj
+msgid "Hide irrelevant tables"
+msgstr "Masquer les tables non pertinentes"
+
+#: src/metabase/api/setup.clj
+msgid "Curate your data"
+msgstr "Organisez vos données"
+
+#: src/metabase/api/setup.clj
+msgid "If your data contains technical or irrelevant info you can hide it."
+msgstr "Si votre donnée contient des informations techniques ou non pertinentes vous pouvez la masquer."
+
+#: src/metabase/api/setup.clj
+msgid "Organize questions"
+msgstr "Organiser les questions"
+
+#: src/metabase/api/setup.clj
+msgid "Have a lot of saved questions in {0}? Create collections to help manage them and add context."
+msgstr "Vous avez beaucoup de questions sauvegardées dans {0} ? Créez des collections pour vous aider à les gérer et les mettre en contexte."
+
+#. 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
+#: src/metabase/api/setup.clj
+msgid "Metabase"
+msgstr "Metabase"
+
+#: src/metabase/api/setup.clj
+msgid "Create metrics"
+msgstr "Créer des métriques"
+
+#: src/metabase/api/setup.clj
+msgid "Define canonical metrics to make it easier for the rest of your team to get the right answers."
+msgstr "Définissez des métriques pour aider votre équipe à obtenir les bonnes réponses."
+
+#: src/metabase/api/setup.clj
+msgid "Create segments"
+msgstr "Créer des segments"
+
+#: src/metabase/api/setup.clj
+msgid "Keep everyone on the same page by creating canonical sets of filters anyone can use while asking questions."
+msgstr "Gagnez en cohésion en créant des jeux communs de filtres utilisables par tous lors de la rédaction de questions."
+
+#: src/metabase/api/table.clj
+msgid "Table ''{0}'' is now visible. Resyncing."
+msgstr "La table \"{0}\" n'est pas visible. Resynchronisation en cours."
+
+#: src/metabase/api/table.clj
+msgid "Auto bin"
+msgstr "Binnage automatique"
+
+#: src/metabase/api/table.clj
+msgid "Don''t bin"
+msgstr "Aucun binnage"
+
+#: src/metabase/api/table.clj
+msgid "Day"
+msgstr "Jour"
+
+#. note the order of these options corresponds to the order they will be shown to the user in the UI
+#: src/metabase/api/table.clj
+msgid "Minute"
+msgstr "Minute"
+
+#: src/metabase/api/table.clj
+msgid "Hour"
+msgstr "Heure"
+
+#: src/metabase/api/table.clj
+msgid "Quarter"
+msgstr "Semestre"
+
+#: src/metabase/api/table.clj
+msgid "Minute of Hour"
+msgstr "Minute dans l'heure"
+
+#: src/metabase/api/table.clj
+msgid "Hour of Day"
+msgstr "Heure dans le jour"
+
+#: src/metabase/api/table.clj
+msgid "Day of Week"
+msgstr "Jour de la semaine"
+
+#: src/metabase/api/table.clj
+msgid "Day of Month"
+msgstr "Jour du mois"
+
+#: src/metabase/api/table.clj
+msgid "Day of Year"
+msgstr "Jour dans l'année"
+
+#: src/metabase/api/table.clj
+msgid "Week of Year"
+msgstr "Semaine de l'année"
+
+#: src/metabase/api/table.clj
+msgid "Month of Year"
+msgstr "Mois dans l'année"
+
+#: src/metabase/api/table.clj
+msgid "Quarter of Year"
+msgstr "Semestre dans l'année"
+
+#: src/metabase/api/table.clj
+msgid "10 bins"
+msgstr "10 cellules"
+
+#: src/metabase/api/table.clj
+msgid "50 bins"
+msgstr "50 cellules"
+
+#: src/metabase/api/table.clj
+msgid "100 bins"
+msgstr "100 cellules"
+
+#: src/metabase/api/table.clj
+msgid "Bin every 0.1 degrees"
+msgstr "Binner tous les 0,1 degrés"
+
+#: src/metabase/api/table.clj
+msgid "Bin every 1 degree"
+msgstr "Binner tous les degrés"
+
+#: src/metabase/api/table.clj
+msgid "Bin every 10 degrees"
+msgstr "Binner tous les 10 degrés"
+
+#: src/metabase/api/table.clj
+msgid "Bin every 20 degrees"
+msgstr "Binner tous les 20 degrés"
+
+#. returns `true` if successful -- see JavaDoc
+#: src/metabase/api/tiles.clj src/metabase/pulse/render.clj
+msgid "No appropriate image writer found!"
+msgstr "Pas de sortie d'image appropriée trouvée !"
+
+#: src/metabase/api/user.clj
+msgid "Email address already in use."
+msgstr "Adresse électronique déjà utilisée"
+
+#: src/metabase/api/user.clj
+msgid "Email address already associated to another user."
+msgstr "Cette adresse électronique est déjà associée à un autre utilisateur."
+
+#: src/metabase/api/user.clj
+msgid "Not able to reactivate an active user"
+msgstr "Impossible de réactiver un utilisateur actif"
+
+#: src/metabase/api/user.clj
+msgid "Invalid password"
+msgstr "Mot de passe incorrect"
+
+#: src/metabase/automagic_dashboards/comparison.clj
+msgid "All {0}"
+msgstr "Tous/Toutes les {0}"
+
+#: src/metabase/automagic_dashboards/comparison.clj
+msgid "{0}, all {1}"
+msgstr "{0}, tous/toutes les {1}"
+
+#: src/metabase/automagic_dashboards/comparison.clj
+msgid "Comparison of {0} and {1}"
+msgstr "Comparaison de {0} avec {1}"
+
+#: src/metabase/automagic_dashboards/comparison.clj
+msgid "Automatically generated comparison dashboard comparing {0} and {1}"
+msgstr "Tableau de bord de comparaison généré automatiquement et comparant {0} à {1}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "sum"
+msgstr "somme"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "average"
+msgstr "moyenne"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "minumum"
+msgstr "minimum"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "maximum"
+msgstr "maximum"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "distinct count"
+msgstr "Nombre de valeurs distinctes"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "standard deviation"
+msgstr "ecart-type"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "cumulative count"
+msgstr "nombre cumulé"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "cumulative sum"
+msgstr "somme cumulée"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} and {1}"
+msgstr "{0} et {1}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} of {1}"
+msgstr "{0} parmi {1}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} by {1}"
+msgstr "{0} par {1}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} in the {1} segment"
+msgstr "{0} dans le segment {1}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} segment"
+msgstr "le segment {0}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} metric"
+msgstr "la métrique {0}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} field"
+msgstr "le champ {0}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "\"{0}\" question"
+msgstr "la question \"{0}\""
+
+#: src/metabase/automagic_dashboards/comparison.clj
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Compare with {0}"
+msgstr "Comparer avec {0}"
+
+#: src/metabase/automagic_dashboards/comparison.clj
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Compare with entire dataset"
+msgstr "Comparer avec le jeu de données complet"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Applying heuristic %s to %s."
+msgstr "Application de l'heuristique %s à %s."
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Dimensions bindings:n%s"
+msgstr "Liens de dimensions : %s"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Using definitions:nMetrics:n%snFilters:n%s"
+msgstr "Utilisation des définitions : Métriques %s, Filtres %s"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Can''t create dashboard for {0}"
+msgstr "Impossible de créer un tableau de bord pour {0}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0}st"
+msgstr "premier {0}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0}nd"
+msgstr "second {0}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0}rd"
+msgstr "troisième {0}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0}th"
+msgstr "{0} ième"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "at {0}"
+msgstr "à {0}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "on {0}"
+msgstr "le {0}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "in {0} week - {1}"
+msgstr "en {0} semaine - {1}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "in {0}"
+msgstr "en {0}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "in Q{0} - {1}"
+msgstr "au semestre {0} - {1}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Q{0}"
+msgstr "au {0} semestre"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} is {1}"
+msgstr "{0} vaut {1}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} is between {1} and {2}"
+msgstr "{0} est compris entre {1} et {2}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} is between {1} and {2}; and {3} is between {4} and {5}"
+msgstr "{0}est compris entre {1} et {2}; et {3} entre {4} et {5}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "where {0}"
+msgstr "où {0}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "A closer look at {0}"
+msgstr "Voir {0} de plus près"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "A closer look at the {0}"
+msgstr "Voir le/les {0} de plus près"
+
+#: src/metabase/automagic_dashboards/populate.clj
+msgid "Adding %s cards to dashboard %s:n%s"
+msgstr "Ajout de %s cartes au tableau de bord %s : n%s"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "0 <= score <= {0}"
+msgstr "0 <= score <= {0}"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "1 <= width <= {0}"
+msgstr "1 <= largeur <= {0}"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid metrics references"
+msgstr "Références de métriques valides"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid filters references"
+msgstr "Références de filtres valides"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid group references"
+msgstr "Références de groupe valides"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid order_by references"
+msgstr "Références de tri valides"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid dashboard filters references"
+msgstr "Références de filtres de tableau de bord valides"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid dimension references"
+msgstr "Références de dimension valides"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid card dimension references"
+msgstr "Références de dimension de carte valides"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Error parsing %s:n%s"
+msgstr "Erreur lors de l'analyse %s : n%s"
+
+#: src/metabase/cmd/reset_password.clj
+msgid "No user found with email address ''{0}''. "
+msgstr "Aucun utilisateur trouvé avec l'adresse électronique \"{0}\". "
+
+#: src/metabase/cmd/reset_password.clj
+msgid "Please check the spelling and try again."
+msgstr "Vérifiez s'il vous plaît l'orthographe et essayez à nouveau."
+
+#: src/metabase/cmd/reset_password.clj
+msgid "Resetting password for {0}..."
+msgstr "Mot de passe de {0} en cours de réinitialisation .."
+
+#: src/metabase/cmd/reset_password.clj
+msgid "OK [[[{0}]]]"
+msgstr "OK [[[{0}]]]"
+
+#: src/metabase/cmd/reset_password.clj
+msgid "FAIL [[[{0}]]]"
+msgstr "ECHEC [[[{0}]]]"
+
+#: src/metabase/core.clj
+msgid "Please use the following URL to setup your Metabase installation:"
+msgstr "Utilisez s'il vous plaît l'URL suivante pour configurer votre installation Metabase :"
+
+#: src/metabase/core.clj
+msgid "Metabase Shutting Down ..."
+msgstr "Metabase en cours d'arrêt..."
+
+#: src/metabase/core.clj
+msgid "Metabase Shutdown COMPLETE"
+msgstr "Arrêt de Metabase EFFECTIF"
+
+#: src/metabase/core.clj
+msgid "Starting Metabase version {0} ..."
+msgstr "Démarrage de Metabase version {0}..."
+
+#: src/metabase/core.clj
+msgid "System timezone is ''{0}'' ..."
+msgstr "Le fuseau horaire du système est \"{0}\"..."
+
+#. startup database.  validates connection & runs any necessary migrations
+#: src/metabase/core.clj
+msgid "Setting up and migrating Metabase DB. Please sit tight, this may take a minute..."
+msgstr "Configuration et migration de la base de données Metabase. Faites s'il vous plaît preuve de patience, cela peut prendre une minute..."
+
+#: src/metabase/core.clj
+msgid "Looks like this is a new installation ... preparing setup wizard"
+msgstr "I semble que ce soit une nouvelle installation... Préparation de l'assistant de configuration"
+
+#: src/metabase/core.clj
+msgid "Metabase Initialization COMPLETE"
+msgstr "Initialisation de Metabase EFFECTIVE"
+
+#: src/metabase/core.clj
+msgid "Launching Embedded Jetty Webserver with config:"
+msgstr "Lancement du serveur web Jetty intégré avec la configuration :"
+
+#: src/metabase/core.clj
+msgid "Shutting Down Embedded Jetty Webserver"
+msgstr "Arrêt du serveur web Jetty intégré"
+
+#: src/metabase/core.clj
+msgid "Starting Metabase in STANDALONE mode"
+msgstr "Démarrage de Metabase en mode AUTONOME"
+
+#: src/metabase/core.clj
+msgid "Metabase Initialization FAILED"
+msgstr "L'Initialisation de Metabase a ECHOUE"
+
+#: src/metabase/db.clj
+msgid "Database has migration lock; cannot run migrations."
+msgstr "La base de données a un verrou de migration; impossible de lancer les migrations."
+
+#: src/metabase/db.clj
+msgid "You can force-release these locks by running `java -jar metabase.jar migrate release-locks`."
+msgstr "Vous pouvez forcer la libération de ces verrous en lançant `java -jar metabase.jar migrate release-locks`."
+
+#: src/metabase/db.clj
+msgid "Checking if Database has unrun migrations..."
+msgstr "Vérification de la présence de migrations non appliquées sur la base de données"
+
+#: src/metabase/db.clj
+msgid "Database has unrun migrations. Waiting for migration lock to be cleared..."
+msgstr "La base de données a des migrations non appliquées. En attente de la libération du verrou de migration..."
+
+#: src/metabase/db.clj
+msgid "Migration lock is cleared. Running migrations..."
+msgstr "Le verrou est libéré. Application des migrations..."
+
+#: src/metabase/db.clj
+msgid "Migration lock cleared, but nothing to do here! Migrations were finished by another instance."
+msgstr "Verrou de migration libéré, mais rien à faire ici ! Les Migrations ont été appliquées par une autre instance."
+
+#. Set up liquibase and let it do its thing
+#: src/metabase/db.clj
+msgid "Setting up Liquibase..."
+msgstr "Configuration de Liquibase..."
+
+#: src/metabase/db.clj
+msgid "Liquibase is ready."
+msgstr "Liquibase est prêt."
+
+#: src/metabase/db.clj
+msgid "Verifying {0} Database Connection ..."
+msgstr "Vérification de la connexion à la base de données {0}..."
+
+#: src/metabase/db.clj
+msgid "Verify Database Connection ... "
+msgstr "Vérifier la connexion à la base de données... "
+
+#: src/metabase/db.clj
+msgid "Running Database Migrations..."
+msgstr "Application des migrations de base de données..."
+
+#: src/metabase/db.clj
+msgid "Database Migrations Current ... "
+msgstr "Migrations de la base de données en cours... "
+
+#: src/metabase/driver.clj
+msgid "Hmm, we couldn''t connect to the database."
+msgstr "Hum, nous n'avons pas pu nous connecter à la base de données"
+
+#: src/metabase/driver.clj
+msgid "Make sure your host and port settings are correct"
+msgstr "Assurez-vous que les réglages d'hôte et de numéro de port sont corrects"
+
+#: src/metabase/driver.clj
+msgid "We couldn''t connect to the ssh tunnel host."
+msgstr "Nous n'avons pas pu ouvrir le tunnel ssh."
+
+#: src/metabase/driver.clj
+msgid "Check the username, password."
+msgstr "Vérifier le nom d'utilisateur, son mot de passe."
+
+#: src/metabase/driver.clj
+msgid "Check the hostname and port."
+msgstr "Vérifier le nom d'hôte et son numéro de port."
+
+#: src/metabase/driver.clj
+msgid "Looks like the database name is incorrect."
+msgstr "Il semble que le nom de la base de données soit incorrect."
+
+#: src/metabase/driver.clj
+msgid "It looks like your host is invalid."
+msgstr "Il semble que votre hôte soit invalide."
+
+#: src/metabase/driver.clj
+msgid "Please double-check it and try again."
+msgstr "Faîtes s'il vous plaît une contre-vérification et essayez à nouveau."
+
+#: src/metabase/driver.clj
+msgid "Looks like your password is incorrect."
+msgstr "Il semble que votre mot de passe soit incorrect."
+
+#: src/metabase/driver.clj
+msgid "Looks like you forgot to enter your password."
+msgstr "Il semble que vous ayez oublié de saisir votre mot de passe."
+
+#: src/metabase/driver.clj
+msgid "Looks like your username is incorrect."
+msgstr "Il semble que votre nom d'utilisateur soit incorrect."
+
+#: src/metabase/driver.clj
+msgid "Looks like the username or password is incorrect."
+msgstr "Il semble que le nom d'utilisateur ou le mot de passe soit incorrect."
+
+#. ## CONFIG
+#: src/metabase/driver.clj
+msgid "Connection timezone to use when executing queries. Defaults to system timezone."
+msgstr "Fuseau horaire de connexion à utiliser lors de l'exécution de requêtes. Positionné au fuseau système par défaut."
+
+#: src/metabase/driver.clj
+msgid "Registered driver {0} {1}"
+msgstr "Gestionnaire enregistré {0} {1}"
+
+#: src/metabase/driver.clj
+msgid "No -init-driver function found for ''{0}''"
+msgstr "Pas de fonction -init-driver trouvé pour \"{0}\""
+
+#: src/metabase/driver.clj
+msgid "Unable to parse date string ''{0}'' for database engine ''{1}''"
+msgstr "Dans l'incapacité d'analyser la date représentée par \"{0}\" dans le moteur de base de données \"{1}\""
+
+#. all-NULL columns in DBs like Mongo w/o explicit types
+#: src/metabase/driver.clj
+msgid "Don''t know how to map class ''{0}'' to a Field base_type, falling back to :type/*."
+msgstr "Pas de connaissance sur la façon d'associer la classe \"{0}\" à un \"base_type\" de champ, solution de repli vers : type/*"
+
+#: src/metabase/driver.clj
+msgid "Failed to connect to database: {0}"
+msgstr "Echec de connexion à la base de données : {0}"
+
+#: src/metabase/driver/bigquery.clj
+msgid "Invalid BigQuery identifier: ''{0}''"
+msgstr "Identifiant BigQuery invalide : \"{0}\""
+
+#: src/metabase/driver/bigquery.clj
+msgid "BigQuery statements can't be parameterized!"
+msgstr "Les ordres BigQuery n'acceptent pas de paramètre !"
+
+#: src/metabase/driver/generic_sql/query_processor.clj
+msgid "Failed to set timezone:"
+msgstr "Echec du réglage du fuseau horaire :"
+
+#: src/metabase/driver/googleanalytics.clj
+msgid "You must enable the Google Analytics API. Use this link to go to the Google Developers Console: {0}"
+msgstr "Vous devez activer l'API Google Analytics. Utilisez ce lien pour vous rendre sur la Google Developers Console : {0}"
+
+#: src/metabase/driver/h2.clj
+msgid "Running SQL queries against H2 databases using the default (admin) database user is forbidden."
+msgstr "Exécuter des requêtes SQL sur des bases de données H2 en utilisant l'utilisateur de base de données par défaut (admin) est interdit."
+
+#: src/metabase/driver/sparksql.clj
+msgid "Error: metabase.driver.FixedHiveDriver is registered, but JDBC does not seem to be using it."
+msgstr "Ereur : metabase.driver.FixedHiveDriver est enregistré, mais JDBC ne semble pas l'utiliser."
+
+#: src/metabase/driver/sparksql.clj
+msgid "Found metabase.driver.FixedHiveDriver."
+msgstr "metabase.driver.FixedHiveDriver trouvé."
+
+#: src/metabase/driver/sparksql.clj
+msgid "Successfully registered metabase.driver.FixedHiveDriver with JDBC."
+msgstr "metabase.driver.FixedHiveDriver enregistré avec succès dans JDBC."
+
+#. CONFIG
+#. TODO - smtp-port should be switched to type :integer
+#: src/metabase/email.clj
+msgid "Email address you want to use as the sender of Metabase."
+msgstr "Adresse électronique que vous souhaitez utiliser comme expéditeur."
+
+#: src/metabase/email.clj
+msgid "The address of the SMTP server that handles your emails."
+msgstr "L'adresse du serveur SMTP qui gère vos courriels."
+
+#: src/metabase/email.clj
+msgid "SMTP username."
+msgstr "Nom d'utilisateur SMTP."
+
+#: src/metabase/email.clj
+msgid "SMTP password."
+msgstr "Mot de passe SMTP."
+
+#: src/metabase/email.clj
+msgid "The port your SMTP server uses for outgoing emails."
+msgstr "Le port que votre serveur SMTP utilise pour les courriels sortants."
+
+#: src/metabase/email.clj
+msgid "SMTP secure connection protocol. (tls, ssl, starttls, or none)"
+msgstr "Protocole de connexion sécurisée SMTP. (tls, ssl, starttls ou aucun)"
+
+#: src/metabase/email.clj
+msgid "none"
+msgstr "aucun"
+
+#: src/metabase/email.clj
+msgid "SMTP host is not set."
+msgstr "L'hôte SMTP n'est pas défini."
+
+#: src/metabase/email.clj
+msgid "Failed to send email"
+msgstr "Impossible d'envoyer le courriel"
+
+#: src/metabase/email.clj
+msgid "Error testing SMTP connection"
+msgstr "Erreur lors du test de la connexion SMTP"
+
+#: src/metabase/integrations/ldap.clj
+msgid "Enable LDAP authentication."
+msgstr "Activer l'authentification LDAP."
+
+#: src/metabase/integrations/ldap.clj
+msgid "Server hostname."
+msgstr "Nom d'hôte du serveur"
+
+#: src/metabase/integrations/ldap.clj
+msgid "Server port, usually 389 or 636 if SSL is used."
+msgstr "port du serveur, habituellement 389, ou 636 si SSL est utilisé."
+
+#: src/metabase/integrations/ldap.clj
+msgid "Use SSL, TLS or plain text."
+msgstr "Utiliser SSL, TLS ou plain texte."
+
+#: src/metabase/integrations/ldap.clj
+msgid "The Distinguished Name to bind as (if any), this user will be used to lookup information about other users."
+msgstr "Le \"Distinguished Name\" avec lequel se lier (si présent), cet utilisateur sera utilisé pour récupérer les informations des autres utilisateurs."
+
+#: src/metabase/integrations/ldap.clj
+msgid "The password to bind with for the lookup user."
+msgstr "Le mot de passe à utiliser pour l'utilisateur de recherche."
+
+#: src/metabase/integrations/ldap.clj
+msgid "Search base for users. (Will be searched recursively)"
+msgstr "Base de recherche pour les utilisateurs. (Sera recherché récursivement)"
+
+#. 'variable' est peut-être plus explicite que 'emplacement réservé' (pour invalides de guerre) ?
+#: src/metabase/integrations/ldap.clj
+msgid "User lookup filter, the placeholder '{login}' will be replaced by the user supplied login."
+msgstr "Filtre de recherche d’utilisateur, la variable '{login}' y sera remplacée par le login fourni par l’utilisateur."
+
+#: src/metabase/integrations/ldap.clj
+msgid "Attribute to use for the user's email. (usually ''mail'', ''email'' or ''userPrincipalName'')"
+msgstr "Attribut correspondant à l'e-mail de l'utilisateur. (le plus souvent \"mail\", \"email\" ou \"userPrincipalName\")"
+
+#: src/metabase/integrations/ldap.clj
+msgid "Attribute to use for the user''s first name. (usually ''givenName'')"
+msgstr "Attribut à utiliser pour le prénom de l'utilisateur."
+
+#: src/metabase/integrations/ldap.clj
+msgid "Attribute to use for the user''s last name. (usually ''sn'')"
+msgstr "Attribut à utiliser pour le nom de famille de l'utilisateur."
+
+#: src/metabase/integrations/ldap.clj
+msgid "Enable group membership synchronization with LDAP."
+msgstr "Activer la synchronisation des appartenances aux groupes entre Metabase et LDAP."
+
+#: src/metabase/integrations/ldap.clj
+msgid "Search base for groups, not required if your LDAP directory provides a ''memberOf'' overlay. (Will be searched recursively)"
+msgstr "Base de recherche pour les groupes, non requis si votre annuaire LDAP fournis la surcouche \"memberOf\". (Recherchera récursivement)"
+
+#. Should be in the form: {"cn=Some Group,dc=...": [1, 2, 3]} where keys are LDAP groups and values are lists of MB groups IDs
+#: src/metabase/integrations/ldap.clj
+msgid "JSON containing LDAP to Metabase group mappings."
+msgstr "JSON contenant les correspondances entre groupes Metabase et LDAP."
+
+#. Define a setting which captures our Slack api token
+#: src/metabase/integrations/slack.clj
+msgid "Slack API bearer token obtained from https://api.slack.com/web#authentication"
+msgstr "Jeton de l'API Slack obtenu à partir de https://api.slack.com/web#authentication"
+
+#: src/metabase/metabot.clj
+msgid "Enable MetaBot, which lets you search for and view your saved questions directly via Slack."
+msgstr "Activer MetaBot, qui vous permet de rechercher et voir vos questions sauvegardées directement via Slack."
+
+#: src/metabase/metabot.clj
+msgid "Last MetaBot checkin was {0} ago."
+msgstr "La dernière vérification MetaBot a eu lieu il y a {0}."
+
+#: src/metabase/metabot.clj
+msgid "This instance will now handle MetaBot duties."
+msgstr "Cette instance gérera désormais les tâches MetaBot."
+
+#: src/metabase/metabot.clj
+msgid "Here''s what I can {0}:"
+msgstr "Voici ce que je peux {0} :"
+
+#: src/metabase/metabot.clj
+msgid "I don''t know how to {0} `{1}`.n{2}"
+msgstr "Je ne sais pas comment {0} `{1}`.n{2}"
+
+#: src/metabase/metabot.clj
+msgid "Uh oh! :cry:n> {0}"
+msgstr "Uh oh! : cry:n> {0}"
+
+#: src/metabase/metabot.clj
+msgid "Here''s your {0} most recent cards:n{1}"
+msgstr "Voici vos {0} plus récentes Cartes : n{1}"
+
+#: src/metabase/metabot.clj
+msgid "Could you be a little more specific? I found these cards with names that matched:n{0}"
+msgstr "Pourriez-vous être un peu plus précis ? J'ai trouvé ces cartes au nom correspondant : {0}"
+
+#: src/metabase/metabot.clj
+msgid "I don''t know what Card `{0}` is. Give me a Card ID or name."
+msgstr "Je ne sais pas ce qu'est la Carte `{0}`. Donnez-moi un ID ou un nom de Carte."
+
+#: src/metabase/metabot.clj
+msgid "Show which card? Give me a part of a card name or its ID and I can show it to you. If you don''t know which card you want, try `metabot list`."
+msgstr "Quelle carte montrer ? Donnez-moi une partie de son nom ou son ID et je peux vous la présenter. Si vous ne savez pas quelle carte vous souhaitez, essayez `metabot list`."
+
+#: src/metabase/metabot.clj
+msgid "Ok, just a second..."
+msgstr "D'accord, une seconde s'il vous plaît..."
+
+#: src/metabase/metabot.clj
+msgid "Not Found"
+msgstr "Recherche infructueuse"
+
+#: src/metabase/metabot.clj
+msgid "Loading Kanye quotes..."
+msgstr "Chargement des citations de Kanye..."
+
+#: src/metabase/metabot.clj
+msgid "Evaluating Metabot command:"
+msgstr "Evaluation de la commande MetaBot :"
+
+#: src/metabase/metabot.clj
+msgid "Go home websocket, you're drunk."
+msgstr "Rentre chez toi websocket, tu es saoûle."
+
+#: src/metabase/metabot.clj
+msgid "Error launching metabot:"
+msgstr "Erreur au lancement de MetaBot :"
+
+#: src/metabase/metabot.clj
+msgid "MetaBot WebSocket is closed. Reconnecting now."
+msgstr "La websocket MetaBot est fermée. Reconnexion en cours."
+
+#: src/metabase/metabot.clj
+msgid "Error connecting websocket:"
+msgstr "Erreur lors de la connexion de la websocket :"
+
+#: src/metabase/metabot.clj
+msgid "This instance is performing MetaBot duties."
+msgstr "Cette instance effectue les tâches MetaBot"
+
+#: src/metabase/metabot.clj
+msgid "Another instance is already handling MetaBot duties."
+msgstr "Une autre instance gère déjà les tâches MetaBot."
+
+#: src/metabase/metabot.clj
+msgid "Starting MetaBot threads..."
+msgstr "Démarrage des fils d'exécution de MetaBot..."
+
+#: src/metabase/metabot.clj
+msgid "Stopping MetaBot...  🤖"
+msgstr "Arrêt de MetaBot... 🤖"
+
+#: src/metabase/metabot.clj
+msgid "MetaBot already running. Killing the previous WebSocket listener first."
+msgstr "MetaBot est déjà en route. Arrêt préliminaire du précédent écouteur de websocket."
+
+#: src/metabase/middleware.clj
+msgid "Base-64 encoded public key for this site's SSL certificate."
+msgstr "Clé publique encodée en base64 du certificat SSL de ce site."
+
+#: src/metabase/middleware.clj
+msgid "Specify this to enable HTTP Public Key Pinning."
+msgstr "Activer l'épinglage des clés publiques HTTP."
+
+#: src/metabase/middleware.clj
+msgid "See {0} for more information."
+msgstr "Consulter {0} pour plus d'information."
+
+#: src/metabase/models/card.clj
+msgid "Cannot save Question: source query has circular references."
+msgstr "Incapable de sauvegarder la Question : la requête source contient des références circulaires."
+
+#: src/metabase/models/card.clj src/metabase/models/query/permissions.clj
+#: src/metabase/query_processor/middleware/permissions.clj
+msgid "Card {0} does not exist."
+msgstr "La Carte {0} n'existe pas."
+
+#: src/metabase/models/card.clj
+msgid "You do not have permissions to run ad-hoc native queries against Database {0}."
+msgstr "Vous n'avez pas les permissions pour exécuter des requêtes natives ad-hoc sur cette base de données {0}."
+
+#: src/metabase/models/collection.clj
+msgid "Invalid color"
+msgstr "Couleur invalide"
+
+#: src/metabase/models/collection.clj
+msgid "must be a valid 6-character hex color code"
+msgstr "doit être un code couleur hexadécimal sur 6 caractères"
+
+#: src/metabase/models/collection.clj
+msgid "Collection name cannot be blank!"
+msgstr "Le nom de collection ne peut être vide !"
+
+#: src/metabase/models/collection.clj
+msgid "cannot be blank"
+msgstr "ne peut être vide"
+
+#: src/metabase/models/collection.clj
+msgid "Invalid Collection location: path is invalid."
+msgstr "Emplacement de collection invalide : le chemin est invalide."
+
+#: src/metabase/models/collection.clj
+msgid "You cannot move a Personal Collection."
+msgstr "Vous ne pouvez déplacer une collection personnelle."
+
+#: src/metabase/models/collection.clj
+msgid "Invalid Collection location: some or all ancestors do not exist."
+msgstr "Emplacement de collection invalide : au moins un ancêtre n'existe pas."
+
+#: src/metabase/models/collection.clj
+msgid "You cannot archive the Root Collection."
+msgstr "Vous ne pouvez archiver la collection racine."
+
+#: src/metabase/models/collection.clj
+msgid "You cannot archive a Personal Collection."
+msgstr "Vous ne pouvez archiver une collection personnelle."
+
+#: src/metabase/models/collection.clj
+msgid "You cannot move the Root Collection."
+msgstr "Vous ne pouvez déplacer la collection racine."
+
+#: src/metabase/models/collection.clj
+msgid "You cannot move a Collection into itself or into one of its descendants."
+msgstr "Vous ne pouvez déplacer une collection vers elle-même ou vers un de ses descendants."
+
+#. first move this Collection
+#: src/metabase/models/collection.clj
+msgid "Moving Collection {0} and its descendants from {1} to {2}"
+msgstr "Déplacement de la collection {0} et de ses descendants depuis {1} vers {2}"
+
+#: src/metabase/models/collection.clj
+msgid "You're not allowed to change the owner of a Personal Collection."
+msgstr "Vous n'êtes pas autorisé à changer le propriétaire d'une collection personnelle."
+
+#: src/metabase/models/collection.clj
+msgid "You're not allowed to move a Personal Collection."
+msgstr "Vous n'êtes pas autorisé à déplacer une collection personnelle."
+
+#: src/metabase/models/collection.clj
+msgid "You cannot move a Collection and archive it at the same time."
+msgstr "Vous ne pouvez en même temps déplacer et archiver une collection."
+
+#: src/metabase/models/collection.clj
+msgid "You cannot delete a Personal Collection!"
+msgstr "Vous ne pouvez supprimer une collection personnelle !"
+
+#: src/metabase/models/collection.clj
+msgid "{0} {1}''s Personal Collection"
+msgstr "Collection personnelle de {0} {1}"
+
+#: src/metabase/models/collection_revision.clj
+msgid "You cannot update a CollectionRevision!"
+msgstr "Vous ne pouvez modifier une Révision de collection !"
+
+#: src/metabase/models/field_values.clj
+msgid "Field {0} was previously automatically set to show a list widget, but now has {1} values."
+msgstr "Le champ {0} était précédemment défini automatiquement pour afficher une liste, mais il contient désormais {1} valeurs."
+
+#: src/metabase/models/field_values.clj
+msgid "Switching Field to use a search widget instead."
+msgstr "Transformation du champ en un élément visuel de recherche."
+
+#: src/metabase/models/field_values.clj
+msgid "Storing updated FieldValues for Field {0}..."
+msgstr "Stockage des valeurs mises à jour du champ {0}..."
+
+#: src/metabase/models/field_values.clj
+msgid "Storing FieldValues for Field {0}..."
+msgstr "Stockage des valeurs du champ {0}..."
+
+#: src/metabase/models/humanization.clj
+msgid "Metabase can attempt to transform your table and field names into more sensible, human-readable versions, e.g. \"somehorriblename\" becomes \"Some Horrible Name\"."
+msgstr "Metabase peut tenter de transformer les noms de table et de champ en une version plus naturelle et lisible; \"unhorriblenom\" deviendrait par exemple \"Un Horrible Nom\"."
+
+#: src/metabase/models/humanization.clj
+msgid "This doesn’t work all that well if the names are in a language other than English, however."
+msgstr "Cela ne donnera de bon résultats que pour des noms en anglais."
+
+#: src/metabase/models/humanization.clj
+msgid "Do you want us to take a guess?"
+msgstr "Voulez-vous que nous l'estimions ?"
+
+#: src/metabase/models/permissions.clj
+msgid "You cannot create or revoke permissions for the 'Admin' group."
+msgstr "Vous ne pouvez accorder ou retirer des permissions au groupe 'Admin'."
+
+#: src/metabase/models/permissions.clj
+msgid "Invalid permissions object path: ''{0}''."
+msgstr "Chemin d'objet invalide : \"{0}\"."
+
+#: src/metabase/models/permissions.clj
+msgid "You cannot update a permissions entry!"
+msgstr "Vous ne pouvez pas mettre à jour une entrée de permissions !"
+
+#: src/metabase/models/permissions.clj
+msgid "Delete it and create a new one."
+msgstr "Supprimez la et créez-en une nouvelle."
+
+#: src/metabase/models/permissions.clj
+msgid "You cannot edit permissions for a Personal Collection or its descendants."
+msgstr "Vous ne pouvez modifier les permissions d'une collection personnelle ou de ses descendants."
+
+#: src/metabase/models/permissions.clj
+msgid "Looks like someone else edited the permissions and your data is out of date."
+msgstr "Il semble que quelqu'un d'autre ait modifié les permissions et que vos données soient obsolètes."
+
+#: src/metabase/models/permissions.clj
+msgid "Please fetch new data and try again."
+msgstr "Récupérez s'il vous plaît de nouvelles données et essayez à nouveau."
+
+#: src/metabase/models/permissions_group.clj
+msgid "Created magic permissions group ''{0}'' (ID = {1})"
+msgstr "Groupe de permissions magique \"{0}\" (ID = {1}) créé"
+
+#: src/metabase/models/permissions_group.clj
+msgid "A group with that name already exists."
+msgstr "Un groupe avec ce nom existe déjà."
+
+#: src/metabase/models/permissions_group.clj
+msgid "You cannot edit or delete the ''{0}'' permissions group!"
+msgstr "Vous ne pouvez pas modifier ou supprimer le groupe d'autorisations '' {0} ''!"
+
+#: src/metabase/models/permissions_group_membership.clj
+msgid "You cannot add or remove users to/from the 'MetaBot' group."
+msgstr "Vous ne pouvez pas ajouter (resp. supprimer) d'utilisateur au (resp. du) groupe 'MetaBot'."
+
+#: src/metabase/models/permissions_group_membership.clj
+msgid "You cannot add or remove users to/from the 'All Users' group."
+msgstr "Vous ne pouvez pas ajouter ou supprimer des utilisateurs du groupe \"Tous les utilisateurs\"."
+
+#: src/metabase/models/permissions_group_membership.clj
+msgid "You cannot remove the last member of the 'Admin' group!"
+msgstr "Vous ne pouvez pas supprimer le dernier membre du groupe 'Admin' !"
+
+#: src/metabase/models/permissions_revision.clj
+msgid "You cannot update a PermissionsRevision!"
+msgstr "Vous ne pouvez mettre à jour une révision de permissions"
+
+#. if there's still not a Card, throw an Exception!
+#: src/metabase/models/pulse.clj
+msgid "Invalid Alert: Alert does not have a Card assoicated with it"
+msgstr "Alerte invalide : l'alerte n'a aucune question d'associée"
+
+#: src/metabase/models/pulse.clj
+msgid "value must be a map with the keys `{0}`, `{1}`, and `{2}`."
+msgstr "La valeur doit être une association contenant les clés `{0}`, `{1}`, et `{2}`."
+
+#: src/metabase/models/pulse.clj
+msgid "value must be a map with the following keys `({0})`"
+msgstr "La valeur doit être une association contenant les clés `({0})`"
+
+#: src/metabase/models/query/permissions.clj
+msgid "Error calculating permissions for query: {0}"
+msgstr "Erreur lors du calcul des permissions de la requête : {0}"
+
+#: src/metabase/models/query/permissions.clj
+msgid "Invalid query type: {0}"
+msgstr "Type de requête invalide : {0}"
+
+#: src/metabase/models/query_execution.clj
+msgid "You cannot update a QueryExecution!"
+msgstr "Vous ne pouvez mettre à jour une exécution de requête !"
+
+#: src/metabase/models/revision.clj
+msgid "You cannot update a Revision!"
+msgstr "Vous ne pouvez mettre à jour une révision !"
+
+#: src/metabase/models/setting.clj
+msgid "Setting {0} does not exist.nFound: {1}"
+msgstr "Le réglage {0} n'existe pas. nRéglage trouvé : {1}"
+
+#: src/metabase/models/setting.clj
+msgid "Updating value of settings-last-updated in DB..."
+msgstr "Mise à jour de la valeur de `settings-last-updated` en base de données..."
+
+#: src/metabase/models/setting.clj
+msgid "Checking whether settings cache is out of date (requires DB call)..."
+msgstr "Vérification de la péremption du cache des réglages (nécessite un appel à la base de données)..."
+
+#: src/metabase/models/setting.clj
+msgid "Settings have been changed on another instance, and will be reloaded here."
+msgstr "Les réglages ont été changés sur une autre instance, et seront rechargés sur celle-ci."
+
+#: src/metabase/models/setting.clj
+msgid "Refreshing Settings cache..."
+msgstr "Rafraîchissement du cache des réglages..."
+
+#: src/metabase/models/setting.clj
+msgid "Invalid value for string: must be either \"true\" or \"false\" (case-insensitive)."
+msgstr "Représentation d'un valeur booléenne invalide : seules \"true\" ou \"false\" sont autorisées (et insensible à la casse)."
+
+#: src/metabase/models/setting.clj
+msgid "You cannot update `settings-last-updated` yourself! This is done automatically."
+msgstr "Vous ne pouvez mettre à jour `settings-last-updated` vous-même ! C'est fait automatiquement."
+
+#. go ahead and log the Exception anyway on the off chance that it *wasn't* just a race condition issue
+#: src/metabase/models/setting.clj
+msgid "Error inserting a new Setting:"
+msgstr "Erreur à l'insertion d'un nouveau réglage :"
+
+#: src/metabase/models/setting.clj
+msgid "Assuming Setting already exists in DB and updating existing value."
+msgstr "Mise à jour d'une valeur existante en présupposant que le réglage existe déjà en base de données."
+
+#: src/metabase/models/user.clj
+msgid "value must be a map with each value either a string or number."
+msgstr "la valeur doit être une association aux valeurs de type chaîne de caractère ou numérique."
+
+#: src/metabase/plugins.clj
+msgid "Loading plugins in directory {0}..."
+msgstr "Chargement des plugins du répertoire {0}..."
+
+#: src/metabase/plugins.clj
+msgid "Loading plugin {0}... "
+msgstr "Chargement du plugin {0}..."
+
+#: src/metabase/plugins.clj
+msgid "It looks like you have some external dependencies in your Metabase plugins directory."
+msgstr "Il semble que vous ayez des dépendances externes dans le répertoire de vos plugins Metabase."
+
+#: src/metabase/plugins.clj
+msgid "With Java 9 or higher, Metabase cannot automatically add them to your classpath."
+msgstr "Depuis Java 9, Metabase peut automatiquement les ajouter à votre classpath."
+
+#: src/metabase/plugins.clj
+msgid "Instead, you should include them at launch with the -cp option. For example:"
+msgstr "Vous devriez plutôt les inclure au lancement avec l'option -cp. Par exemple :"
+
+#: src/metabase/plugins.clj
+msgid "See https://metabase.com/docs/latest/operations-guide/start.html#java-versions for more details."
+msgstr "Consulter https://metabase.com/docs/latest/operations-guide/start.html#java-versions pour plus de détails."
+
+#: src/metabase/plugins.clj
+msgid "(If you're already running Metabase this way, you can ignore this message.)"
+msgstr "(Si vous faites déjà tourner Metabase de cette façon, vous pouvez ignorer ce message.)"
+
+#: src/metabase/public_settings.clj
+msgid "Identify when new versions of Metabase are available."
+msgstr "Identifie quand de nouvelles versions de Metabase sont disponibles."
+
+#: src/metabase/public_settings.clj
+msgid "Information about available versions of Metabase."
+msgstr "Information sur les versions de Metabase disponibles."
+
+#: src/metabase/public_settings.clj
+msgid "The name used for this instance of Metabase."
+msgstr "Le nom utilisé pour cette instance de Metabase."
+
+#: src/metabase/public_settings.clj
+msgid "The base URL of this Metabase instance, e.g. \"http://metabase.my-company.com\"."
+msgstr "L'URL de base de cette instance de Metabase, par exemple : \"http://metabase.mon-entreprise.com\"."
+
+#: src/metabase/public_settings.clj
+msgid "The default language for this Metabase instance."
+msgstr "Le langage par défaut de cette instance de Metabase."
+
+#: src/metabase/public_settings.clj
+msgid "This only applies to emails, Pulses, etc. Users'' browsers will specify the language used in the user interface."
+msgstr "S'applique uniquement aux courriels, Pulses, etc. La langue du navigateur de l'utilisateur est utilisée pour afficher l'interface web."
+
+#: src/metabase/public_settings.clj
+msgid "The email address users should be referred to if they encounter a problem."
+msgstr "Adresse électronique que les utilisateurs contacteront s'ils rencontrent un problème."
+
+#: src/metabase/public_settings.clj
+msgid "Enable the collection of anonymous usage data in order to help Metabase improve."
+msgstr "Activer la collecte de données d'utilisation anonymes nous permettant d'améliorer Metabase."
+
+#: src/metabase/public_settings.clj
+msgid "The map tile server URL template used in map visualizations, for example from OpenStreetMaps or MapBox."
+msgstr "Le patron d'URL du serveur de tuiles de cartes utilisé dans les visualisations de carte, par exemple depuis OpenStreetMap ou MapBox."
+
+#: src/metabase/public_settings.clj
+msgid "Enable admins to create publicly viewable links (and embeddable iframes) for Questions and Dashboards?"
+msgstr "Activer la création - par les administrateurs - de liens publics (et d'iframes intégrées) vers les questions et les tableaux de bord ?"
+
+#: src/metabase/public_settings.clj
+msgid "Allow admins to securely embed questions and dashboards within other applications?"
+msgstr "Autoriser l'intégration sécurisée - par les administrateurs - des questions et tableaux de bord dans d'autres applications ?"
+
+#: src/metabase/public_settings.clj
+msgid "Allow using a saved question as the source for other queries?"
+msgstr "Autoriser l'utilisation d'une question sauvegardée comme source d'autres questions ?"
+
+#: src/metabase/public_settings.clj
+msgid "Enabling caching will save the results of queries that take a long time to run."
+msgstr "Activer le cache sauvegardera les résultats des requêtes qui prennent longtemps à s'exécuter."
+
+#: src/metabase/public_settings.clj
+msgid "The maximum size of the cache, per saved question, in kilobytes:"
+msgstr "La taille maximum du cache, par question sauvegardée, en Ko :"
+
+#: src/metabase/public_settings.clj
+msgid "The absolute maximum time to keep any cached query results, in seconds."
+msgstr "La durée maximum absolue de conservation en cache des résultats de requête, en secondes."
+
+#: src/metabase/public_settings.clj
+msgid "Metabase will cache all saved questions with an average query execution time longer than this many seconds:"
+msgstr "Metabase mettra en cache toutes les questions sauvegardées qui ont une durée d'exécution moyenne plus longue que ce nombre de secondes :"
+
+#: src/metabase/public_settings.clj
+msgid "To determine how long each saved question''s cached result should stick around, we take the query''s average execution time and multiply that by whatever you input here."
+msgstr "Pour déterminer la durée de conservation du résultat d'une question sauvegardée, nous pondérons sa durée moyenne d'exécution par le coefficient multiplicateur que vous saisissez ici."
+
+#: src/metabase/public_settings.clj
+msgid "So if a query takes on average 2 minutes to run, and you input 10 for your multiplier, its cache entry will persist for 20 minutes."
+msgstr "Ainsi avec un coefficient 10, le résultat d'une requête prenant en moyenne 2 minutes à s'exécuter persistera en cache pendant 20 minutes."
+
+#: src/metabase/public_settings.clj
+msgid "When using the default binning strategy and a number of bins is not provided, this number will be used as the default."
+msgstr "Valeur par défaut du nombre de cellules (bins) pour le binnage."
+
+#: src/metabase/public_settings.clj
+msgid "When using the default binning strategy for a field of type Coordinate (such as Latitude and Longitude), this number will be used as the default bin width (in degrees)."
+msgstr "Valeur par défaut en degrés du côté d'une cellule (bin) pour le binnage d'un champ de type Coordonnées (tels que Latitude ou Longitude)."
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Unable to validate token."
+msgstr "Impossible de valider le jeton."
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Error fetching token status:"
+msgstr "Ereur à la récupération du statut du jeton :"
+
+#: src/metabase/public_settings/metastore.clj
+msgid "There was an error checking whether this token was valid."
+msgstr "Il y a eu une erreur à la vérification de la validité du jeton."
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Token validation timed out."
+msgstr "Dépassement de la durée maximum fixée pour la validation du jeton."
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Invalid token: token isn't in the right format."
+msgstr "Jeton invalide : le jeton n'est pas dans le bon format."
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Checking with the MetaStore to see whether {0} is valid..."
+msgstr "Vérification dans le MetaStore de la validité de {0}..."
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Token for premium embedding. Go to the MetaStore to get yours!"
+msgstr "Jeton pour l'intégration Premium. Rendez-vous dans le MetaStore pour obtenir le votre !"
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Token is valid."
+msgstr "Le jeton est valide."
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Error setting premium embedding token"
+msgstr "Erreur au réglage du jeton d'intégration Premium."
+
+#: src/metabase/pulse.clj
+msgid "Unable to compare results to goal for alert."
+msgstr "Dans l'incapacité de comparer les résultats à l'objectif pour générer une alerte."
+
+#: src/metabase/pulse.clj
+msgid "Question ID is ''{0}'' with visualization settings ''{1}''"
+msgstr "L'ID de question est \"{0}\" avec les réglages de Visualisation \"{1}\""
+
+#: src/metabase/pulse.clj
+msgid "Unrecognized alert with condition ''{0}''"
+msgstr "Alerte non reconnue de condition \"{0}\""
+
+#: src/metabase/pulse.clj
+msgid "Unrecognized channel type {0}"
+msgstr "Type de canal {0} non reconnu"
+
+#: src/metabase/pulse.clj
+msgid "Error sending notification!"
+msgstr "Erreur à l'envoi de la notification !"
+
+#: src/metabase/pulse/color.clj
+msgid "Can't find JS color selector at ''{0}''"
+msgstr "Dans l'incapacité de trouver le sélecteur de couleur JS à \"{0}\""
+
+#: src/metabase/pulse/render.clj
+msgid "Card has errors: {0}"
+msgstr "Questions en erreur : {0}"
+
+#: src/metabase/pulse/render.clj
+msgid "Pulse card render error"
+msgstr "Erreur au rendu de la carte de Pulse"
+
+#: src/metabase/query_processor/middleware/fetch_source_query.clj
+msgid "Trimming trailing comment from card with id {0}"
+msgstr "Découpage du commentaire final de la question d'ID {0}"
+
+#: src/metabase/query_processor/middleware/parameters/sql.clj
+msgid "Can't find field with ID: {0}"
+msgstr "Dans l'incapacité de trouver le champ d'ID : {0}"
+
+#: src/metabase/query_processor/middleware/parameters/sql.clj
+msgid "''{0}'' is a required param."
+msgstr "\"{0}\" est un paramètre obligatoire."
+
+#: src/metabase/query_processor/middleware/parameters/sql.clj
+msgid "Found ''{0}'' with no terminating ''{1}'' in query ''{2}''"
+msgstr "Identification d'un \"{0}\" sans terminaison \"{1}\" dans la requête \"{2}\""
+
+#: src/metabase/query_processor/middleware/parameters/sql.clj
+msgid "Unable to substitute ''{0}'': param not specified.nFound: {1}"
+msgstr "Dans l'incapacité de substituer \"{0}\" : paramètre non spécifié. nValeur trouvée : {1}"
+
+#: src/metabase/query_processor/middleware/permissions.clj
+msgid "You do not have permissions to view Card {0}."
+msgstr "Vous n'êtes pas autorisé à afficher la question {0}."
+
+#: src/metabase/query_processor/middleware/permissions.clj
+msgid "You do not have permissions to run this query."
+msgstr "Vous n'êtes pas autorisé à exécuter cette requête."
+
+#: src/metabase/sync/analyze.clj
+msgid "Fingerprint updates attempted {0}, updated {1}, no data found {2}, failed {3}"
+msgstr "{0} mise(s) à jour d'empreintes tentée(s), {1} mise(s) à jour effective(s), {2} sans donnée correspondante, {3} en échec"
+
+#: src/metabase/sync/analyze.clj
+msgid "Total number of fields classified {0}, {1} failed"
+msgstr "{0} champs classifié(s), {1} échec(s)"
+
+#: src/metabase/sync/analyze.clj
+msgid "Total number of tables classified {0}, {1} updated"
+msgstr "Nombre total de tables classifiées {0}, {1} mises à jour"
+
+#: src/metabase/sync/analyze/fingerprint/fingerprinters.clj
+msgid "Error generating fingerprint for {0}"
+msgstr "Erreur à la génération de l'empreinte pour {0}"
+
+#: src/metabase/sync/field_values.clj
+msgid "Updated {0} field value sets, created {1}, deleted {2} with {3} errors"
+msgstr "{0} ensemble de valeurs mis à jour, {1} crée(s), {2} supprimée(s), dont {3} en erreur"
+
+#: src/metabase/sync/sync_metadata.clj
+msgid "Total number of fields sync''d {0}, number of fields updated {1}"
+msgstr "{0} champ(s) synchronisé(s), {1} champ(s) mis à jour"
+
+#: src/metabase/sync/sync_metadata.clj
+msgid "Total number of tables sync''d {0}, number of tables updated {1}"
+msgstr "{0} table(s) synchronisée(s), {1} table(s) mise(s) à jour"
+
+#: src/metabase/sync/sync_metadata.clj
+msgid "Found timezone id {0}"
+msgstr "Identification d'un fuseau horaire d' ID {0}"
+
+#: src/metabase/sync/sync_metadata.clj
+msgid "Total number of foreign keys sync''d {0}, {1} updated and {2} tables failed to update"
+msgstr "{0} clé(s) étrangère(s) synchronisée(s), {1} clé(s) étrangère(s) mise(s) à jour et {2} table(s) non mises à jour à cause d'un erreur"
+
+#: src/metabase/sync/util.clj
+msgid "{0} Database {1} ''{2}''"
+msgstr "{0} Base de données {1} ''{2}''"
+
+#: src/metabase/sync/util.clj
+msgid "Table {0} ''{1}''"
+msgstr "Table {0} ''{1}''"
+
+#: src/metabase/sync/util.clj
+msgid "Field {0} ''{1}''"
+msgstr "Champ {0} ''{1}''"
+
+#: src/metabase/sync/util.clj
+msgid "Field ''{0}''"
+msgstr "Champ '' {0} ''"
+
+#: src/metabase/sync/util.clj
+msgid "step ''{0}'' for {1}"
+msgstr "étape ''{0}'' pour {1}"
+
+#: src/metabase/sync/util.clj
+msgid "Completed {0} on {1}"
+msgstr "Terminé {0} le {1}"
+
+#: src/metabase/sync/util.clj
+msgid "Start: {0}"
+msgstr "Début : {0}"
+
+#: src/metabase/sync/util.clj
+msgid "End: {0}"
+msgstr "Fin : {0}"
+
+#: src/metabase/sync/util.clj
+msgid "Duration: {0}"
+msgstr "Durée : {0}"
+
+#: src/metabase/sync/util.clj
+msgid "Completed step ''{0}''"
+msgstr "Etape \"{0}\" effectuée"
+
+#: src/metabase/task.clj
+msgid "Loading tasks namespace:"
+msgstr "Chargement de l'espace de nommage des tâches :"
+
+#: src/metabase/task.clj
+msgid "Starting Quartz Scheduler"
+msgstr "Démarrage du planificateur Quartz"
+
+#: src/metabase/task.clj
+msgid "Stopping Quartz Scheduler"
+msgstr "Arrêt du planificateur Quartz"
+
+#: src/metabase/task.clj
+msgid "Job already exists:"
+msgstr "Le job existe déjà :"
+
+#. 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
+#: src/metabase/util.clj
+msgid "Loading Metabase..."
+msgstr "Chargement de Metabase..."
+
+#: src/metabase/util/date.clj
+msgid "Possible timezone conflict found on database {0}."
+msgstr "Possible conflit de fuseau horaire dans la base de données {0}."
+
+#: src/metabase/util/date.clj
+msgid "JVM timezone is {0} and detected database timezone is {1}."
+msgstr "Le fuseau horaire de la JVM est {0} et celui détecté dans la base de données est {1}."
+
+#: src/metabase/util/date.clj
+msgid "Configure a report timezone to ensure proper date and time conversions."
+msgstr "Configurer un fuseau horaire de restitution pour s'assurer d'une conversion correcte des dates et heures."
+
+#: src/metabase/util/embed.clj
+msgid "Secret key used to sign JSON Web Tokens for requests to `/api/embed` endpoints."
+msgstr "Clé secrète utilisée pour signer les \"JSON Web Tokens\" dans les requêtes aux endpoints `/api/embed`."
+
+#: src/metabase/util/encryption.clj
+msgid "MB_ENCRYPTION_SECRET_KEY must be at least 16 characters."
+msgstr "MB_ENCRYPTION_SECRET_KEY doit comporter au moins 16 caractères."
+
+#: src/metabase/util/encryption.clj
+msgid "Saved credentials encryption is ENABLED for this Metabase instance."
+msgstr "Le chiffrement des informations d'identification enregistrées est ACTIVÉ pour cette instance de Metabase."
+
+#: src/metabase/util/encryption.clj
+msgid "Saved credentials encryption is DISABLED for this Metabase instance."
+msgstr "Le chiffrement des informations d'identification enregistrées est DÉSACTIVÉ pour cette instance de Metabase."
+
+#: src/metabase/util/encryption.clj
+msgid "nFor more information, see"
+msgstr "nPour plus d'informations, voir"
+
+#: src/metabase/util/schema.clj
+msgid "value must be an integer."
+msgstr "la valeur doit être un integer."
+
+#: src/metabase/util/schema.clj
+msgid "value must be a string."
+msgstr "la valeur doit être une chaîne de caractères. "
+
+#: src/metabase/util/schema.clj
+msgid "value must be a boolean."
+msgstr "la valeur doit être un booléen."
+
+#: src/metabase/util/schema.clj
+msgid "value must be a string that matches the regex `{0}`."
+msgstr "la valeur doit être une chaîne de caractères qui correspond à l'expression régulière {0}."
+
+#: src/metabase/util/schema.clj
+msgid "value must satisfy one of the following requirements: "
+msgstr "la valeur doit satisfaire une des exigences suivantes : "
+
+#: src/metabase/util/schema.clj
+msgid "value may be nil, or if non-nil, {0}"
+msgstr "la valeur peut être nil, ou si non-nil, {0}"
+
+#: src/metabase/util/schema.clj
+msgid "value must be one of: {0}."
+msgstr "les valeurs possibles sont : {0}."
+
+#: src/metabase/util/schema.clj
+msgid "value must be an array."
+msgstr "la valeur doit être un tableau."
+
+#: src/metabase/util/schema.clj
+msgid "Each {0}"
+msgstr "Chaque {0}"
+
+#: src/metabase/util/schema.clj
+msgid "The array cannot be empty."
+msgstr "Le tableau ne peut être vide."
+
+#: src/metabase/util/schema.clj
+msgid "value must be a non-blank string."
+msgstr "la valeur doit être une chaîne de caractères non vide."
+
+#: src/metabase/util/schema.clj
+msgid "Integer greater than zero"
+msgstr "Un entier supérieur à zéro"
+
+#: src/metabase/util/schema.clj
+msgid "value must be an integer greater than zero."
+msgstr "la valeur doit être un entier supérieur à zéro."
+
+#: src/metabase/util/schema.clj
+msgid "Number greater than zero"
+msgstr "Un nombre supérieur à zéro"
+
+#: src/metabase/util/schema.clj
+msgid "value must be a number greater than zero."
+msgstr "la valeur doit être un nombre supérieur à zéro."
+
+#: src/metabase/util/schema.clj
+msgid "Keyword or string"
+msgstr "Mot clé ou chaîne de caractères"
+
+#: src/metabase/util/schema.clj
+msgid "Valid field type"
+msgstr "type de champ valide"
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid field type."
+msgstr "la valeur doit être un type de champ valide."
+
+#: src/metabase/util/schema.clj
+msgid "Valid field type (keyword or string)"
+msgstr "Type de champ valide (mot clé ou chaîne de caractères)"
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid field type (keyword or string)."
+msgstr "la valeur doit être un type de champ valide (mot clé ou chaîne de caractères)."
+
+#: src/metabase/util/schema.clj
+msgid "Valid entity type (keyword or string)"
+msgstr "type d'entité valide (mot clé ou chaîne de caractères)"
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid entity type (keyword or string)."
+msgstr "la valeur doit être un type d'entité valide (mot clé ou chaîne de caractères)."
+
+#: src/metabase/util/schema.clj
+msgid "Valid map"
+msgstr "Association valide"
+
+#: src/metabase/util/schema.clj
+msgid "value must be a map."
+msgstr "la valeur doit être une association."
+
+#: src/metabase/util/schema.clj
+msgid "Valid email address"
+msgstr "Adresse électronique valide"
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid email address."
+msgstr "cette valeur doit être une adresse électronique valide."
+
+#: src/metabase/util/schema.clj
+msgid "Insufficient password strength"
+msgstr "Force de mot de passe insuffisante"
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid integer."
+msgstr "la valeur doit être un entier."
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid integer greater than zero."
+msgstr "la valeur doit être un entier supérieur à zéro."
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid boolean string (''true'' or ''false'')."
+msgstr "la valeur doit être un booléen représenté par \"true\" ou \"false\"."
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid JSON string."
+msgstr "la valeur doit être du JSON valide."
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid embedding params map."
+msgstr "la valeur doit être une association de paramètres d'intégration valide."
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsTabs.jsx:12
+msgid "Data permissions"
+msgstr "Permissions sur les données"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsTabs.jsx:13
+msgid "Collection permissions"
+msgstr "Permissions sur la collection"
+
+#: frontend/src/metabase/admin/permissions/containers/CollectionPermissionsModal.jsx:56
+msgid "See all collection permissions"
+msgstr "Voir toutes les permissions sur la collection"
+
+#: frontend/src/metabase/admin/permissions/containers/TogglePropagateAction.jsx:25
+msgid "Also change sub-collections"
+msgstr "Changer aussi les sous-collections"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:278
+msgid "Can edit this collection and its contents"
+msgstr "Peut modifier cette collection et son contenu"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:285
+msgid "Can view items in this collection"
+msgstr "Peut voir les items de cette collection"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:745
+msgid "Collection Access"
+msgstr "Accès à la collection"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:821
+msgid "This group has permission to view at least one subcollection of this collection."
+msgstr "Ce groupe a la permission de voir au moins une sous-collection de cette collection."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:826
+msgid "This group has permission to edit at least one subcollection of this collection."
+msgstr "Ce groupe a le droit de modifier au moins une sous-collection de cette colleciton."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:839
+msgid "View sub-collections"
+msgstr "Voir les sous-collections"
+
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:211
+msgid "Remember Me"
+msgstr "Se souvenir de moi"
+
+#: frontend/src/metabase/components/BrowseApp.jsx:118
+msgid "X-ray this schema"
+msgstr "Radiographier ce schéma"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:246
+msgid "Edit the permissions for this collection"
+msgstr "Modifier les permissions sur cette collection"
+
+#: frontend/src/metabase/containers/AddToDashSelectDashModal.jsx:51
+msgid "Add this question to a dashboard"
+msgstr "Ajouter cette question au tableau de bord"
+
+#: frontend/src/metabase/containers/AddToDashSelectDashModal.jsx:61
+msgid "Create a new dashboard"
+msgstr "Créer un nouveau tableau de bord"
+
+#: frontend/src/metabase/containers/ErrorPages.jsx:45
+msgid "The page you asked for couldn't be found."
+msgstr "La page que vous avez demandé n'a pas pu être trouvée."
+
+#: frontend/src/metabase/containers/ItemSelect.jsx:30
+msgid "Select a {0}"
+msgstr "Sélectionner un(e) {0}"
+
+#: frontend/src/metabase/containers/Overworld.jsx:188
+msgid "Save dashboards, questions, and collections in \"{0}\""
+msgstr "Sauvegarder les tableaux de bord, questions et collections de \"{0}\""
+
+#: frontend/src/metabase/containers/Overworld.jsx:191
+msgid "Access dashboards, questions, and collections in \"{0}\""
+msgstr "Accéder aux tableaux de bord, questions et collections de \"{0}\""
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:216
+msgid "Compare"
+msgstr "Comparer"
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:224
+msgid "Zoom out"
+msgstr "Voir moins"
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:228
+msgid "Related"
+msgstr "En lien"
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:288
+msgid "More X-rays"
+msgstr "Plus de radiographies"
+
+#: frontend/src/metabase/home/containers/SearchApp.jsx:50
+msgid "No results"
+msgstr "Pas de résultats"
+
+#: frontend/src/metabase/home/containers/SearchApp.jsx:51
+msgid "Metabase couldn't find any results for your search."
+msgstr "Metabase n'a trouvé aucun résultat à votre recherche."
+
+#: frontend/src/metabase/new_query/containers/MetricSearch.jsx:111
+msgid "No metrics"
+msgstr "Aucune métrique"
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:31
+msgid "Aggregations"
+msgstr "Agrégations"
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:32
+msgid "Operators"
+msgstr "Opérateurs"
+
+#: frontend/src/metabase/query_builder/components/expressions/Expressions.jsx:30
+msgid "Custom fields"
+msgstr "Champs personnalisés"
+
+#. 2. Create the new collections.
+#: src/metabase/db/migrations.clj
+msgid "Migrated Dashboards"
+msgstr "Tableaux de bord migrés"
+
+#: src/metabase/db/migrations.clj
+msgid "Migrated Pulses"
+msgstr "Pulses migrés"
+
+#: src/metabase/db/migrations.clj
+msgid "Migrated Questions"
+msgstr "Questions migrées"
+
+#. 4. move everything not in this Collection to a new Collection
+#: src/metabase/db/migrations.clj
+msgid "Moving instances of {0} that aren't in a Collection to {1} Collection {2}"
+msgstr "Déplacement des instances de {0} qui ne sont pas dans une collection vers {1} collection {2}"
+
+#: src/metabase/models/permissions.clj
+msgid "Failed to grant permissions: {0}"
+msgstr "Echec de l'attribution de permissions : {0}"
+
+#: src/metabase/util/encryption.clj
+msgid "Cannot decrypt encrypted string. Have you changed or forgot to set MB_ENCRYPTION_SECRET_KEY?"
+msgstr "Impossible de déchiffrer la chaîne chiffrée. Avez-vous changé ou oublié de régler MB_ENCRIPTION_SECRET_KEY ?"
+
+#: frontend/src/metabase/entities/collections.js:157
+msgid "All personal collections"
+msgstr "Toutes les collections personnelles"
+
+#: src/metabase/driver.clj
+msgid "Host"
+msgstr "Hôte"
+
+#: src/metabase/driver.clj
+msgid "Port"
+msgstr "Port"
+
+#: src/metabase/driver.clj
+msgid "Database username"
+msgstr "Nom utilisateur de la base de données"
+
+#: src/metabase/driver.clj
+msgid "What username do you use to login to the database?"
+msgstr "Quel nom d'utilisateur utilisez-vous pour vous connecter à la base de données?"
+
+#: src/metabase/driver.clj
+msgid "Database password"
+msgstr "Mot de passe de la base de données"
+
+#: src/metabase/driver.clj
+msgid "Database name"
+msgstr "Nom de la base de données"
+
+#: src/metabase/driver.clj
+msgid "birds_of_the_world"
+msgstr "birds_of_the_world"
+
+#: src/metabase/driver.clj
+msgid "Use a secure connection (SSL)?"
+msgstr "Utiliser une connexion sécurisée (SSL) ?"
+
+#: src/metabase/driver.clj
+msgid "Additional JDBC connection string options"
+msgstr "Options additionnelles de la chaîne de connexion JDBC"
+
+#: src/metabase/driver/bigquery.clj
+msgid "Project ID"
+msgstr "Project ID"
+
+#: src/metabase/driver/bigquery.clj
+msgid "praxis-beacon-120871"
+msgstr " praxis-beacon-120871"
+
+#: src/metabase/driver/bigquery.clj
+msgid "Dataset ID"
+msgstr "Dataset ID"
+
+#: src/metabase/driver/bigquery.clj
+msgid "toucanSightings"
+msgstr "\"toucanSightings\""
+
+#: src/metabase/driver/bigquery.clj src/metabase/driver/googleanalytics.clj
+msgid "Client ID"
+msgstr "ID client"
+
+#: src/metabase/driver/bigquery.clj src/metabase/driver/googleanalytics.clj
+msgid "Client Secret"
+msgstr "Client Secret"
+
+#: src/metabase/driver/bigquery.clj src/metabase/driver/googleanalytics.clj
+msgid "Auth Code"
+msgstr "Auth Code"
+
+#: src/metabase/driver/crate.clj
+msgid "Hosts"
+msgstr "Hôtes"
+
+#: src/metabase/driver/druid.clj
+msgid "Broker node port"
+msgstr "port du nœud de courtage (broker)"
+
+#: src/metabase/driver/googleanalytics.clj
+msgid "Google Analytics Account ID"
+msgstr "ID du compte Google Analytics"
+
+#: src/metabase/driver/h2.clj
+msgid "Connection String"
+msgstr "Chaîne de connexion"
+
+#: src/metabase/driver/h2.clj
+msgid "Users/camsaul/bird_sightings/toucans"
+msgstr "\"Users/camsaul/bird_sightings/toucans\""
+
+#: src/metabase/driver/mongo.clj
+msgid "carrierPigeonDeliveries"
+msgstr "\"carrierPigeonDeliveries\""
+
+#: src/metabase/driver/mongo.clj
+msgid "Authentication Database"
+msgstr "Base de données d'authentification"
+
+#: src/metabase/driver/mongo.clj
+msgid "Optional database to use when authenticating"
+msgstr "Base de données optionnelle à utiliser lors de l'authentificaiton"
+
+#: src/metabase/driver/mongo.clj
+msgid "Additional Mongo connection string options"
+msgstr "Options additionnelles de la chaîne de connexion MongoDB"
+
+#: src/metabase/driver/oracle.clj
+msgid "Oracle system ID (SID)"
+msgstr "ID Oracle (SID)"
+
+#: src/metabase/driver/oracle.clj
+msgid "Usually something like ORCL or XE."
+msgstr "Généralement quelque chose comme ORCL ou XE"
+
+#: src/metabase/driver/oracle.clj
+msgid "Optional if using service name"
+msgstr "Optionnel si vous utilisez un nom de service"
+
+#: src/metabase/driver/oracle.clj
+msgid "Oracle service name"
+msgstr "Nom de service Oracle"
+
+#: src/metabase/driver/oracle.clj
+msgid "Optional TNS alias"
+msgstr "Alias TNS optionnel"
+
+#: src/metabase/driver/presto.clj
+msgid "hive"
+msgstr "hive"
+
+#: src/metabase/driver/redshift.clj
+msgid "my-cluster-name.abcd1234.us-east-1.redshift.amazonaws.com"
+msgstr "my-cluster-name.abcd1234.us-east-1.redshift.amazonaws.com"
+
+#: src/metabase/driver/redshift.clj
+msgid "toucan_sightings"
+msgstr "toucan_sightings"
+
+#: src/metabase/driver/sparksql.clj
+msgid "default"
+msgstr "défaut"
+
+#: src/metabase/driver/sqlite.clj
+msgid "Filename"
+msgstr "Nom de fichier"
+
+#: src/metabase/driver/sqlite.clj
+msgid "/home/camsaul/toucan_sightings.sqlite 😋"
+msgstr " /home/camsaul/toucan_sightings.sqlite 😋"
+
+#: src/metabase/driver/sqlserver.clj
+msgid "BirdsOfTheWorld"
+msgstr "BirdsOfTheWorld"
+
+#: src/metabase/driver/sqlserver.clj
+msgid "Database instance name"
+msgstr "Nom de l'instance de base de données"
+
+#: src/metabase/driver/sqlserver.clj
+msgid "N/A"
+msgstr "N/A"
+
+#: src/metabase/driver/sqlserver.clj
+msgid "Windows domain"
+msgstr "Nom de domaine Windows"
+
+#. Il semble que ce soit le libellé d'un des onglets : Données/Visualisation/Axes/Libellés
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:407
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:413
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:422
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:428
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:437
+msgid "Labels"
+msgstr "Libellées"
+
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:329
+msgid "Add members"
+msgstr "Ajouter des membres"
+
+#: frontend/src/metabase/entities/collections.js:108
+msgid "Collection it's saved in"
+msgstr "La collection est enregistrée dans"
+
+#: frontend/src/metabase/lib/groups.js:4
+msgid "All Users"
+msgstr "Tous les utilisateurs"
+
+#: frontend/src/metabase/lib/groups.js:5
+msgid "Administrators"
+msgstr "Administrateurs"
+
+#: frontend/src/metabase/lib/groups.js:6
+msgid "MetaBot"
+msgstr "MetaBot"
+
+#: frontend/src/metabase/public/components/widgets/EmbedModalContent.jsx:290
+msgid "Sharing"
+msgstr "Partage"
+
+#. Il semble que ce soit le libellé d'un des onglets : Données/Visualisation/Axes/Libellés
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:156
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:169
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:177
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:200
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:206
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:217
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:233
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:81
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:71
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:76
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:82
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:41
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:47
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:72
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:85
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:98
+msgid "Display"
+msgstr "Affichage"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:283
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:316
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:329
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:344
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:356
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:362
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:370
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:400
+msgid "Axes"
+msgstr "Axes"
+
+#: frontend/src/metabase/visualizations/visualizations/Table.jsx:93
+msgid "Formatting"
+msgstr "Formatage"
+
+#: frontend/src/metabase/containers/Overworld.jsx:105
+msgid "Try these x-rays based on your data."
+msgstr "Essayez ces radiographies basées sur vos données."
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:35
+msgid "There was a problem displaying this chart."
+msgstr "Un problème est survenu lors de l'affichage de ce graphique."
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:36
+msgid "Sorry, you don't have permission to see this card."
+msgstr "Désolé, vous n'êtes pas autorisé à voir cette question."
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:51
+msgid "Just a heads up:"
+msgstr "Juste un aperçu :"
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:59
+msgid "{0} without the Sample Dataset, the Query Builder tutorial won't work. You can always restore the Sample Dataset, but any questions you've saved using this data will be lost."
+msgstr "{0} sans le jeu de données exemple, le didacticiel du générateur de requêtes ne fonctionnera pas. Vous pouvez restaurer le jeu de données, mais toutes les questions que vous avez enregistrées à l'aide de ces données seront perdues."
+
+#: frontend/src/metabase/qb/components/drill/AutomaticDashboardDrill.jsx:27
+msgid "X-ray"
+msgstr "Radiographie"
+
+#: frontend/src/metabase/qb/components/drill/CompareToRestDrill.js:27
+msgid "Compare to the rest"
+msgstr "Comparer au reste"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:247
+msgid "Use the Java Virtual Machine (JVM) timezone"
+msgstr "Utiliser le fuseau horaire de la machine virtuelle Java (JVM)"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:249
+msgid "We suggest you leave this off unless you're doing manual timezone casting in\n"
+"many or most of your queries with this data."
+msgstr "Nous vous suggérons de le laisser cela désactivé à moins que vous ne procédiez à un paramétrage manuel du fuseau horaire dans la plupart de vos requêtes avec ces données."
+
+#: frontend/src/metabase/containers/Overworld.jsx:313
+msgid "Your team's most important dashboards go here"
+msgstr "Les tableaux de bord les plus importants pour votre équipe vont ici"
+
+#: frontend/src/metabase/containers/Overworld.jsx:314
+msgid "Pin dashboards in {0} to have them appear in this space for everyone"
+msgstr "Épingler les tableaux de bord dans {0} pour qu'ils apparaissent dans cet espace pour tout le monde"
+
+#: src/metabase/db.clj
+msgid "Unable to release the Liquibase lock after a migration failure"
+msgstr "Impossible de libérer le verrou Liquibase après un échec de migration"
+
+#: src/metabase/driver/bigquery.clj
+msgid "Use JVM Time Zone"
+msgstr "Utiliser le fuseau horaire de la JVM"
+
diff --git a/locales/metabase.pot b/locales/metabase.pot
index bd710fe4c347d4a9f3bbcc047a4540d3e075647a..796ad13007953aaa767fe7ab10b906c9944ad571 100644
--- a/locales/metabase.pot
+++ b/locales/metabase.pot
@@ -13,7 +13,7 @@ msgstr ""
 "#-#-#-#-#  metabase-backend.pot (metabase)  #-#-#-#-#\n"
 "Project-Id-Version: metabase\n"
 "Report-Msgid-Bugs-To: docs@metabase.com\n"
-"POT-Creation-Date: 2018-03-02 12:58+0700\n"
+"POT-Creation-Date: 2018-09-05 12:05-0700\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -28,25 +28,16 @@ msgstr ""
 
 #: frontend/src/metabase/admin/databases/components/CreatedDatabaseModal.jsx:22
 msgid ""
-"We're analyzing its schema now to make some educated guesses about its\n"
-"metadata. {0} in the Data Model section to see what we've found and to\n"
-"make edits, or {1} about\n"
-"this database."
+"We took a look at your data, and we have some automated explorations that we "
+"can show you!"
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/components/CreatedDatabaseModal.jsx:41
-#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:135
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:222
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:259
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:348
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:368
-#: frontend/src/metabase/components/HeaderModal.jsx:43
-#: frontend/src/metabase/parameters/components/widgets/CategoryWidget.jsx:106
-#: frontend/src/metabase/query_builder/components/AggregationPopover.jsx:298
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:182
-#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:96
-#: frontend/src/metabase/visualizations/components/ChartSettings.jsx:200
-msgid "Done"
+#: frontend/src/metabase/admin/databases/components/CreatedDatabaseModal.jsx:27
+msgid "I'm good thanks"
+msgstr ""
+
+#: frontend/src/metabase/admin/databases/components/CreatedDatabaseModal.jsx:32
+msgid "Explore this data"
 msgstr ""
 
 #: frontend/src/metabase/admin/databases/components/DatabaseEditForms.jsx:42
@@ -54,30 +45,33 @@ msgid "Select a database type"
 msgstr ""
 
 #: frontend/src/metabase/admin/databases/components/DatabaseEditForms.jsx:75
-#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:182
-#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:438
-#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:175
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:170
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:435
+#: frontend/src/metabase/admin/permissions/containers/CollectionPermissionsModal.jsx:71
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:182
 #: frontend/src/metabase/components/ActionButton.jsx:51
-#: frontend/src/metabase/components/ButtonWithStatus.jsx:6
+#: frontend/src/metabase/components/ButtonWithStatus.jsx:7
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:180
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:197
 #: frontend/src/metabase/reference/components/EditHeader.jsx:54
 #: frontend/src/metabase/reference/components/EditHeader.jsx:69
-#: frontend/src/metabase/user/components/SetUserPassword.jsx:167
-#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:162
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:171
+#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:164
 msgid "Save"
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:120
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:122
 msgid ""
 "To do some of its magic, Metabase needs to scan your database. We will also "
 "rescan it periodically to keep the metadata up-to-date. You can control when "
 "the periodic rescans happen below."
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:125
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:127
 msgid "Database syncing"
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:126
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:128
 msgid ""
 "This is a lightweight process that checks for\n"
 "updates to this database’s schema. In most cases, you should be fine leaving "
@@ -85,16 +79,16 @@ msgid ""
 "set to sync hourly."
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:145
-#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:182
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:147
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:184
 msgid "Scan"
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:150
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:152
 msgid "Scanning for Filter Values"
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:151
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:153
 msgid ""
 "Metabase can scan the values present in each\n"
 "field in this database to enable checkbox filters in dashboards and "
@@ -104,19 +98,19 @@ msgid ""
 "database."
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:157
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:159
 msgid "When should Metabase automatically scan and cache field values?"
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:162
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:164
 msgid "Regularly, on a schedule"
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:193
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:195
 msgid "Only when adding a new filter widget"
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:197
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:199
 msgid ""
 "When a user adds a new filter to a dashboard or a SQL question, Metabase "
 "will\n"
@@ -124,252 +118,253 @@ msgid ""
 "selectable values."
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:208
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:210
 msgid "Never, I'll do this manually if I need to"
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:220
-#: frontend/src/metabase/admin/settings/components/SettingsEmailForm.jsx:245
-#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:258
-#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:218
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:222
+#: frontend/src/metabase/admin/settings/components/SettingsBatchForm.jsx:27
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:222
 #: frontend/src/metabase/components/ActionButton.jsx:52
-#: frontend/src/metabase/components/ButtonWithStatus.jsx:7
-#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:398
+#: frontend/src/metabase/components/ButtonWithStatus.jsx:8
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:429
 msgid "Saving..."
 msgstr ""
 
 #: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:38
-#: frontend/src/metabase/components/CreateDashboardModal.jsx:59
 #: frontend/src/metabase/components/form/FormMessage.jsx:4
-#: frontend/src/metabase/containers/SaveQuestionModal.jsx:142
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:144
 msgid "Server error encountered"
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:53
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:51
+msgid "Just a heads up:"
+msgstr ""
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:54
 msgid "Delete this database?"
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:58
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:59
 msgid ""
-"<strong>Just a heads up:</strong> without the Sample Dataset, the Query "
-"Builder tutorial won't work. You can always restore the Sample Dataset, but "
-"any questions you've saved using this data will be lost."
+"{0} without the Sample Dataset, the Query Builder tutorial won't work. You "
+"can always restore the Sample Dataset, but any questions you've saved using "
+"this data will be lost."
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:61
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:62
 msgid ""
 "All saved questions, metrics, and segments that rely on this database will "
 "be lost."
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:62
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:63
 msgid "This cannot be undone."
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:65
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:66
 msgid "If you're sure, please type"
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:65
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:66
 msgid "DELETE"
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:66
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:67
 msgid "in this box:"
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:80
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:78
 #: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:50
 #: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:87
 #: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:93
 #: frontend/src/metabase/admin/people/components/AddRow.jsx:27
-#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:234
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:299
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:324
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:250
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:302
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:322
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:343
 #: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:49
 #: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:52
-#: frontend/src/metabase/admin/permissions/selectors.js:151
-#: frontend/src/metabase/admin/permissions/selectors.js:161
-#: frontend/src/metabase/admin/permissions/selectors.js:176
-#: frontend/src/metabase/admin/permissions/selectors.js:215
+#: frontend/src/metabase/admin/permissions/containers/CollectionPermissionsModal.jsx:58
+#: frontend/src/metabase/admin/permissions/selectors.js:156
+#: frontend/src/metabase/admin/permissions/selectors.js:166
+#: frontend/src/metabase/admin/permissions/selectors.js:181
+#: frontend/src/metabase/admin/permissions/selectors.js:220
 #: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:355
-#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:174
-#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:240
-#: frontend/src/metabase/components/ConfirmContent.jsx:15
-#: frontend/src/metabase/components/CreateDashboardModal.jsx:78
-#: frontend/src/metabase/components/DeleteModalWithConfirm.jsx:71
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:181
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:247
+#: frontend/src/metabase/components/ConfirmContent.jsx:18
+#: frontend/src/metabase/components/DeleteModalWithConfirm.jsx:72
 #: frontend/src/metabase/components/HeaderModal.jsx:49
-#: frontend/src/metabase/containers/SaveQuestionModal.jsx:194
-#: frontend/src/metabase/pulse/components/PulseEdit.jsx:199
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:180
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:332
+#: frontend/src/metabase/components/form/StandardForm.jsx:55
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:196
+#: frontend/src/metabase/dashboard/components/AddSeriesModal.jsx:289
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:162
+#: frontend/src/metabase/dashboard/components/RemoveFromDashboardModal.jsx:42
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:189
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:192
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:352
 #: frontend/src/metabase/query_builder/components/RunButton.jsx:24
 #: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:83
 #: frontend/src/metabase/query_builder/containers/ArchiveQuestionModal.jsx:48
-#: frontend/src/metabase/questions/containers/ArchiveCollectionWidget.jsx:50
-#: frontend/src/metabase/questions/containers/CollectionEditorForm.jsx:49
-#: frontend/src/metabase/questions/containers/EditLabels.jsx:110
-#: frontend/src/metabase/questions/containers/MoveToCollection.jsx:61
 #: frontend/src/metabase/reference/components/EditHeader.jsx:34
 #: frontend/src/metabase/reference/components/RevisionMessageModal.jsx:52
-#: frontend/src/metabase/visualizations/components/ChartSettings.jsx:205
+#: frontend/src/metabase/visualizations/components/ChartSettings.jsx:209
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:397
 msgid "Cancel"
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:86
-#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:121
-#: frontend/src/metabase/pulse/components/PulseEdit.jsx:177
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:84
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:123
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:397
 msgid "Delete"
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:141
-#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:74
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:60
+msgid "Connection"
+msgstr ""
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:61
+msgid "Scheduling"
+msgstr ""
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:128
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:76
 #: frontend/src/metabase/admin/permissions/selectors.js:316
 #: frontend/src/metabase/admin/permissions/selectors.js:323
 #: frontend/src/metabase/admin/permissions/selectors.js:419
+#: frontend/src/metabase/admin/routes.jsx:39
+#: frontend/src/metabase/nav/containers/Navbar.jsx:215
 #: frontend/src/metabase/reference/databases/DatabaseSidebar.jsx:18
 #: frontend/src/metabase/reference/databases/TableSidebar.jsx:18
-#: frontend/src/metabase/routes.jsx:376
 msgid "Databases"
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:142
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:129
 msgid "Add Database"
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:150
-msgid "Connection"
-msgstr ""
-
-#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:150
-msgid "Scheduling"
-msgstr ""
-
-#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:182
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:170
 #: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:78
 #: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:84
-#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:237
-#: frontend/src/metabase/admin/settings/components/SettingsEmailForm.jsx:244
-#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:257
-#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:217
-#: frontend/src/metabase/pulse/components/PulseEdit.jsx:194
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:334
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:253
+#: frontend/src/metabase/admin/settings/components/SettingsBatchForm.jsx:26
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:221
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:182
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:354
 #: frontend/src/metabase/reference/components/RevisionMessageModal.jsx:47
 msgid "Save changes"
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:197
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:185
 #: frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx:38
 #: frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx:38
 msgid "Actions"
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:205
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:193
 msgid "Sync database schema now"
 msgstr ""
 
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:194
 #: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:206
-#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:218
-#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:783
-#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:791
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:788
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:796
 #: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:112
 #: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:120
 msgid "Starting…"
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:207
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:195
 msgid "Failed to sync"
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:208
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:196
 msgid "Sync triggered!"
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:217
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:205
 msgid "Re-scan field values now"
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:219
-#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:784
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:207
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:789
 #: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:113
 msgid "Failed to start scan"
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:220
-#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:785
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:208
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:790
 #: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:114
 msgid "Scan triggered!"
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:227
-#: frontend/src/metabase/pulse/components/PulseEdit.jsx:165
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:377
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:215
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:399
 msgid "Danger Zone"
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:233
-#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:236
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:221
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:224
 msgid "Discard saved field values"
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:251
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:239
 msgid "Remove this database"
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:73
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:75
 msgid "Add database"
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:85
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:87
 #: frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx:36
 #: frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx:36
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:421
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:468
 #: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:183
 #: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:91
-#: frontend/src/metabase/components/CreateDashboardModal.jsx:90
-#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:371
-#: frontend/src/metabase/containers/EntitySearch.jsx:24
-#: frontend/src/metabase/containers/SaveQuestionModal.jsx:215
-#: frontend/src/metabase/questions/containers/CollectionEditorForm.jsx:76
-#: frontend/src/metabase/questions/containers/LabelEditorForm.jsx:60
-#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:463
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:402
+#: frontend/src/metabase/containers/EntitySearch.jsx:26
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:218
+#: frontend/src/metabase/entities/collections.js:86
+#: frontend/src/metabase/entities/dashboards.js:96
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:461
 #: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:62
 msgid "Name"
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:86
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:88
 msgid "Engine"
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:115
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:117
 msgid "Deleting..."
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:145
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:147
 msgid "Loading ..."
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:161
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:163
 msgid "Bring the sample dataset back"
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/database.js:180
+#: frontend/src/metabase/admin/databases/database.js:175
 msgid "Couldn't connect to the database. Please check the connection details."
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/database.js:402
+#: frontend/src/metabase/admin/databases/database.js:383
 msgid "Successfully created!"
 msgstr ""
 
-#: frontend/src/metabase/admin/databases/database.js:412
+#: frontend/src/metabase/admin/databases/database.js:393
 msgid "Successfully saved!"
 msgstr ""
 
 #: frontend/src/metabase/admin/datamodel/components/ObjectActionSelect.jsx:44
+#: frontend/src/metabase/dashboard/components/DashCard.jsx:276
 #: frontend/src/metabase/parameters/components/ParameterWidget.jsx:173
-#: frontend/src/metabase/pulse/components/PulseListItem.jsx:54
-#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:205
-#: frontend/src/metabase/questions/containers/EditLabels.jsx:119
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:209
 #: frontend/src/metabase/reference/components/EditButton.jsx:18
 msgid "Edit"
 msgstr ""
@@ -434,6 +429,7 @@ msgid "No special type"
 msgstr ""
 
 #: frontend/src/metabase/admin/datamodel/components/database/ColumnItem.jsx:190
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:34
 #: frontend/src/metabase/reference/components/Field.jsx:57
 #: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:42
 msgid "Other"
@@ -452,84 +448,103 @@ msgid "Columns"
 msgstr ""
 
 #: frontend/src/metabase/admin/datamodel/components/database/ColumnsList.jsx:22
-#: frontend/src/metabase/admin/datamodel/components/database/MetadataSchema.jsx:41
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataSchema.jsx:44
 msgid "Column"
 msgstr ""
 
 #: frontend/src/metabase/admin/datamodel/components/database/ColumnsList.jsx:24
-#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:119
-#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:206
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:121
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:203
 msgid "Visibility"
 msgstr ""
 
 #: frontend/src/metabase/admin/datamodel/components/database/ColumnsList.jsx:25
-#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:217
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:214
 msgid "Type"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/components/database/MetadataHeader.jsx:85
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataHeader.jsx:87
 msgid "Current database:"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/components/database/MetadataHeader.jsx:90
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataHeader.jsx:92
 msgid "Show original schema"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/components/database/MetadataSchema.jsx:42
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataSchema.jsx:45
 msgid "Data Type"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/components/database/MetadataSchema.jsx:43
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataSchema.jsx:46
 msgid "Additional Info"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/components/database/MetadataSchemaList.jsx:45
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataSchemaList.jsx:44
 msgid "Find a schema"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:80
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataSchemaList.jsx:51
+msgid "{0} schema"
+msgid_plural "{0} schemas"
+msgstr[0] ""
+msgstr[1] ""
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:82
 msgid "Why Hide?"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:81
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:83
 msgid "Technical Data"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:82
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:84
 msgid "Irrelevant/Cruft"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:88
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:90
 msgid "Queryable"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:89
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:91
 msgid "Hidden"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:115
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:117
 msgid "No table description yet"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:122
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:124
 msgid "Metadata Strength"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/components/database/MetadataTableList.jsx:104
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTableList.jsx:87
+msgid "{0} Queryable Table"
+msgid_plural "{0} Queryable Tables"
+msgstr[0] ""
+msgstr[1] ""
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTableList.jsx:96
+msgid "{0} Hidden Table"
+msgid_plural "{0} Hidden Tables"
+msgstr[0] ""
+msgstr[1] ""
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTableList.jsx:113
 msgid "Find a table"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/components/database/MetadataTableList.jsx:117
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTableList.jsx:126
 msgid "Schemas"
 msgstr ""
 
 #: frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx:24
-#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:139
-#: frontend/src/metabase/reference/guide/BaseSidebar.jsx:33
-#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:192
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:137
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:33
+#: frontend/src/metabase/reference/guide/BaseSidebar.jsx:27
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:189
 #: frontend/src/metabase/reference/metrics/MetricList.jsx:56
 #: frontend/src/metabase/reference/metrics/MetricSidebar.jsx:18
-#: frontend/src/metabase/routes.jsx:235
+#: frontend/src/metabase/routes.jsx:231
 msgid "Metrics"
 msgstr ""
 
@@ -548,8 +563,8 @@ msgid "Create metrics to add them to the View dropdown in the query builder"
 msgstr ""
 
 #: frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx:24
-#: frontend/src/metabase/query_builder/components/DataSelector.jsx:919
-#: frontend/src/metabase/reference/guide/BaseSidebar.jsx:39
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:922
+#: frontend/src/metabase/reference/guide/BaseSidebar.jsx:33
 #: frontend/src/metabase/reference/segments/SegmentFieldSidebar.jsx:19
 #: frontend/src/metabase/reference/segments/SegmentList.jsx:56
 #: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:18
@@ -590,6 +605,7 @@ msgstr ""
 
 #: frontend/src/metabase/admin/datamodel/components/revisions/Revision.jsx:46
 #: frontend/src/metabase/home/components/Activity.jsx:80
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:343
 msgid "You"
 msgstr ""
 
@@ -605,120 +621,120 @@ msgstr ""
 msgid "Revision History for"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:188
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:185
 msgid "{0} – Field Settings"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:207
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:204
 msgid "Where this field will appear throughout Metabase"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:229
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:226
 msgid "Filtering on this field"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:230
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:227
 msgid ""
 "When this field is used in a filter, what should people use to enter the "
 "value they want to filter on?"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:337
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:334
 msgid "No description for this field yet"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:416
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:413
 msgid "Original value"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:417
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:414
 msgid "Mapped value"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:460
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:457
 msgid "Enter value"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:483
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:480
 msgid "Use original value"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:484
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:481
 msgid "Use foreign key"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:485
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:482
 msgid "Custom mapping"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:505
-#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:605
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:510
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:610
 msgid "Unrecognized mapping type"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:539
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:544
 msgid ""
 "Current field isn't a foreign key or FK target table metadata is missing"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:636
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:641
 msgid "The selected field isn't a foreign key"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:698
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:703
 msgid "Display values"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:699
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:704
 msgid ""
 "Choose to show the original value from the database, or have this field "
 "display associated or custom information."
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:725
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:730
 msgid "Choose a field"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:746
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:751
 msgid "Please select a column to use for display."
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:766
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:771
 msgid "Tip:"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:767
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:772
 msgid ""
 "You might want to update the field name to make sure it still makes sense "
 "based on your remapping choices."
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:776
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:781
 #: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:105
 msgid "Cached field values"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:777
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:782
 msgid ""
 "Metabase can scan the values for this field to enable checkbox filters in "
 "dashboards and questions."
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:782
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:787
 msgid "Re-scan this field"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:790
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:795
 #: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:119
 msgid "Discard cached field values"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:792
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:797
 #: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:121
 msgid "Failed to discard values"
 msgstr ""
 
-#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:793
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:798
 #: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:122
 msgid "Discard triggered!"
 msgstr ""
@@ -729,7 +745,7 @@ msgstr ""
 
 #: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:37
 #: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:34
-#: frontend/src/metabase/questions/containers/CollectionEditorForm.jsx:19
+#: frontend/src/metabase/entities/collections.js:89
 msgid "Name is required"
 msgstr ""
 
@@ -865,9 +881,11 @@ msgid ""
 msgstr ""
 
 #: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:91
-#: frontend/src/metabase/admin/settings/containers/SettingsEditorApp.jsx:272
+#: frontend/src/metabase/admin/routes.jsx:79
+#: frontend/src/metabase/admin/settings/containers/SettingsEditorApp.jsx:266
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:96
+#: frontend/src/metabase/nav/containers/Navbar.jsx:200
 #: frontend/src/metabase/query_builder/components/template_tags/TagEditorSidebar.jsx:99
-#: frontend/src/metabase/routes.jsx:419
 msgid "Settings"
 msgstr ""
 
@@ -882,55 +900,56 @@ msgid "Re-scan this table"
 msgstr ""
 
 #: frontend/src/metabase/admin/people/components/AddRow.jsx:34
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:188
-#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:246
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:194
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:253
+#: frontend/src/metabase/dashboard/components/DashCard.jsx:276
 msgid "Add"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:78
-#: frontend/src/metabase/setup/components/UserStep.jsx:100
-#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:65
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:80
+#: frontend/src/metabase/setup/components/UserStep.jsx:103
+#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:67
 msgid "Not a valid formatted email address"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:124
-#: frontend/src/metabase/setup/components/UserStep.jsx:182
-#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:98
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:135
+#: frontend/src/metabase/setup/components/UserStep.jsx:186
+#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:100
 msgid "First name"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:145
-#: frontend/src/metabase/setup/components/UserStep.jsx:199
-#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:115
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:156
+#: frontend/src/metabase/setup/components/UserStep.jsx:203
+#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:117
 msgid "Last name"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:167
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:178
 #: frontend/src/metabase/auth/containers/ForgotPasswordApp.jsx:77
-#: frontend/src/metabase/auth/containers/LoginApp.jsx:156
-#: frontend/src/metabase/components/NewsletterForm.jsx:90
-#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:472
-#: frontend/src/metabase/setup/components/UserStep.jsx:217
-#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:136
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:158
+#: frontend/src/metabase/components/NewsletterForm.jsx:94
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:470
+#: frontend/src/metabase/setup/components/UserStep.jsx:222
+#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:138
 msgid "Email address"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:191
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:202
 msgid "Permission Groups"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:227
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:238
 msgid "Make this user an admin"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:31
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:32
 msgid ""
 "All users belong to the {0} group and can't be removed from it. Setting "
 "permissions for this group is a great way to\n"
 "make sure you know what new Metabase users will be able to see."
 msgstr ""
 
-#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:40
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:41
 msgid ""
 "This is a special group whose members can see everything in the Metabase "
 "instance, and who can access and make changes to the\n"
@@ -938,88 +957,120 @@ msgid ""
 "to this group with care."
 msgstr ""
 
-#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:44
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:45
 msgid ""
 "To make sure you don't get locked out of Metabase, there always has to be at "
 "least one user in this group."
 msgstr ""
 
-#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:176
-#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:212
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:177
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:219
 msgid "Members"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:176
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:423
-#: frontend/src/metabase/admin/settings/selectors.js:104
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:177
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:470
+#: frontend/src/metabase/admin/settings/selectors.js:107
 #: frontend/src/metabase/lib/core.js:50
-#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:296
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:293
 msgid "Email"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:202
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:203
 msgid "A group is only as good as its members."
 msgstr ""
 
-#: frontend/src/metabase/admin/people/components/GroupSummary.jsx:16
-#: frontend/src/metabase/routes.jsx:373
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:329
+msgid "Add members"
+msgstr ""
+
+#: frontend/src/metabase/admin/people/components/GroupSummary.jsx:15
+#: frontend/src/metabase/admin/routes.jsx:34
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:50
 msgid "Admin"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/components/GroupSummary.jsx:36
+#: frontend/src/metabase/admin/people/components/GroupSummary.jsx:16
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:237
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:290
+msgid "and"
+msgstr ""
+
+#: frontend/src/metabase/admin/people/components/GroupSummary.jsx:19
+#: frontend/src/metabase/admin/people/components/GroupSummary.jsx:31
+msgid "{0} other group"
+msgid_plural "{0} other groups"
+msgstr[0] ""
+msgstr[1] ""
+
+#: frontend/src/metabase/admin/people/components/GroupSummary.jsx:37
 msgid "Default"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:36
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:40
 msgid "Something like \"Marketing\""
 msgstr ""
 
-#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:55
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:59
 msgid "Remove this group?"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:57
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:61
 msgid ""
 "Are you sure? All members of this group will lose any permissions settings "
-"the have based on this group.\n"
+"they have based on this group.\n"
 "This can't be undone."
 msgstr ""
 
-#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:68
-#: frontend/src/metabase/components/ConfirmContent.jsx:14
-#: frontend/src/metabase/xray/components/InsightCard.jsx:21
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:72
+#: frontend/src/metabase/components/ConfirmContent.jsx:17
 msgid "Yes"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:71
-#: frontend/src/metabase/xray/components/InsightCard.jsx:27
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:75
 msgid "No"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:89
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:93
 msgid "Edit Name"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:92
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:96
 msgid "Remove Group"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:212
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:139
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:225
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:263
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:367
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:385
+#: frontend/src/metabase/components/HeaderModal.jsx:43
+#: frontend/src/metabase/dashboard/components/AddSeriesModal.jsx:282
+#: frontend/src/metabase/parameters/components/widgets/CategoryWidget.jsx:106
+#: frontend/src/metabase/query_builder/components/AggregationPopover.jsx:298
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:194
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:96
+#: frontend/src/metabase/visualizations/components/ChartSettings.jsx:215
+msgid "Done"
+msgstr ""
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:219
 msgid "Group name"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:389
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:424
-#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:147
-#: frontend/src/metabase/routes.jsx:412
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:399
+#: frontend/src/metabase/admin/people/containers/AdminPeopleApp.jsx:25
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:477
+#: frontend/src/metabase/admin/routes.jsx:72
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:154
 msgid "Groups"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:390
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:400
 msgid "Create a group"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:396
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:406
 msgid ""
 "You can use groups to control your users' access to your data. Put users in "
 "groups and then go to the Permissions section to control each group's "
@@ -1040,161 +1091,194 @@ msgid "Reset Password"
 msgstr ""
 
 #: frontend/src/metabase/admin/people/components/UserActionsSelect.jsx:97
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:303
-#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:201
-#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:320
-#: frontend/src/metabase/parameters/components/ParameterWidget.jsx:177
-#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:104
-msgid "Remove"
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:304
+msgid "Deactivate"
+msgstr ""
+
+#: frontend/src/metabase/admin/people/containers/AdminPeopleApp.jsx:24
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:435
+#: frontend/src/metabase/admin/routes.jsx:70
+#: frontend/src/metabase/nav/containers/Navbar.jsx:205
+msgid "People"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:186
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:192
 msgid "Who do you want to add?"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:203
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:207
 msgid "Edit {0}'s details"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:217
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:254
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:220
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:258
 msgid "{0} has been added"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:221
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:258
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:224
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:262
 msgid "Add another person"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:227
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:231
 msgid ""
 "We couldn’t send them an email invitation,\n"
 "so make sure to tell them to log in using {0}\n"
 "and this password we’ve generated for them:"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:238
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:242
 msgid "If you want to be able to send email invites, just go to the {0} page."
 msgstr ""
 
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:266
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:268
 msgid "We’ve sent an invite to {0} with instructions to set their password."
 msgstr ""
 
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:280
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:283
 msgid "We've re-sent {0}'s invite"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:282
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:285
 #: frontend/src/metabase/query_builder/components/SavedQuestionIntroModal.jsx:22
-#: frontend/src/metabase/tutorial/Tutorial.jsx:245
+#: frontend/src/metabase/tutorial/Tutorial.jsx:253
 msgid "Okay"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:286
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:289
 msgid "Any previous email invites they have will no longer work."
 msgstr ""
 
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:297
-msgid "Remove {0}?"
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:300
+msgid "Deactivate {0}?"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:308
-msgid "{0} won't be able to log in anymore. This can't be undone."
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:309
+msgid "{0} won't be able to log in anymore."
 msgstr ""
 
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:322
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:320
+msgid "Reactivate {0}'s account?"
+msgstr ""
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:326
+msgid "Reactivate"
+msgstr ""
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:330
+msgid ""
+"They'll be able to log in again, and they'll be placed back into the groups "
+"they were in before their account was deactivated."
+msgstr ""
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:341
 msgid "Reset {0}'s password?"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:328
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:347
+#: frontend/src/metabase/components/form/StandardForm.jsx:71
 msgid "Reset"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:332
-#: frontend/src/metabase/components/ConfirmContent.jsx:10
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:351
+#: frontend/src/metabase/components/ConfirmContent.jsx:13
+#: frontend/src/metabase/dashboard/components/ArchiveDashboardModal.jsx:44
 msgid "Are you sure you want to do this?"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:343
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:367
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:362
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:384
 msgid "{0}'s password has been reset"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:353
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:371
 msgid ""
 "Here’s a temporary password they can use to log in and then change their "
 "password."
 msgstr ""
 
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:371
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:388
 msgid "We've sent them an email with instructions for creating a new password."
 msgstr ""
 
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:411
-#: frontend/src/metabase/routes.jsx:410
-msgid "People"
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:443
+msgid "Active"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:412
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:444
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:473
+msgid "Deactivated"
+msgstr ""
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:459
 msgid "Add someone"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:425
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:478
 msgid "Last Login"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:447
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:501
 msgid "Signed up via Google"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:452
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:506
 msgid "Signed up via LDAP"
 msgstr ""
 
-#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:467
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:518
+msgid "Reactivate this account"
+msgstr ""
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:545
 msgid "Never"
 msgstr ""
 
-#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:46
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:27
+#: frontend/src/metabase/query_builder/components/dataref/MainPane.jsx:24
+#: src/metabase/automagic_dashboards/core.clj
+#, fuzzy
+msgid "{0} table"
+msgid_plural "{0} tables"
+msgstr[0] ""
+"#-#-#-#-#  metabase-frontend.pot  #-#-#-#-#\n"
+"#-#-#-#-#  metabase-backend.pot (metabase)  #-#-#-#-#\n"
+msgstr[1] "#-#-#-#-#  metabase-frontend.pot  #-#-#-#-#\n"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:45
 msgid " will be "
 msgstr ""
 
-#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:49
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:48
 msgid "given access to"
 msgstr ""
 
-#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:54
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:53
 msgid " and "
 msgstr ""
 
-#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:57
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:56
 msgid "denied access to"
 msgstr ""
 
-#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:71
-msgid " will no longer able to "
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:70
+msgid " will no longer be able to "
 msgstr ""
 
-#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:72
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:71
 msgid " will now be able to "
 msgstr ""
 
-#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:80
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:79
 msgid " native queries for "
 msgstr ""
 
-#: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:17
-#: frontend/src/metabase/admin/permissions/routes.jsx:12
-msgid "Permissions"
-msgstr ""
-
 #: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:32
 msgid "Save permissions?"
 msgstr ""
 
 #: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:38
-#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:157
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:161
 msgid "Save Changes"
 msgstr ""
 
@@ -1206,10 +1290,26 @@ msgstr ""
 msgid "No changes to permissions will be made."
 msgstr ""
 
-#: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:81
+#: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:65
 msgid "You've made changes to permissions."
 msgstr ""
 
+#: frontend/src/metabase/admin/permissions/components/PermissionsTabs.jsx:12
+msgid "Data permissions"
+msgstr ""
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsTabs.jsx:13
+msgid "Collection permissions"
+msgstr ""
+
+#: frontend/src/metabase/admin/permissions/containers/CollectionPermissionsModal.jsx:52
+msgid "Permissions for this collection"
+msgstr ""
+
+#: frontend/src/metabase/admin/permissions/containers/CollectionPermissionsModal.jsx:56
+msgid "See all collection permissions"
+msgstr ""
+
 #: frontend/src/metabase/admin/permissions/containers/PermissionsApp.jsx:53
 msgid "You have unsaved changes"
 msgstr ""
@@ -1218,117 +1318,126 @@ msgstr ""
 msgid "Do you want to leave this page and discard your changes?"
 msgstr ""
 
-#: frontend/src/metabase/admin/permissions/permissions.js:146
+#: frontend/src/metabase/admin/permissions/containers/TogglePropagateAction.jsx:25
+msgid "Also change sub-collections"
+msgstr ""
+
+#: frontend/src/metabase/admin/permissions/permissions.js:137
 msgid "Sorry, an error occurred."
 msgstr ""
 
-#: frontend/src/metabase/admin/permissions/selectors.js:55
+#: frontend/src/metabase/admin/permissions/routes.jsx:12
+#: frontend/src/metabase/nav/containers/Navbar.jsx:220
+msgid "Permissions"
+msgstr ""
+
+#: frontend/src/metabase/admin/permissions/selectors.js:59
 msgid ""
 "Administrators always have the highest level of access to everything in "
 "Metabase."
 msgstr ""
 
-#: frontend/src/metabase/admin/permissions/selectors.js:57
+#: frontend/src/metabase/admin/permissions/selectors.js:61
 msgid ""
 "Every Metabase user belongs to the All Users group. If you want to limit or "
 "restrict a group's access to something, make sure the All Users group has an "
 "equal or lower level of access."
 msgstr ""
 
-#: frontend/src/metabase/admin/permissions/selectors.js:59
+#: frontend/src/metabase/admin/permissions/selectors.js:63
 msgid ""
 "MetaBot is Metabase's Slack bot. You can choose what it has access to here."
 msgstr ""
 
-#: frontend/src/metabase/admin/permissions/selectors.js:111
+#: frontend/src/metabase/admin/permissions/selectors.js:115
 msgid ""
 "The \"{0}\" group may have access to a different set of {1} than this group, "
 "which may give this group additional access to some {2}."
 msgstr ""
 
-#: frontend/src/metabase/admin/permissions/selectors.js:116
+#: frontend/src/metabase/admin/permissions/selectors.js:120
 msgid ""
 "The \"{0}\" group has a higher level of access than this, which will "
 "override this setting. You should limit or revoke the \"{1}\" group's access "
 "to this item."
 msgstr ""
 
-#: frontend/src/metabase/admin/permissions/selectors.js:145
-msgid "{0} access even though \"{1}\" has greater access?"
+#: frontend/src/metabase/admin/permissions/selectors.js:150
+msgid "Limit"
 msgstr ""
 
 #: frontend/src/metabase/admin/permissions/selectors.js:150
-#: frontend/src/metabase/admin/permissions/selectors.js:247
+msgid "Revoke"
+msgstr ""
+
+#: frontend/src/metabase/admin/permissions/selectors.js:152
+msgid "access even though \"{0}\" has greater access?"
+msgstr ""
+
+#: frontend/src/metabase/admin/permissions/selectors.js:155
+#: frontend/src/metabase/admin/permissions/selectors.js:254
 msgid "Limit access"
 msgstr ""
 
-#: frontend/src/metabase/admin/permissions/selectors.js:150
-#: frontend/src/metabase/admin/permissions/selectors.js:214
-#: frontend/src/metabase/admin/permissions/selectors.js:255
+#: frontend/src/metabase/admin/permissions/selectors.js:155
+#: frontend/src/metabase/admin/permissions/selectors.js:219
+#: frontend/src/metabase/admin/permissions/selectors.js:262
 msgid "Revoke access"
 msgstr ""
 
-#: frontend/src/metabase/admin/permissions/selectors.js:159
+#: frontend/src/metabase/admin/permissions/selectors.js:164
 msgid "Change access to this database to limited?"
 msgstr ""
 
-#: frontend/src/metabase/admin/permissions/selectors.js:160
+#: frontend/src/metabase/admin/permissions/selectors.js:165
 msgid "Change"
 msgstr ""
 
-#: frontend/src/metabase/admin/permissions/selectors.js:173
+#: frontend/src/metabase/admin/permissions/selectors.js:178
 msgid "Allow Raw Query Writing?"
 msgstr ""
 
-#: frontend/src/metabase/admin/permissions/selectors.js:174
+#: frontend/src/metabase/admin/permissions/selectors.js:179
 msgid ""
 "This will also change this group's data access to Unrestricted for this "
 "database."
 msgstr ""
 
-#: frontend/src/metabase/admin/permissions/selectors.js:175
+#: frontend/src/metabase/admin/permissions/selectors.js:180
 msgid "Allow"
 msgstr ""
 
-#: frontend/src/metabase/admin/permissions/selectors.js:212
+#: frontend/src/metabase/admin/permissions/selectors.js:217
 msgid "Revoke access to all tables?"
 msgstr ""
 
-#: frontend/src/metabase/admin/permissions/selectors.js:213
+#: frontend/src/metabase/admin/permissions/selectors.js:218
 msgid ""
 "This will also revoke this group's access to raw queries for this database."
 msgstr ""
 
-#: frontend/src/metabase/admin/permissions/selectors.js:240
+#: frontend/src/metabase/admin/permissions/selectors.js:247
 msgid "Grant unrestricted access"
 msgstr ""
 
-#: frontend/src/metabase/admin/permissions/selectors.js:241
+#: frontend/src/metabase/admin/permissions/selectors.js:248
 msgid "Unrestricted access"
 msgstr ""
 
-#: frontend/src/metabase/admin/permissions/selectors.js:248
+#: frontend/src/metabase/admin/permissions/selectors.js:255
 msgid "Limited access"
 msgstr ""
 
-#: frontend/src/metabase/admin/permissions/selectors.js:256
+#: frontend/src/metabase/admin/permissions/selectors.js:263
 msgid "No access"
 msgstr ""
 
-#: frontend/src/metabase/admin/permissions/selectors.js:262
+#: frontend/src/metabase/admin/permissions/selectors.js:269
 msgid "Write raw queries"
 msgstr ""
 
-#: frontend/src/metabase/admin/permissions/selectors.js:263
-msgid "Can write raw queries"
-msgstr ""
-
 #: frontend/src/metabase/admin/permissions/selectors.js:270
-msgid "View raw queries"
-msgstr ""
-
-#: frontend/src/metabase/admin/permissions/selectors.js:271
-msgid "Can view raw queries"
+msgid "Can write raw queries"
 msgstr ""
 
 #: frontend/src/metabase/admin/permissions/selectors.js:277
@@ -1336,7 +1445,7 @@ msgid "Curate collection"
 msgstr ""
 
 #: frontend/src/metabase/admin/permissions/selectors.js:278
-msgid "Can add and remove questions from this collection"
+msgid "Can edit this collection and its contents"
 msgstr ""
 
 #: frontend/src/metabase/admin/permissions/selectors.js:284
@@ -1344,7 +1453,7 @@ msgid "View collection"
 msgstr ""
 
 #: frontend/src/metabase/admin/permissions/selectors.js:285
-msgid "Can view questions in this collection"
+msgid "Can view items in this collection"
 msgstr ""
 
 #: frontend/src/metabase/admin/permissions/selectors.js:327
@@ -1367,13 +1476,45 @@ msgstr ""
 msgid "View schemas"
 msgstr ""
 
+#: frontend/src/metabase/admin/permissions/selectors.js:733
+#: frontend/src/metabase/components/CollectionLanding.jsx:341
+#: frontend/src/metabase/home/containers/SearchApp.jsx:35
+#: frontend/src/metabase/home/containers/SearchApp.jsx:96
+msgid "Collections"
+msgstr ""
+
+#: frontend/src/metabase/admin/permissions/selectors.js:745
+msgid "Collection Access"
+msgstr ""
+
+#: frontend/src/metabase/admin/permissions/selectors.js:821
+msgid ""
+"This group has permission to view at least one subcollection of this "
+"collection."
+msgstr ""
+
+#: frontend/src/metabase/admin/permissions/selectors.js:826
+msgid ""
+"This group has permission to edit at least one subcollection of this "
+"collection."
+msgstr ""
+
+#: frontend/src/metabase/admin/permissions/selectors.js:839
+msgid "View sub-collections"
+msgstr ""
+
+#: frontend/src/metabase/admin/routes.jsx:45
+#: frontend/src/metabase/nav/containers/Navbar.jsx:210
+msgid "Data Model"
+msgstr ""
+
 #: frontend/src/metabase/admin/settings/components/SettingsAuthenticationOptions.jsx:11
-#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:114
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:118
 msgid "Sign in with Google"
 msgstr ""
 
 #: frontend/src/metabase/admin/settings/components/SettingsAuthenticationOptions.jsx:13
-#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:116
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:120
 msgid ""
 "Allows users with existing Metabase accounts to login with a Google account "
 "that matches their email address in addition to their Metabase username and "
@@ -1387,8 +1528,8 @@ msgid "Configure"
 msgstr ""
 
 #: frontend/src/metabase/admin/settings/components/SettingsAuthenticationOptions.jsx:23
-#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:270
-#: frontend/src/metabase/admin/settings/selectors.js:194
+#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:13
+#: frontend/src/metabase/admin/settings/selectors.js:201
 msgid "LDAP"
 msgstr ""
 
@@ -1399,68 +1540,63 @@ msgid ""
 "groups."
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/SettingsEmailForm.jsx:75
-#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:71
-#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:67
-#: frontend/src/metabase/admin/settings/selectors.js:150
+#: frontend/src/metabase/admin/settings/components/SettingsBatchForm.jsx:17
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:69
+#: frontend/src/metabase/admin/settings/selectors.js:154
 msgid "That's not a valid email address"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/SettingsEmailForm.jsx:79
-#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:75
-#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:71
+#: frontend/src/metabase/admin/settings/components/SettingsBatchForm.jsx:21
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:73
 msgid "That's not a valid integer"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/SettingsEmailForm.jsx:133
-#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:136
-#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:128
+#: frontend/src/metabase/admin/settings/components/SettingsBatchForm.jsx:28
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:161
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:223
+msgid "Changes saved!"
+msgstr ""
+
+#: frontend/src/metabase/admin/settings/components/SettingsBatchForm.jsx:157
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:132
 msgid "Looks like we ran into some problems"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/SettingsEmailForm.jsx:238
+#: frontend/src/metabase/admin/settings/components/SettingsEmailForm.jsx:12
 msgid "Send test email"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/SettingsEmailForm.jsx:239
+#: frontend/src/metabase/admin/settings/components/SettingsEmailForm.jsx:13
 msgid "Sending..."
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/SettingsEmailForm.jsx:240
+#: frontend/src/metabase/admin/settings/components/SettingsEmailForm.jsx:14
 msgid "Sent!"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/SettingsEmailForm.jsx:246
-#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:259
-#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:157
-#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:219
-msgid "Changes saved!"
-msgstr ""
-
-#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:80
-#: frontend/src/metabase/admin/settings/selectors.js:246
-msgid "Check your parentheses"
+#: frontend/src/metabase/admin/settings/components/SettingsEmailForm.jsx:82
+msgid "Clear"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:269
-#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:109
-#: frontend/src/metabase/admin/settings/selectors.js:190
+#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:12
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:113
+#: frontend/src/metabase/admin/settings/selectors.js:196
 msgid "Authentication"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:274
+#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:18
 msgid "Server Settings"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:276
+#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:29
 msgid "User Schema"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:285
+#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:33
 msgid "Attributes"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:292
+#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:42
 msgid "Group Schema"
 msgstr ""
 
@@ -1468,90 +1604,72 @@ msgstr ""
 msgid "Using "
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/SettingsSetupList.jsx:104
+#: frontend/src/metabase/admin/settings/components/SettingsSetupList.jsx:105
 msgid "Getting set up"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/SettingsSetupList.jsx:105
+#: frontend/src/metabase/admin/settings/components/SettingsSetupList.jsx:106
 msgid "A few things you can do to get the most out of Metabase."
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/SettingsSetupList.jsx:114
+#: frontend/src/metabase/admin/settings/components/SettingsSetupList.jsx:115
 msgid "Recommended next step"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:110
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:114
 msgid "Google Sign-In"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:119
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:123
 msgid ""
 "To allow users to sign in with Google you'll need to give Metabase a Google "
 "Developers console application client ID. It only takes a few steps and "
 "instructions on how to create a key can be found {0}"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:133
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:137
 msgid "Your Google client ID"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:138
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:142
 msgid ""
 "Allow users to sign up on their own if their Google account email address is "
 "from:"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:238
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:242
 msgid "Answers sent right to your Slack #channels"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:247
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:251
 msgid "Create a Slack Bot User for MetaBot"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:257
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:261
 msgid ""
 "Once you're there, give it a name and click {0}. Then copy and paste the Bot "
 "API Token into the field below. Once you are done, create a \"metabase_files"
 "\" channel in Slack. Metabase needs this to upload graphs."
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/SettingsUpdatesForm.jsx:88
+#: frontend/src/metabase/admin/settings/components/SettingsUpdatesForm.jsx:90
 msgid "You're running Metabase {0} which is the latest and greatest!"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/SettingsUpdatesForm.jsx:97
+#: frontend/src/metabase/admin/settings/components/SettingsUpdatesForm.jsx:99
 msgid "Metabase {0} is available.  You're running {1}"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/SettingsUpdatesForm.jsx:110
+#: frontend/src/metabase/admin/settings/components/SettingsUpdatesForm.jsx:112
+#: frontend/src/metabase/components/form/StandardForm.jsx:63
 #: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:96
-#: frontend/src/metabase/questions/containers/CollectionEditorForm.jsx:39
 msgid "Update"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/SettingsUpdatesForm.jsx:114
+#: frontend/src/metabase/admin/settings/components/SettingsUpdatesForm.jsx:116
 msgid "What's Changed:"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/SettingsXrayForm.jsx:28
-msgid "X-Rays and Comparisons"
-msgstr ""
-
-#: frontend/src/metabase/admin/settings/components/SettingsXrayForm.jsx:40
-msgid "Maximum Cost"
-msgstr ""
-
-#: frontend/src/metabase/admin/settings/components/SettingsXrayForm.jsx:42
-msgid ""
-"If you're having performance issues related to x-ray usage you can cap how "
-"expensive x-rays are allowed to be."
-msgstr ""
-
-#: frontend/src/metabase/admin/settings/components/SettingsXrayForm.jsx:45
-msgid "{0} \"Extended\" is required for viewing time series x-rays."
-msgstr ""
-
 #: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:131
 msgid "Add a map"
 msgstr ""
@@ -1565,8 +1683,17 @@ msgstr ""
 msgid "Delete custom map"
 msgstr ""
 
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:201
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:327
+#: frontend/src/metabase/dashboard/components/RemoveFromDashboardModal.jsx:48
+#: frontend/src/metabase/parameters/components/ParameterWidget.jsx:177
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:104
+msgid "Remove"
+msgstr ""
+
 #: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:226
-#: frontend/src/metabase/parameters/components/ParameterValueWidget.jsx:244
+#: frontend/src/metabase/dashboard/containers/DashCardCardParameterMapper.jsx:187
+#: frontend/src/metabase/parameters/components/ParameterValueWidget.jsx:241
 #: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:142
 #: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:184
 msgid "Select…"
@@ -1640,10 +1767,10 @@ msgstr ""
 
 #: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLegalese.jsx:19
 msgid ""
-"In plain english, when you embed charts or dashboards from Metabase in your "
+"In plain English, when you embed charts or dashboards from Metabase in your "
 "own application, that application isn't subject to the Affero General Public "
 "License that covers the rest of Metabase, provided you keep the Metabase "
-"logo and the \"Powered by Metabase\" visible on those embeds. You should "
+"logo and the \"Powered by Metabase\" visible on those embeds. You should, "
 "however, read the license text linked above as that is the actual license "
 "that you will be agreeing to by enabling this feature."
 msgstr ""
@@ -1652,41 +1779,41 @@ msgstr ""
 msgid "Enable"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLevel.jsx:14
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLevel.jsx:24
 msgid "Premium embedding enabled"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLevel.jsx:15
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLevel.jsx:26
 msgid "Enter the token you bought from the Metabase Store"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLevel.jsx:28
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLevel.jsx:53
 msgid ""
-"Premium embedding lets you disable \"Powered by Metabase\" on your embeded "
+"Premium embedding lets you disable \"Powered by Metabase\" on your embedded "
 "dashboards and questions."
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLevel.jsx:35
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLevel.jsx:60
 msgid "Buy a token"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLevel.jsx:38
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLevel.jsx:63
 msgid "Enter a token"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:127
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:134
 msgid "Edit Mappings"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:133
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:140
 msgid "Group Mappings"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:140
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:147
 msgid "Create a mapping"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:142
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:149
 msgid ""
 "Mappings allow Metabase to automatically add and remove users from groups "
 "based on the membership information provided by the\n"
@@ -1695,7 +1822,7 @@ msgid ""
 "failsafe measure."
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:147
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:154
 msgid "Distinguished Name"
 msgstr ""
 
@@ -1713,8 +1840,7 @@ msgstr ""
 
 #: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:122
 msgid ""
-"They won't work any more, and can't be restored, but you can create new "
-"links."
+"They won't work anymore, and can't be restored, but you can create new links."
 msgstr ""
 
 #: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:149
@@ -1768,14 +1894,14 @@ msgid "Generate Key"
 msgstr ""
 
 #: frontend/src/metabase/admin/settings/components/widgets/SettingToggle.jsx:11
-#: frontend/src/metabase/admin/settings/selectors.js:75
-#: frontend/src/metabase/admin/settings/selectors.js:84
+#: frontend/src/metabase/admin/settings/selectors.js:77
+#: frontend/src/metabase/admin/settings/selectors.js:86
 msgid "Enabled"
 msgstr ""
 
 #: frontend/src/metabase/admin/settings/components/widgets/SettingToggle.jsx:11
-#: frontend/src/metabase/admin/settings/selectors.js:80
-#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:104
+#: frontend/src/metabase/admin/settings/selectors.js:82
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:103
 msgid "Disabled"
 msgstr ""
 
@@ -1787,261 +1913,257 @@ msgstr ""
 msgid "Setup"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:26
+#: frontend/src/metabase/admin/settings/selectors.js:27
 msgid "General"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:30
+#: frontend/src/metabase/admin/settings/selectors.js:32
 msgid "Site Name"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:35
+#: frontend/src/metabase/admin/settings/selectors.js:37
 msgid "Site URL"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:40
+#: frontend/src/metabase/admin/settings/selectors.js:42
 msgid "Email Address for Help Requests"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:45
+#: frontend/src/metabase/admin/settings/selectors.js:47
 msgid "Report Timezone"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:48
+#: frontend/src/metabase/admin/settings/selectors.js:50
 msgid "Database Default"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:51
+#: frontend/src/metabase/admin/settings/selectors.js:53
 msgid "Select a timezone"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:52
+#: frontend/src/metabase/admin/settings/selectors.js:54
 msgid ""
 "Not all databases support timezones, in which case this setting won't take "
 "effect."
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:57
+#: frontend/src/metabase/admin/settings/selectors.js:59
 msgid "Language"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:62
+#: frontend/src/metabase/admin/settings/selectors.js:64
 msgid "Select a language"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:67
+#: frontend/src/metabase/admin/settings/selectors.js:69
 msgid "Anonymous Tracking"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:72
+#: frontend/src/metabase/admin/settings/selectors.js:74
 msgid "Friendly Table and Field Names"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:78
+#: frontend/src/metabase/admin/settings/selectors.js:80
 msgid "Only replace underscores and dashes with spaces"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:88
+#: frontend/src/metabase/admin/settings/selectors.js:90
 msgid "Enable Nested Queries"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:94
+#: frontend/src/metabase/admin/settings/selectors.js:96
 msgid "Updates"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:98
+#: frontend/src/metabase/admin/settings/selectors.js:101
 msgid "Check for updates"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:108
+#: frontend/src/metabase/admin/settings/selectors.js:112
 msgid "SMTP Host"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:116
+#: frontend/src/metabase/admin/settings/selectors.js:120
 msgid "SMTP Port"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:120
-#: frontend/src/metabase/admin/settings/selectors.js:216
+#: frontend/src/metabase/admin/settings/selectors.js:124
+#: frontend/src/metabase/admin/settings/selectors.js:224
 msgid "That's not a valid port number"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:124
+#: frontend/src/metabase/admin/settings/selectors.js:128
 msgid "SMTP Security"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:132
+#: frontend/src/metabase/admin/settings/selectors.js:136
 msgid "SMTP Username"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:139
+#: frontend/src/metabase/admin/settings/selectors.js:143
 msgid "SMTP Password"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:146
+#: frontend/src/metabase/admin/settings/selectors.js:150
 msgid "From Address"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:159
+#: frontend/src/metabase/admin/settings/selectors.js:164
 msgid "Slack API Token"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:161
+#: frontend/src/metabase/admin/settings/selectors.js:166
 msgid "Enter the token you received from Slack"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:178
+#: frontend/src/metabase/admin/settings/selectors.js:183
 msgid "Single Sign-On"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:199
+#: frontend/src/metabase/admin/settings/selectors.js:207
 msgid "LDAP Authentication"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:205
+#: frontend/src/metabase/admin/settings/selectors.js:213
 msgid "LDAP Host"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:213
+#: frontend/src/metabase/admin/settings/selectors.js:221
 msgid "LDAP Port"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:220
+#: frontend/src/metabase/admin/settings/selectors.js:228
 msgid "LDAP Security"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:228
+#: frontend/src/metabase/admin/settings/selectors.js:236
 msgid "Username or DN"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:233
-#: frontend/src/metabase/auth/containers/LoginApp.jsx:178
+#: frontend/src/metabase/admin/settings/selectors.js:241
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:188
 #: frontend/src/metabase/user/components/UserSettings.jsx:72
 msgid "Password"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:238
+#: frontend/src/metabase/admin/settings/selectors.js:246
 msgid "User search base"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:244
+#: frontend/src/metabase/admin/settings/selectors.js:252
 msgid "User filter"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:250
+#: frontend/src/metabase/admin/settings/selectors.js:258
+msgid "Check your parentheses"
+msgstr ""
+
+#: frontend/src/metabase/admin/settings/selectors.js:264
 msgid "Email attribute"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:255
+#: frontend/src/metabase/admin/settings/selectors.js:269
 msgid "First name attribute"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:260
+#: frontend/src/metabase/admin/settings/selectors.js:274
 msgid "Last name attribute"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:265
+#: frontend/src/metabase/admin/settings/selectors.js:279
 msgid "Synchronize group memberships"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:271
-msgid "\"Group search base"
+#: frontend/src/metabase/admin/settings/selectors.js:285
+msgid "Group search base"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:280
+#: frontend/src/metabase/admin/settings/selectors.js:294
 msgid "Maps"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:284
+#: frontend/src/metabase/admin/settings/selectors.js:299
 msgid "Map tile server URL"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:285
+#: frontend/src/metabase/admin/settings/selectors.js:300
 msgid "Metabase uses OpenStreetMaps by default."
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:290
+#: frontend/src/metabase/admin/settings/selectors.js:305
 msgid "Custom Maps"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:291
+#: frontend/src/metabase/admin/settings/selectors.js:306
 msgid ""
 "Add your own GeoJSON files to enable different region map visualizations"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:298
+#: frontend/src/metabase/admin/settings/selectors.js:313
 msgid "Public Sharing"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:302
+#: frontend/src/metabase/admin/settings/selectors.js:318
 msgid "Enable Public Sharing"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:307
+#: frontend/src/metabase/admin/settings/selectors.js:323
 msgid "Shared Dashboards"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:313
+#: frontend/src/metabase/admin/settings/selectors.js:329
 msgid "Shared Questions"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:320
+#: frontend/src/metabase/admin/settings/selectors.js:336
 msgid "Embedding in other Applications"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:346
+#: frontend/src/metabase/admin/settings/selectors.js:363
 msgid "Enable Embedding Metabase in other Applications"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:356
+#: frontend/src/metabase/admin/settings/selectors.js:373
 msgid "Embedding secret key"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:362
+#: frontend/src/metabase/admin/settings/selectors.js:379
 msgid "Embedded Dashboards"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:368
+#: frontend/src/metabase/admin/settings/selectors.js:385
 msgid "Embedded Questions"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:375
+#: frontend/src/metabase/admin/settings/selectors.js:392
 msgid "Caching"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:379
+#: frontend/src/metabase/admin/settings/selectors.js:397
 msgid "Enable Caching"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:384
+#: frontend/src/metabase/admin/settings/selectors.js:402
 msgid "Minimum Query Duration"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:391
+#: frontend/src/metabase/admin/settings/selectors.js:409
 msgid "Cache Time-To-Live (TTL) multiplier"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:398
+#: frontend/src/metabase/admin/settings/selectors.js:416
 msgid "Max Cache Entry Size"
 msgstr ""
 
-#: frontend/src/metabase/admin/settings/selectors.js:406
-msgid "X-Rays"
-msgstr ""
-
-#: frontend/src/metabase/admin/settings/selectors.js:410
-msgid "Enable X-Rays"
-msgstr ""
-
-#: frontend/src/metabase/alert/alert.js:66
+#: frontend/src/metabase/alert/alert.js:60
 msgid "Your alert is all set up."
 msgstr ""
 
-#: frontend/src/metabase/alert/alert.js:100
+#: frontend/src/metabase/alert/alert.js:101
 msgid "Your alert was updated."
 msgstr ""
 
-#: frontend/src/metabase/alert/alert.js:156
+#: frontend/src/metabase/alert/alert.js:149
 msgid "The alert was successfully deleted."
 msgstr ""
 
@@ -2049,9 +2171,9 @@ msgstr ""
 msgid "Please enter a valid formatted email address."
 msgstr ""
 
-#: frontend/src/metabase/auth/auth.js:113
-#: frontend/src/metabase/setup/components/UserStep.jsx:107
-#: frontend/src/metabase/user/components/SetUserPassword.jsx:67
+#: frontend/src/metabase/auth/auth.js:116
+#: frontend/src/metabase/setup/components/UserStep.jsx:110
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:69
 msgid "Passwords do not match"
 msgstr ""
 
@@ -2093,86 +2215,96 @@ msgstr ""
 msgid "Check your email for instructions on how to reset your password."
 msgstr ""
 
-#: frontend/src/metabase/auth/containers/LoginApp.jsx:126
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:128
 msgid "Sign in to Metabase"
 msgstr ""
 
-#: frontend/src/metabase/auth/containers/LoginApp.jsx:136
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:138
 msgid "OR"
 msgstr ""
 
-#: frontend/src/metabase/auth/containers/LoginApp.jsx:155
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:157
 msgid "Username or email address"
 msgstr ""
 
-#: frontend/src/metabase/auth/containers/LoginApp.jsx:195
-msgid "Remember Me:"
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:211
+msgid "Remember Me"
+msgstr ""
+
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:217
+msgid "Sign in"
 msgstr ""
 
-#: frontend/src/metabase/auth/containers/LoginApp.jsx:219
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:230
 msgid "I seem to have forgotten my password"
 msgstr ""
 
-#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:112
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:102
+msgid "request a new reset email"
+msgstr ""
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:120
 msgid "Whoops, that's an expired link"
 msgstr ""
 
-#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:114
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:122
 msgid ""
 "For security reasons, password reset links expire after a little while. If "
 "you still need\n"
-"to reset your password, you can <Link to=\"/auth/forgot_password\" className="
-"\"link\">request a new reset email</Link>."
+"to reset your password, you can {0}."
 msgstr ""
 
-#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:139
-#: frontend/src/metabase/user/components/SetUserPassword.jsx:122
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:147
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:126
 msgid "New password"
 msgstr ""
 
-#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:141
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:149
 msgid "To keep your data secure, passwords {0}"
 msgstr ""
 
-#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:155
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:163
 msgid "Create a new password"
 msgstr ""
 
-#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:162
-#: frontend/src/metabase/user/components/SetUserPassword.jsx:137
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:170
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:141
 msgid "Make sure its secure like the instructions above"
 msgstr ""
 
-#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:176
-#: frontend/src/metabase/user/components/SetUserPassword.jsx:146
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:184
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:150
 msgid "Confirm new password"
 msgstr ""
 
-#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:183
-#: frontend/src/metabase/user/components/SetUserPassword.jsx:155
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:191
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:159
 msgid "Make sure it matches the one you just entered"
 msgstr ""
 
-#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:208
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:216
 msgid "Your password has been reset."
 msgstr ""
 
-#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:214
-#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:219
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:222
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:227
 msgid "Sign in with your new password"
 msgstr ""
 
 #: frontend/src/metabase/components/ActionButton.jsx:53
-#: frontend/src/metabase/pulse/components/PulseEdit.jsx:196
-#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:269
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:182
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:184
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:251
 msgid "Save failed"
 msgstr ""
 
 #: frontend/src/metabase/components/ActionButton.jsx:54
 #: frontend/src/metabase/components/SaveStatus.jsx:60
-#: frontend/src/metabase/pulse/components/PulseEdit.jsx:197
-#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:243
-#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:270
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:183
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:124
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:185
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:225
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:252
 msgid "Saved"
 msgstr ""
 
@@ -2180,63 +2312,95 @@ msgstr ""
 msgid "Ok"
 msgstr ""
 
-#: frontend/src/metabase/components/Archived.jsx:10
-msgid "This {0} has been archived"
+#: frontend/src/metabase/components/ArchiveCollectionModal.jsx:40
+msgid "Archive this collection?"
 msgstr ""
 
-#: frontend/src/metabase/components/Archived.jsx:15
-#: frontend/src/metabase/dashboards/containers/Dashboards.jsx:170
-#: frontend/src/metabase/questions/containers/QuestionIndex.jsx:94
-msgid "View the archive"
+#: frontend/src/metabase/components/ArchiveCollectionModal.jsx:45
+msgid ""
+"The dashboards, collections, and pulses in this collection will also be "
+"archived."
 msgstr ""
 
-#: frontend/src/metabase/components/ArchivedItem.jsx:24
-msgid "Unarchive this question"
+#: frontend/src/metabase/components/ArchiveCollectionModal.jsx:49
+#: frontend/src/metabase/components/CollectionLanding.jsx:587
+#: frontend/src/metabase/components/EntityItem.jsx:54
+#: frontend/src/metabase/components/EntityMenu.info.js:31
+#: frontend/src/metabase/components/EntityMenu.info.js:87
+#: frontend/src/metabase/home/containers/ArchiveApp.jsx:48
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:195
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:200
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:201
+#: frontend/src/metabase/query_builder/containers/ArchiveQuestionModal.jsx:40
+#: frontend/src/metabase/query_builder/containers/ArchiveQuestionModal.jsx:53
+#: frontend/src/metabase/routes.jsx:199
+msgid "Archive"
 msgstr ""
 
-#: frontend/src/metabase/components/ArchivedItem.jsx:25
+#: frontend/src/metabase/components/ArchivedItem.jsx:39
 msgid "Unarchive this {0}"
 msgstr ""
 
+#: frontend/src/metabase/components/BrowseApp.jsx:93
+#: frontend/src/metabase/components/BrowseApp.jsx:152
+#: frontend/src/metabase/components/BrowseApp.jsx:243
+#: frontend/src/metabase/containers/Overworld.jsx:222
+msgid "Our data"
+msgstr ""
+
+#: frontend/src/metabase/components/BrowseApp.jsx:118
+msgid "X-ray this schema"
+msgstr ""
+
+#: frontend/src/metabase/components/BrowseApp.jsx:188
+#: frontend/src/metabase/reference/databases/TableSidebar.jsx:51
+msgid "X-ray this table"
+msgstr ""
+
+#: frontend/src/metabase/components/BrowseApp.jsx:201
+#: frontend/src/metabase/containers/Overworld.jsx:249
+msgid "Learn about this table"
+msgstr ""
+
 #: frontend/src/metabase/components/Button.info.js:11
 #: frontend/src/metabase/components/Button.info.js:12
 #: frontend/src/metabase/components/Button.info.js:13
 msgid "Clickity click"
 msgstr ""
 
-#: frontend/src/metabase/components/ButtonWithStatus.jsx:8
+#: frontend/src/metabase/components/ButtonWithStatus.jsx:9
 msgid "Saved!"
 msgstr ""
 
-#: frontend/src/metabase/components/ButtonWithStatus.jsx:9
+#: frontend/src/metabase/components/ButtonWithStatus.jsx:10
 msgid "Saving failed."
 msgstr ""
 
-#: frontend/src/metabase/components/Calendar.jsx:109
+#: frontend/src/metabase/components/Calendar.jsx:119
 msgid "Su"
 msgstr ""
 
-#: frontend/src/metabase/components/Calendar.jsx:109
+#: frontend/src/metabase/components/Calendar.jsx:119
 msgid "Mo"
 msgstr ""
 
-#: frontend/src/metabase/components/Calendar.jsx:109
+#: frontend/src/metabase/components/Calendar.jsx:119
 msgid "Tu"
 msgstr ""
 
-#: frontend/src/metabase/components/Calendar.jsx:109
+#: frontend/src/metabase/components/Calendar.jsx:119
 msgid "We"
 msgstr ""
 
-#: frontend/src/metabase/components/Calendar.jsx:109
+#: frontend/src/metabase/components/Calendar.jsx:119
 msgid "Th"
 msgstr ""
 
-#: frontend/src/metabase/components/Calendar.jsx:109
+#: frontend/src/metabase/components/Calendar.jsx:119
 msgid "Fr"
 msgstr ""
 
-#: frontend/src/metabase/components/Calendar.jsx:109
+#: frontend/src/metabase/components/Calendar.jsx:119
 msgid "Sa"
 msgstr ""
 
@@ -2257,48 +2421,103 @@ msgstr ""
 msgid "To send {0}, an admin needs to set up {1} integration."
 msgstr ""
 
-#: frontend/src/metabase/components/CopyButton.jsx:35
-msgid "Copied!"
+#: frontend/src/metabase/components/CollectionEmptyState.jsx:15
+msgid "This collection is empty, like a blank canvas"
 msgstr ""
 
-#: frontend/src/metabase/components/CreateDashboardModal.jsx:75
-msgid "Create dashboard"
+#: frontend/src/metabase/components/CollectionEmptyState.jsx:16
+msgid ""
+"You can use collections to organize and group dashboards, questions and "
+"pulses for your team or yourself"
 msgstr ""
 
-#: frontend/src/metabase/components/CreateDashboardModal.jsx:83
-#: frontend/src/metabase/questions/containers/CollectionEditorForm.jsx:39
-msgid "Create"
+#: frontend/src/metabase/components/CollectionEmptyState.jsx:28
+msgid "Create another collection"
 msgstr ""
 
-#: frontend/src/metabase/components/CreateDashboardModal.jsx:97
-msgid "What is the name of your dashboard?"
+#: frontend/src/metabase/components/CollectionLanding.jsx:61
+msgid "Dashboards let you collect and share data in one place."
 msgstr ""
 
-#: frontend/src/metabase/components/CreateDashboardModal.jsx:105
-#: frontend/src/metabase/containers/SaveQuestionModal.jsx:229
-#: frontend/src/metabase/lib/core.js:45
-#: frontend/src/metabase/questions/containers/CollectionEditorForm.jsx:84
-#: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:156
-#: frontend/src/metabase/reference/databases/FieldDetail.jsx:211
-#: frontend/src/metabase/reference/databases/TableDetail.jsx:189
-#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:203
-#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:207
-#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:207
-#: frontend/src/metabase/visualizations/lib/settings.js:166
-msgid "Description"
+#: frontend/src/metabase/components/CollectionLanding.jsx:70
+msgid ""
+"Pulses let you send out the latest data to your team on a schedule via email "
+"or slack."
 msgstr ""
 
-#: frontend/src/metabase/components/CreateDashboardModal.jsx:112
-#: frontend/src/metabase/containers/SaveQuestionModal.jsx:236
-#: frontend/src/metabase/questions/containers/CollectionEditorForm.jsx:87
-msgid "It's optional but oh, so helpful"
+#: frontend/src/metabase/components/CollectionLanding.jsx:79
+msgid "Questions are a saved look at your data."
+msgstr ""
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:246
+msgid "Edit the permissions for this collection"
+msgstr ""
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:275
+msgid "Pins"
+msgstr ""
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:329
+msgid "Drag something here to pin it to the top"
+msgstr ""
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:411
+#: frontend/src/metabase/components/CollectionLanding.jsx:434
+msgid "Drag here to un-pin"
+msgstr ""
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:469
+msgid "{0} item selected"
+msgid_plural "{0} items selected"
+msgstr[0] ""
+msgstr[1] ""
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:487
+msgid "Move {0} items?"
+msgstr ""
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:488
+msgid "Move \"{0}\"?"
+msgstr ""
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:594
+#: frontend/src/metabase/components/EntityMenu.info.js:29
+#: frontend/src/metabase/components/EntityMenu.info.js:85
+#: frontend/src/metabase/containers/CollectionMoveModal.jsx:78
+msgid "Move"
+msgstr ""
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:656
+msgid "Edit this collection"
+msgstr ""
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:664
+msgid "Archive this collection"
+msgstr ""
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:679
+msgid "View the archive"
+msgstr ""
+
+#: frontend/src/metabase/components/CollectionList.jsx:64
+#: frontend/src/metabase/entities/collections.js:148
+msgid "My personal collection"
+msgstr ""
+
+#: frontend/src/metabase/components/CollectionList.jsx:106
+#: frontend/src/metabase/containers/CollectionForm.jsx:9
+msgid "New collection"
 msgstr ""
 
-#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:212
+#: frontend/src/metabase/components/CopyButton.jsx:35
+msgid "Copied!"
+msgstr ""
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:216
 msgid "Use an SSH-tunnel for database connections"
 msgstr ""
 
-#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:214
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:218
 msgid ""
 "Some database installations can only be accessed by connecting through an "
 "SSH bastion host.\n"
@@ -2307,62 +2526,88 @@ msgid ""
 "Enabling this is usually slower than a direct connection."
 msgstr ""
 
-#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:243
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:247
+msgid "Use the Java Virtual Machine (JVM) timezone"
+msgstr ""
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:249
+msgid ""
+"We suggest you leave this off unless you're doing manual timezone casting "
+"in\n"
+"many or most of your queries with this data."
+msgstr ""
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:274
 msgid ""
 "This is a large database, so let me choose when Metabase syncs and scans"
 msgstr ""
 
-#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:245
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:276
 msgid ""
-"By default, Metabase does a lightweight hourly sync, and an intensive daily "
+"By default, Metabase does a lightweight hourly sync and an intensive daily "
 "scan of field values.\n"
 "If you have a large database, we recommend turning this on and reviewing "
 "when and how often the field value scans happen."
 msgstr ""
 
-#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:261
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:292
 msgid "{0} to generate a Client ID and Client Secret for your project."
 msgstr ""
 
-#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:266
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:294
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:321
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:356
+msgid "Click here"
+msgstr ""
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:297
 msgid "Choose \"Other\" as the application type. Name it whatever you'd like."
 msgstr ""
 
-#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:288
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:319
 msgid "{0} to get an auth code"
 msgstr ""
 
-#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:300
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:331
 msgid "with Google Drive permissions"
 msgstr ""
 
-#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:320
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:351
 msgid ""
 "To use Metabase with this data you must enable API access in the Google "
 "Developers Console."
 msgstr ""
 
-#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:323
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:354
 msgid "{0} to go to the console if you haven't already done so."
 msgstr ""
 
-#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:372
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:403
 msgid "How would you like to refer to this database?"
 msgstr ""
 
-#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:399
-#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:96
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:430
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:97
 #: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:240
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:188
 #: frontend/src/metabase/setup/components/DatabaseSchedulingStep.jsx:74
 #: frontend/src/metabase/setup/components/PreferencesStep.jsx:116
-#: frontend/src/metabase/setup/components/UserStep.jsx:303
+#: frontend/src/metabase/setup/components/UserStep.jsx:308
 msgid "Next"
 msgstr ""
 
-#: frontend/src/metabase/components/DeleteModalWithConfirm.jsx:79
+#: frontend/src/metabase/components/DeleteModalWithConfirm.jsx:80
 msgid "Delete this {0}"
 msgstr ""
 
+#: frontend/src/metabase/components/EntityItem.jsx:42
+msgid "Pin this item"
+msgstr ""
+
+#: frontend/src/metabase/components/EntityItem.jsx:48
+msgid "Move this item"
+msgstr ""
+
 #: frontend/src/metabase/components/EntityMenu.info.js:24
 #: frontend/src/metabase/components/EntityMenu.info.js:80
 msgid "Edit this question"
@@ -2380,34 +2625,11 @@ msgstr ""
 msgid "View revision history"
 msgstr ""
 
-#: frontend/src/metabase/components/EntityMenu.info.js:29
-#: frontend/src/metabase/components/EntityMenu.info.js:85
-#: frontend/src/metabase/questions/components/ActionHeader.jsx:57
-#: frontend/src/metabase/questions/containers/MoveToCollection.jsx:68
-msgid "Move"
-msgstr ""
-
 #: frontend/src/metabase/components/EntityMenu.info.js:29
 #: frontend/src/metabase/components/EntityMenu.info.js:85
 msgid "Move action"
 msgstr ""
 
-#: frontend/src/metabase/components/EntityMenu.info.js:31
-#: frontend/src/metabase/components/EntityMenu.info.js:87
-#: frontend/src/metabase/dashboards/components/DashboardList.jsx:40
-#: frontend/src/metabase/dashboards/containers/DashboardsArchive.jsx:78
-#: frontend/src/metabase/query_builder/containers/ArchiveQuestionModal.jsx:40
-#: frontend/src/metabase/query_builder/containers/ArchiveQuestionModal.jsx:53
-#: frontend/src/metabase/questions/components/ActionHeader.jsx:71
-#: frontend/src/metabase/questions/components/Item.jsx:106
-#: frontend/src/metabase/questions/containers/Archive.jsx:61
-#: frontend/src/metabase/questions/containers/ArchiveCollectionWidget.jsx:51
-#: frontend/src/metabase/questions/containers/EntityList.jsx:100
-#: frontend/src/metabase/questions/selectors.js:223
-#: frontend/src/metabase/routes.jsx:250
-msgid "Archive"
-msgstr ""
-
 #: frontend/src/metabase/components/EntityMenu.info.js:33
 #: frontend/src/metabase/components/EntityMenu.info.js:89
 msgid "Archive action"
@@ -2415,8 +2637,8 @@ msgstr ""
 
 #: frontend/src/metabase/components/EntityMenu.info.js:45
 #: frontend/src/metabase/components/EntityMenu.info.js:97
-#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:343
-#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:356
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:329
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:342
 msgid "Add to dashboard"
 msgstr ""
 
@@ -2440,8 +2662,8 @@ msgstr ""
 #: frontend/src/metabase/components/EntityMenu.info.js:67
 #: frontend/src/metabase/components/EntityMenu.info.js:113
 #: frontend/src/metabase/components/EntityMenu.info.js:115
-#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:462
-#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:467
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:449
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:454
 msgid "Get alerts about this"
 msgstr ""
 
@@ -2450,43 +2672,65 @@ msgstr ""
 msgid "View the SQL"
 msgstr ""
 
-#: frontend/src/metabase/components/FieldValuesWidget.jsx:237
+#: frontend/src/metabase/components/EntitySegments.jsx:18
+msgid "Segments for this"
+msgstr ""
+
+#: frontend/src/metabase/components/ErrorDetails.jsx:20
+msgid "Show error details"
+msgstr ""
+
+#: frontend/src/metabase/components/ErrorDetails.jsx:26
+msgid "Here's the full error message"
+msgstr ""
+
+#: frontend/src/metabase/components/ExplorePane.jsx:19
+msgid "Hi, Metabot here."
+msgstr ""
+
+#: frontend/src/metabase/components/ExplorePane.jsx:95
+msgid "Based on the schema"
+msgstr ""
+
+#: frontend/src/metabase/components/ExplorePane.jsx:174
+msgid "A look at your"
+msgstr ""
+
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:234
 msgid "Search the list"
 msgstr ""
 
-#: frontend/src/metabase/components/FieldValuesWidget.jsx:241
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:238
 msgid "Search by {0}"
 msgstr ""
 
-#: frontend/src/metabase/components/FieldValuesWidget.jsx:243
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:240
 msgid " or enter an ID"
 msgstr ""
 
-#: frontend/src/metabase/components/FieldValuesWidget.jsx:247
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:244
 msgid "Enter an ID"
 msgstr ""
 
-#: frontend/src/metabase/components/FieldValuesWidget.jsx:249
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:246
 msgid "Enter a number"
 msgstr ""
 
-#: frontend/src/metabase/components/FieldValuesWidget.jsx:251
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:248
 msgid "Enter some text"
 msgstr ""
 
-#: frontend/src/metabase/components/FieldValuesWidget.jsx:357
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:355
 msgid "No matching {0} found."
 msgstr ""
 
-#: frontend/src/metabase/components/FieldValuesWidget.jsx:365
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:363
 msgid "Including every option in your filter probably won’t do much…"
 msgstr ""
 
-#: frontend/src/metabase/components/Header.jsx:93
+#: frontend/src/metabase/components/Header.jsx:97
 #: frontend/src/metabase/components/HeaderBar.jsx:45
 #: frontend/src/metabase/components/ListItem.jsx:37
-#: frontend/src/metabase/components/SortableItemList.jsx:78
-#: frontend/src/metabase/questions/components/Item.jsx:191
 #: frontend/src/metabase/reference/components/Detail.jsx:47
 #: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:158
 #: frontend/src/metabase/reference/databases/FieldDetail.jsx:213
@@ -2497,11 +2741,11 @@ msgstr ""
 msgid "No description yet"
 msgstr ""
 
-#: frontend/src/metabase/components/Header.jsx:108
+#: frontend/src/metabase/components/Header.jsx:112
 msgid "New {0}"
 msgstr ""
 
-#: frontend/src/metabase/components/Header.jsx:119
+#: frontend/src/metabase/components/Header.jsx:123
 msgid "Asked by {0}"
 msgstr ""
 
@@ -2521,40 +2765,60 @@ msgstr ""
 msgid "Reverted to an earlier revision and {0}"
 msgstr ""
 
-#: frontend/src/metabase/components/HistoryModal.jsx:80
-#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:392
-#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:53
+#: frontend/src/metabase/components/HistoryModal.jsx:82
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:289
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:379
+#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:54
 msgid "Revision history"
 msgstr ""
 
-#: frontend/src/metabase/components/HistoryModal.jsx:91
+#: frontend/src/metabase/components/HistoryModal.jsx:90
 msgid "When"
 msgstr ""
 
-#: frontend/src/metabase/components/HistoryModal.jsx:92
+#: frontend/src/metabase/components/HistoryModal.jsx:91
 msgid "Who"
 msgstr ""
 
-#: frontend/src/metabase/components/HistoryModal.jsx:93
+#: frontend/src/metabase/components/HistoryModal.jsx:92
 msgid "What"
 msgstr ""
 
-#: frontend/src/metabase/components/HistoryModal.jsx:114
+#: frontend/src/metabase/components/HistoryModal.jsx:113
 msgid "Revert"
 msgstr ""
 
-#: frontend/src/metabase/components/HistoryModal.jsx:115
+#: frontend/src/metabase/components/HistoryModal.jsx:114
 msgid "Reverting…"
 msgstr ""
 
-#: frontend/src/metabase/components/HistoryModal.jsx:116
+#: frontend/src/metabase/components/HistoryModal.jsx:115
 msgid "Revert failed"
 msgstr ""
 
-#: frontend/src/metabase/components/HistoryModal.jsx:117
+#: frontend/src/metabase/components/HistoryModal.jsx:116
 msgid "Reverted"
 msgstr ""
 
+#: frontend/src/metabase/components/ItemTypeFilterBar.jsx:13
+msgid "Everything"
+msgstr ""
+
+#: frontend/src/metabase/components/ItemTypeFilterBar.jsx:18
+#: frontend/src/metabase/home/containers/SearchApp.jsx:73
+msgid "Dashboards"
+msgstr ""
+
+#: frontend/src/metabase/components/ItemTypeFilterBar.jsx:23
+#: frontend/src/metabase/home/containers/SearchApp.jsx:119
+msgid "Questions"
+msgstr ""
+
+#: frontend/src/metabase/components/ItemTypeFilterBar.jsx:28
+#: frontend/src/metabase/routes.jsx:320
+msgid "Pulses"
+msgstr ""
+
 #: frontend/src/metabase/components/LeftNavPane.jsx:36
 #: frontend/src/metabase/query_builder/components/dataref/DataReference.jsx:86
 msgid "Back"
@@ -2564,69 +2828,43 @@ msgstr ""
 msgid "Find..."
 msgstr ""
 
-#: frontend/src/metabase/components/LoadingAndErrorWrapper.jsx:47
+#: frontend/src/metabase/components/LoadingAndErrorWrapper.jsx:48
 msgid "An error occured"
 msgstr ""
 
-#: frontend/src/metabase/components/LoadingAndErrorWrapper.jsx:36
+#: frontend/src/metabase/components/LoadingAndErrorWrapper.jsx:35
 msgid "Loading..."
 msgstr ""
 
-#: frontend/src/metabase/components/NewsletterForm.jsx:70
+#: frontend/src/metabase/components/NewsletterForm.jsx:71
 msgid "Metabase Newsletter"
 msgstr ""
 
-#: frontend/src/metabase/components/NewsletterForm.jsx:77
+#: frontend/src/metabase/components/NewsletterForm.jsx:81
 msgid "Get infrequent emails about new releases and feature updates."
 msgstr ""
 
-#: frontend/src/metabase/components/NewsletterForm.jsx:95
+#: frontend/src/metabase/components/NewsletterForm.jsx:99
 msgid "Subscribe"
 msgstr ""
 
-#: frontend/src/metabase/components/NewsletterForm.jsx:102
+#: frontend/src/metabase/components/NewsletterForm.jsx:106
 msgid "You're subscribed. Thanks for using Metabase!"
 msgstr ""
 
-#: frontend/src/metabase/components/NotFound.jsx:11
-msgid "We're a little lost..."
-msgstr ""
-
-#: frontend/src/metabase/components/NotFound.jsx:13
-msgid "The page you asked for couldn't be found"
-msgstr ""
-
-#: frontend/src/metabase/components/NotFound.jsx:15
-msgid ""
-"You might've been tricked by a ninja, but in all likelihood, you were just "
-"given a bad link."
-msgstr ""
-
-#: frontend/src/metabase/components/NotFound.jsx:16
-msgid "You can always:"
-msgstr ""
-
-#: frontend/src/metabase/components/NotFound.jsx:19
-msgid "Ask a new question."
-msgstr ""
-
-#: frontend/src/metabase/components/NotFound.jsx:21
-msgid "or"
-msgstr ""
-
-#: frontend/src/metabase/components/NotFound.jsx:29
-msgid "Take a kitten break."
-msgstr ""
-
-#: frontend/src/metabase/components/PasswordReveal.jsx:26
+#: frontend/src/metabase/components/PasswordReveal.jsx:27
 msgid "Temporary Password"
 msgstr ""
 
-#: frontend/src/metabase/components/PasswordReveal.jsx:67
+#: frontend/src/metabase/components/PasswordReveal.jsx:68
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:334
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:349
 msgid "Hide"
 msgstr ""
 
-#: frontend/src/metabase/components/PasswordReveal.jsx:67
+#: frontend/src/metabase/components/PasswordReveal.jsx:68
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:335
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:350
 msgid "Show"
 msgstr ""
 
@@ -2647,7 +2885,7 @@ msgid "Error:"
 msgstr ""
 
 #: frontend/src/metabase/components/SchedulePicker.jsx:23
-msgid "\"Sunday"
+msgid "Sunday"
 msgstr ""
 
 #: frontend/src/metabase/components/SchedulePicker.jsx:24
@@ -2719,4696 +2957,6262 @@ msgid "A component used to make a selection"
 msgstr ""
 
 #: frontend/src/metabase/components/Select.info.js:20
-#: frontend/src/metabase/components/Select.info.js:25
+#: frontend/src/metabase/components/Select.info.js:28
 msgid "Selected"
 msgstr ""
 
-#: frontend/src/metabase/components/Select.jsx:280
+#: frontend/src/metabase/components/Select.jsx:281
 msgid "Nothing to select"
 msgstr ""
 
-#: frontend/src/metabase/components/Unauthorized.jsx:10
-msgid "Sorry, you don’t have permission to see that."
-msgstr ""
-
 #: frontend/src/metabase/components/form/FormMessage.jsx:5
 msgid "Unknown error encountered"
 msgstr ""
 
-#: frontend/src/metabase/containers/AddToDashSelectDashModal.jsx:79
-msgid "Add Question to Dashboard"
+#: frontend/src/metabase/components/form/StandardForm.jsx:63
+#: frontend/src/metabase/nav/containers/Navbar.jsx:300
+msgid "Create"
+msgstr ""
+
+#: frontend/src/metabase/containers/AddToDashSelectDashModal.jsx:51
+msgid "Add this question to a dashboard"
+msgstr ""
+
+#: frontend/src/metabase/containers/AddToDashSelectDashModal.jsx:61
+msgid "Create a new dashboard"
 msgstr ""
 
-#: frontend/src/metabase/containers/AddToDashSelectDashModal.jsx:92
-msgid "Add to new dashboard"
+#: frontend/src/metabase/containers/DashboardForm.jsx:9
+msgid "Create dashboard"
 msgstr ""
 
-#: frontend/src/metabase/containers/EntitySearch.jsx:33
-#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:318
-#: frontend/src/metabase/visualizations/visualizations/Table.jsx:40
-#: frontend/src/metabase/xray/containers/TableXRay.jsx:61
+#: frontend/src/metabase/containers/EntitySearch.jsx:35
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:324
+#: frontend/src/metabase/visualizations/visualizations/Table.jsx:44
 msgid "Table"
 msgstr ""
 
-#: frontend/src/metabase/containers/EntitySearch.jsx:40
-#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:293
+#: frontend/src/metabase/containers/EntitySearch.jsx:42
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:299
 msgid "Database"
 msgstr ""
 
-#: frontend/src/metabase/containers/EntitySearch.jsx:47
+#: frontend/src/metabase/containers/EntitySearch.jsx:49
 msgid "Creator"
 msgstr ""
 
-#: frontend/src/metabase/containers/EntitySearch.jsx:236
-#: frontend/src/metabase/dashboards/containers/Dashboards.jsx:221
-#: frontend/src/metabase/dashboards/containers/DashboardsArchive.jsx:118
+#: frontend/src/metabase/containers/EntitySearch.jsx:238
 msgid "No results found"
 msgstr ""
 
-#: frontend/src/metabase/containers/EntitySearch.jsx:237
-#: frontend/src/metabase/dashboards/containers/DashboardsArchive.jsx:119
+#: frontend/src/metabase/containers/EntitySearch.jsx:239
 msgid "Try adjusting your filter to find what you’re looking for."
 msgstr ""
 
-#: frontend/src/metabase/containers/EntitySearch.jsx:256
+#: frontend/src/metabase/containers/EntitySearch.jsx:258
 msgid "View by"
 msgstr ""
 
-#: frontend/src/metabase/containers/EntitySearch.jsx:488
+#: frontend/src/metabase/containers/EntitySearch.jsx:494
 #: frontend/src/metabase/query_builder/components/AggregationWidget.jsx:69
 #: frontend/src/metabase/tutorial/TutorialModal.jsx:34
 msgid "of"
 msgstr ""
 
-#: frontend/src/metabase/containers/SaveQuestionModal.jsx:162
-msgid "Replace or save as new?"
+#: frontend/src/metabase/containers/ErrorPages.jsx:24
+msgid "Something's gone wrong"
 msgstr ""
 
-#: frontend/src/metabase/containers/SaveQuestionModal.jsx:171
-msgid "Replace original question, \"{0}\""
+#: frontend/src/metabase/containers/ErrorPages.jsx:25
+msgid ""
+"We've run into an error. You can try refreshing the page, or just go back."
 msgstr ""
 
-#: frontend/src/metabase/containers/SaveQuestionModal.jsx:176
-msgid "Save as new question"
+#: frontend/src/metabase/containers/ErrorPages.jsx:44
+msgid "We're a little lost..."
 msgstr ""
 
-#: frontend/src/metabase/containers/SaveQuestionModal.jsx:185
-msgid "First, save your question"
+#: frontend/src/metabase/containers/ErrorPages.jsx:45
+msgid "The page you asked for couldn't be found."
 msgstr ""
 
-#: frontend/src/metabase/containers/SaveQuestionModal.jsx:186
-msgid "Save question"
+#: frontend/src/metabase/containers/ErrorPages.jsx:54
+msgid "Sorry, you don’t have permission to see that."
 msgstr ""
 
-#: frontend/src/metabase/containers/SaveQuestionModal.jsx:222
-msgid "What is the name of your card?"
+#: frontend/src/metabase/containers/ErrorPages.jsx:63
+msgid "This {0} has been archived"
 msgstr ""
 
-#: frontend/src/metabase/containers/SaveQuestionModal.jsx:245
-msgid "Which collection should this go in?"
+#: frontend/src/metabase/containers/ItemSelect.jsx:30
+msgid "Select a {0}"
 msgstr ""
 
-#: frontend/src/metabase/containers/SaveQuestionModal.jsx:256
-#: frontend/src/metabase/query_builder/components/LimitWidget.jsx:27
-#: frontend/src/metabase/questions/containers/MoveToCollection.jsx:81
-msgid "None"
+#: frontend/src/metabase/containers/Overworld.jsx:78
+msgid "Don't tell anyone, but you're my favorite."
 msgstr ""
 
-#: frontend/src/metabase/containers/UndoListing.jsx:58
-msgid "Undo"
+#: frontend/src/metabase/containers/Overworld.jsx:105
+msgid "Try these x-rays based on your data."
 msgstr ""
 
-#: frontend/src/metabase/dashboards/components/DashboardList.jsx:40
-#: frontend/src/metabase/questions/components/ActionHeader.jsx:71
-#: frontend/src/metabase/questions/components/Item.jsx:106
-msgid "Unarchive"
+#: frontend/src/metabase/containers/Overworld.jsx:131
+#: frontend/src/metabase/containers/Overworld.jsx:302
+#: frontend/src/metabase/reference/components/GuideHeader.jsx:12
+msgid "Start here"
 msgstr ""
 
-#: frontend/src/metabase/dashboards/components/DashboardList.jsx:57
-#: frontend/src/metabase/questions/components/Item.jsx:170
-msgid "Unfavorite"
+#: frontend/src/metabase/containers/Overworld.jsx:188
+msgid "Save dashboards, questions, and collections in \"{0}\""
 msgstr ""
 
-#: frontend/src/metabase/dashboards/components/DashboardList.jsx:57
-#: frontend/src/metabase/questions/components/Item.jsx:170
-msgid "Favorite"
+#: frontend/src/metabase/containers/Overworld.jsx:191
+msgid "Access dashboards, questions, and collections in \"{0}\""
 msgstr ""
 
-#: frontend/src/metabase/dashboards/containers/Dashboards.jsx:46
-msgid "All dashboards"
+#: frontend/src/metabase/containers/Overworld.jsx:206
+msgid "Browse all items"
 msgstr ""
 
-#: frontend/src/metabase/dashboards/containers/Dashboards.jsx:52
-#: frontend/src/metabase/questions/containers/EntityList.jsx:76
-#: frontend/src/metabase/questions/selectors.js:155
-msgid "Favorites"
+#: frontend/src/metabase/containers/Overworld.jsx:297
+#: frontend/src/metabase/entities/collections.js:140
+#: src/metabase/models/collection.clj
+msgid "Our analytics"
 msgstr ""
 
-#: frontend/src/metabase/dashboards/containers/Dashboards.jsx:58
-#: frontend/src/metabase/questions/containers/EntityList.jsx:88
-#: frontend/src/metabase/questions/selectors.js:157
-msgid "Saved by me"
+#: frontend/src/metabase/containers/Overworld.jsx:313
+msgid "Your team's most important dashboards go here"
 msgstr ""
 
-#: frontend/src/metabase/dashboards/containers/Dashboards.jsx:163
-#: frontend/src/metabase/nav/containers/Navbar.jsx:163
-#: frontend/src/metabase/routes.jsx:209 frontend/src/metabase/routes.jsx:214
-msgid "Dashboards"
+#: frontend/src/metabase/containers/Overworld.jsx:314
+msgid "Pin dashboards in {0} to have them appear in this space for everyone"
 msgstr ""
 
-#: frontend/src/metabase/dashboards/containers/Dashboards.jsx:179
-msgid "Add new dashboard"
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:165
+msgid "Replace or save as new?"
 msgstr ""
 
-#: frontend/src/metabase/dashboards/containers/Dashboards.jsx:190
-msgid ""
-"Put the charts and graphs you look at {0}frequently in a single, handy place."
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:173
+msgid "Replace original question, \"{0}\""
 msgstr ""
 
-#: frontend/src/metabase/dashboards/containers/Dashboards.jsx:195
-#: frontend/src/metabase/dashboards/containers/Dashboards.jsx:228
-msgid "Create a dashboard"
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:178
+msgid "Save as new question"
 msgstr ""
 
-#: frontend/src/metabase/dashboards/containers/Dashboards.jsx:222
-msgid ""
-"Try adjusting your filter to find what you’re\n"
-"looking for."
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:187
+msgid "First, save your question"
 msgstr ""
 
-#: frontend/src/metabase/dashboards/containers/DashboardsArchive.jsx:96
-msgid "No dashboards have been {0} archived yet"
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:188
+msgid "Save question"
 msgstr ""
 
-#: frontend/src/metabase/home/components/Activity.jsx:92
-msgid "did some super awesome stuff thats hard to describe"
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:224
+msgid "What is the name of your card?"
 msgstr ""
 
-#: frontend/src/metabase/home/components/Activity.jsx:101
-#: frontend/src/metabase/home/components/Activity.jsx:116
-msgid "created an alert about - "
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:232
+#: frontend/src/metabase/entities/collections.js:94
+#: frontend/src/metabase/entities/dashboards.js:102
+#: frontend/src/metabase/lib/core.js:45 frontend/src/metabase/lib/core.js:200
+#: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:156
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:211
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:189
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:203
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:207
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:207
+#: frontend/src/metabase/visualizations/lib/settings.js:163
+msgid "Description"
 msgstr ""
 
-#: frontend/src/metabase/home/components/Activity.jsx:126
-msgid "deleted an alert about - "
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:238
+#: frontend/src/metabase/entities/dashboards.js:104
+msgid "It's optional but oh, so helpful"
 msgstr ""
 
-#: frontend/src/metabase/home/components/Activity.jsx:141
-msgid "deleted an alert about- "
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:245
+#: frontend/src/metabase/entities/dashboards.js:108
+msgid "Which collection should this go in?"
 msgstr ""
 
-#: frontend/src/metabase/home/components/Activity.jsx:152
-msgid "saved a question about "
+#: frontend/src/metabase/containers/UndoListing.jsx:34
+msgid "modified"
 msgstr ""
 
-#: frontend/src/metabase/home/components/Activity.jsx:165
-msgid "saved a question"
+#: frontend/src/metabase/containers/UndoListing.jsx:34
+msgid "item"
 msgstr ""
 
-#: frontend/src/metabase/home/components/Activity.jsx:169
-msgid "deleted a question"
+#: frontend/src/metabase/containers/UndoListing.jsx:81
+msgid "Undo"
 msgstr ""
 
-#: frontend/src/metabase/home/components/Activity.jsx:172
-msgid "created a dashboard"
+#: frontend/src/metabase/dashboard/components/AddSeriesModal.jsx:270
+msgid "Applying Question"
 msgstr ""
 
-#: frontend/src/metabase/home/components/Activity.jsx:175
-msgid "deleted a dashboard"
+#: frontend/src/metabase/dashboard/components/AddSeriesModal.jsx:274
+msgid "That question isn't compatible"
 msgstr ""
 
-#: frontend/src/metabase/home/components/Activity.jsx:181
-#: frontend/src/metabase/home/components/Activity.jsx:196
-msgid "added a question to the dashboard - "
+#: frontend/src/metabase/dashboard/components/AddSeriesModal.jsx:310
+msgid "Search for a question"
 msgstr ""
 
-#: frontend/src/metabase/home/components/Activity.jsx:206
-#: frontend/src/metabase/home/components/Activity.jsx:221
-msgid "removed a question from the dashboard - "
+#: frontend/src/metabase/dashboard/components/AddSeriesModal.jsx:339
+msgid "We're not sure if this question is compatible"
 msgstr ""
 
-#: frontend/src/metabase/home/components/Activity.jsx:234
-#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:267
-msgid "Unknown"
+#: frontend/src/metabase/dashboard/components/ArchiveDashboardModal.jsx:43
+msgid "Archive Dashboard"
 msgstr ""
 
-#: frontend/src/metabase/home/components/Activity.jsx:238
-#: frontend/src/metabase/home/components/Activity.jsx:245
-msgid "received the latest data from"
+#: frontend/src/metabase/dashboard/components/DashCardParameterMapper.jsx:20
+msgid ""
+"Make sure to make a selection for each series, or the filter won't work on "
+"this card."
 msgstr ""
 
-#: frontend/src/metabase/home/components/Activity.jsx:252
-msgid "Hello World!"
+#: frontend/src/metabase/dashboard/components/Dashboard.jsx:284
+msgid "This dashboard is looking empty."
 msgstr ""
 
-#: frontend/src/metabase/home/components/Activity.jsx:253
-msgid "Metabase is up and running."
+#: frontend/src/metabase/dashboard/components/Dashboard.jsx:287
+msgid "Add a question to start making it useful!"
 msgstr ""
 
-#: frontend/src/metabase/home/components/Activity.jsx:259
-#: frontend/src/metabase/home/components/Activity.jsx:289
-msgid "added the metric "
+#: frontend/src/metabase/dashboard/components/DashboardActions.jsx:36
+msgid "Daytime mode"
 msgstr ""
 
-#: frontend/src/metabase/home/components/Activity.jsx:273
-#: frontend/src/metabase/home/components/Activity.jsx:363
-msgid " to the "
+#: frontend/src/metabase/dashboard/components/DashboardActions.jsx:36
+msgid "Nighttime mode"
 msgstr ""
 
-#: frontend/src/metabase/home/components/Activity.jsx:283
-#: frontend/src/metabase/home/components/Activity.jsx:323
-#: frontend/src/metabase/home/components/Activity.jsx:373
-#: frontend/src/metabase/home/components/Activity.jsx:414
-msgid " table"
+#: frontend/src/metabase/dashboard/components/DashboardActions.jsx:53
+msgid "Exit fullscreen"
 msgstr ""
 
-#: frontend/src/metabase/home/components/Activity.jsx:299
-#: frontend/src/metabase/home/components/Activity.jsx:329
-msgid "made changes to the metric "
+#: frontend/src/metabase/dashboard/components/DashboardActions.jsx:53
+msgid "Enter fullscreen"
 msgstr ""
 
-#: frontend/src/metabase/home/components/Activity.jsx:313
-#: frontend/src/metabase/home/components/Activity.jsx:404
-msgid " in the "
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:181
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:183
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:250
+msgid "Saving…"
 msgstr ""
 
-#: frontend/src/metabase/home/components/Activity.jsx:336
-msgid "removed the metric "
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:216
+msgid "Add a question"
 msgstr ""
 
-#: frontend/src/metabase/home/components/Activity.jsx:339
-msgid "created a pulse"
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:219
+msgid "Add a question to this dashboard"
 msgstr ""
 
-#: frontend/src/metabase/home/components/Activity.jsx:342
-msgid "deleted a pulse"
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:248
+msgid "Add a filter"
 msgstr ""
 
-#: frontend/src/metabase/home/components/Activity.jsx:348
-msgid "added the filter "
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:254
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:78
+msgid "Parameters"
 msgstr ""
 
-#: frontend/src/metabase/home/components/Activity.jsx:379
-msgid "added the filter"
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:275
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:279
+msgid "Add a text box"
 msgstr ""
 
-#: frontend/src/metabase/home/components/Activity.jsx:389
-msgid "made changes to the filter "
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:301
+msgid "Move dashboard"
 msgstr ""
 
-#: frontend/src/metabase/home/components/Activity.jsx:420
-msgid "made changes to the filter"
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:313
+msgid "Edit dashboard"
 msgstr ""
 
-#: frontend/src/metabase/home/components/Activity.jsx:427
-msgid "removed the filter {0}"
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:317
+msgid "Edit Dashboard Layout"
 msgstr ""
 
-#: frontend/src/metabase/home/components/Activity.jsx:430
-msgid "joined!"
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:352
+msgid "You are editing a dashboard"
 msgstr ""
 
-#: frontend/src/metabase/home/components/Activity.jsx:530
-msgid "Hmmm, looks like nothing has happened yet."
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:357
+msgid "Select the field that should be filtered for each card"
 msgstr ""
 
-#: frontend/src/metabase/home/components/Activity.jsx:533
-msgid "Save a question and get this baby going!"
+#: frontend/src/metabase/dashboard/components/DashboardMoveModal.jsx:28
+msgid "Move dashboard to..."
 msgstr ""
 
-#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:18
-msgid "Ask questions and explore"
+#: frontend/src/metabase/dashboard/components/DashboardMoveModal.jsx:52
+msgid "Dashboard moved to {0}"
 msgstr ""
 
-#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:19
-msgid ""
-"Click on charts or tables to explore, or ask a new question using the easy "
-"interface or the powerful SQL editor."
+#: frontend/src/metabase/dashboard/components/ParametersPopover.jsx:82
+msgid "What do you want to filter?"
 msgstr ""
 
-#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:29
-msgid "Make your own charts"
+#: frontend/src/metabase/dashboard/components/ParametersPopover.jsx:115
+msgid "What kind of filter?"
 msgstr ""
 
-#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:30
-msgid "Create line charts, scatter plots, maps, and more."
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:13
+msgid "Off"
 msgstr ""
 
-#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:40
-msgid "Share what you find"
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:14
+msgid "1 minute"
 msgstr ""
 
-#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:41
-msgid ""
-"Create powerful and flexible dashboards, and send regular updates via email "
-"or Slack."
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:15
+msgid "5 minutes"
 msgstr ""
 
-#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:96
-msgid "Let's go"
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:16
+msgid "10 minutes"
 msgstr ""
 
-#: frontend/src/metabase/home/components/NextStep.jsx:34
-msgid "Setup Tip"
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:17
+msgid "15 minutes"
 msgstr ""
 
-#: frontend/src/metabase/home/components/NextStep.jsx:40
-msgid "View all"
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:18
+msgid "30 minutes"
 msgstr ""
 
-#: frontend/src/metabase/home/components/RecentViews.jsx:39
-msgid "Recently Viewed"
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:19
+msgid "60 minutes"
 msgstr ""
 
-#: frontend/src/metabase/home/components/RecentViews.jsx:73
-msgid "You haven't looked at any dashboards or questions recently"
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:31
+msgid "Auto-refresh"
 msgstr ""
 
-#: frontend/src/metabase/home/containers/HomepageApp.jsx:84
-msgid "Activity"
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:37
+msgid "Refreshing in"
 msgstr ""
 
-#: frontend/src/metabase/lib/core.js:7
-msgid "Entity Key"
+#: frontend/src/metabase/dashboard/components/RemoveFromDashboardModal.jsx:37
+msgid "Remove this question?"
 msgstr ""
 
-#: frontend/src/metabase/lib/core.js:9
-msgid "The primary key for this table."
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:70
+msgid "Your dashboard was saved"
 msgstr ""
 
-#: frontend/src/metabase/lib/core.js:13
-msgid "Entity Name"
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:75
+msgid "See it"
 msgstr ""
 
-#: frontend/src/metabase/lib/core.js:15
-msgid ""
-"The \"name\" of each record. Usually a column called \"name\", \"title\", "
-"etc."
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:132
+msgid "Save this"
 msgstr ""
 
-#: frontend/src/metabase/lib/core.js:19
-msgid "Foreign Key"
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:165
+msgid "Show more about this"
 msgstr ""
 
-#: frontend/src/metabase/lib/core.js:21
-msgid "Points to another table to make a connection."
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:216
+msgid "Compare"
 msgstr ""
 
-#: frontend/src/metabase/lib/core.js:25
-msgid "Avatar Image URL"
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:220
+#: frontend/src/metabase/qb/components/drill/ZoomDrill.jsx:26
+msgid "Zoom in"
 msgstr ""
 
-#: frontend/src/metabase/lib/core.js:30
-#: frontend/src/metabase/meta/Dashboard.js:82
-#: frontend/src/metabase/qb/components/actions/PivotByCategoryAction.jsx:9
-msgid "Category"
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:224
+msgid "Zoom out"
 msgstr ""
 
-#: frontend/src/metabase/lib/core.js:35
-#: frontend/src/metabase/meta/Dashboard.js:62
-msgid "City"
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:228
+msgid "Related"
 msgstr ""
 
-#: frontend/src/metabase/lib/core.js:40
-#: frontend/src/metabase/meta/Dashboard.js:74
-msgid "Country"
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:288
+msgid "More X-rays"
 msgstr ""
 
-#: frontend/src/metabase/lib/core.js:55
-msgid "Enum"
+#: frontend/src/metabase/dashboard/containers/DashCardCardParameterMapper.jsx:140
+msgid ""
+"This card doesn't have any fields or parameters that can be mapped to this "
+"parameter type."
 msgstr ""
 
-#: frontend/src/metabase/lib/core.js:60
-msgid "Image URL"
+#: frontend/src/metabase/dashboard/containers/DashCardCardParameterMapper.jsx:142
+msgid ""
+"The values in this field don't overlap with the values of any other fields "
+"you've chosen."
 msgstr ""
 
-#: frontend/src/metabase/lib/core.js:65
-msgid "Field containing JSON"
+#: frontend/src/metabase/dashboard/containers/DashCardCardParameterMapper.jsx:186
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingFieldPicker.jsx:15
+msgid "No valid fields"
 msgstr ""
 
-#: frontend/src/metabase/lib/core.js:70
-msgid "Latitude"
+#: frontend/src/metabase/entities/collections.js:90
+msgid "Name must be 100 characters or less"
 msgstr ""
 
-#: frontend/src/metabase/lib/core.js:75
-msgid "Longitude"
+#: frontend/src/metabase/entities/collections.js:101
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:48
+msgid "Color"
 msgstr ""
 
-#: frontend/src/metabase/lib/core.js:80
-#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:146
-#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:22
-msgid "Number"
+#: frontend/src/metabase/entities/collections.js:104
+msgid "Color is required"
 msgstr ""
 
-#: frontend/src/metabase/lib/core.js:85
-#: frontend/src/metabase/meta/Dashboard.js:66
-msgid "State"
+#: frontend/src/metabase/entities/collections.js:108
+msgid "Collection it's saved in"
 msgstr ""
 
-#: frontend/src/metabase/lib/core.js:90
-msgid "UNIX Timestamp (Seconds)"
+#: frontend/src/metabase/entities/collections.js:157
+msgid "All personal collections"
 msgstr ""
 
-#: frontend/src/metabase/lib/core.js:95
-msgid "UNIX Timestamp (Milliseconds)"
+#: frontend/src/metabase/entities/dashboards.js:97
+msgid "What is the name of your dashboard?"
 msgstr ""
 
-#: frontend/src/metabase/lib/core.js:105
-msgid "Zip Code"
+#: frontend/src/metabase/home/components/Activity.jsx:92
+msgid "did some super awesome stuff that's hard to describe"
 msgstr ""
 
-#: frontend/src/metabase/lib/core.js:118
-msgid "Everywhere"
+#: frontend/src/metabase/home/components/Activity.jsx:101
+#: frontend/src/metabase/home/components/Activity.jsx:116
+msgid "created an alert about - "
 msgstr ""
 
-#: frontend/src/metabase/lib/core.js:119
-msgid ""
-"The default setting. This field will be displayed normally in tables and "
-"charts."
+#: frontend/src/metabase/home/components/Activity.jsx:126
+#: frontend/src/metabase/home/components/Activity.jsx:141
+msgid "deleted an alert about - "
 msgstr ""
 
-#: frontend/src/metabase/lib/core.js:123
-msgid "Only in Detail Views"
+#: frontend/src/metabase/home/components/Activity.jsx:152
+msgid "saved a question about "
 msgstr ""
 
-#: frontend/src/metabase/lib/core.js:124
-msgid ""
-"This field will only be displayed when viewing the details of a single "
-"record. Use this for information that's lengthy or that isn't useful in a "
-"table or chart."
+#: frontend/src/metabase/home/components/Activity.jsx:165
+msgid "saved a question"
 msgstr ""
 
-#: frontend/src/metabase/lib/core.js:128
-msgid "Do Not Include"
+#: frontend/src/metabase/home/components/Activity.jsx:169
+msgid "deleted a question"
 msgstr ""
 
-#: frontend/src/metabase/lib/core.js:129
-msgid ""
-"Metabase will never retrieve this field. Use this for sensitive or "
-"irrelevant information."
+#: frontend/src/metabase/home/components/Activity.jsx:172
+msgid "created a dashboard"
 msgstr ""
 
-#: frontend/src/metabase/lib/expressions/config.js:7
-#: frontend/src/metabase/lib/query.js:609
-#: frontend/src/metabase/visualizations/lib/utils.js:113
-#: frontend/src/metabase/xray/components/XRayComparison.jsx:193
-msgid "Count"
+#: frontend/src/metabase/home/components/Activity.jsx:175
+msgid "deleted a dashboard"
 msgstr ""
 
-#: frontend/src/metabase/lib/expressions/config.js:8
-msgid "CumulativeCount"
+#: frontend/src/metabase/home/components/Activity.jsx:181
+#: frontend/src/metabase/home/components/Activity.jsx:196
+msgid "added a question to the dashboard - "
 msgstr ""
 
-#: frontend/src/metabase/lib/expressions/config.js:9
-#: frontend/src/metabase/qb/components/drill/SummarizeColumnDrill.js:17
-#: frontend/src/metabase/visualizations/lib/utils.js:114
-msgid "Sum"
+#: frontend/src/metabase/home/components/Activity.jsx:206
+#: frontend/src/metabase/home/components/Activity.jsx:221
+msgid "removed a question from the dashboard - "
 msgstr ""
 
-#: frontend/src/metabase/lib/expressions/config.js:10
-msgid "CumulativeSum"
+#: frontend/src/metabase/home/components/Activity.jsx:231
+#: frontend/src/metabase/home/components/Activity.jsx:238
+msgid "received the latest data from"
 msgstr ""
 
-#: frontend/src/metabase/lib/expressions/config.js:11
-#: frontend/src/metabase/visualizations/lib/utils.js:115
-msgid "Distinct"
+#: frontend/src/metabase/home/components/Activity.jsx:244
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:273
+msgid "Unknown"
 msgstr ""
 
-#: frontend/src/metabase/lib/expressions/config.js:12
-msgid "StandardDeviation"
+#: frontend/src/metabase/home/components/Activity.jsx:251
+msgid "Hello World!"
 msgstr ""
 
-#: frontend/src/metabase/lib/expressions/config.js:13
-#: frontend/src/metabase/visualizations/lib/utils.js:112
-msgid "Average"
+#: frontend/src/metabase/home/components/Activity.jsx:252
+msgid "Metabase is up and running."
 msgstr ""
 
-#: frontend/src/metabase/lib/expressions/config.js:14
-#: frontend/src/metabase/qb/components/drill/SummarizeColumnDrill.js:25
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:335
-msgid "Min"
+#: frontend/src/metabase/home/components/Activity.jsx:258
+#: frontend/src/metabase/home/components/Activity.jsx:288
+msgid "added the metric "
 msgstr ""
 
-#: frontend/src/metabase/lib/expressions/config.js:15
-#: frontend/src/metabase/qb/components/drill/SummarizeColumnDrill.js:29
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:343
-msgid "Max"
+#: frontend/src/metabase/home/components/Activity.jsx:272
+#: frontend/src/metabase/home/components/Activity.jsx:362
+msgid " to the "
 msgstr ""
 
-#: frontend/src/metabase/lib/expressions/parser.js:384
-msgid "sad sad panda, lexing errors detected"
+#: frontend/src/metabase/home/components/Activity.jsx:282
+#: frontend/src/metabase/home/components/Activity.jsx:322
+#: frontend/src/metabase/home/components/Activity.jsx:372
+#: frontend/src/metabase/home/components/Activity.jsx:413
+msgid " table"
 msgstr ""
 
-#: frontend/src/metabase/lib/greeting.js:4
-msgid "Hey there"
+#: frontend/src/metabase/home/components/Activity.jsx:298
+#: frontend/src/metabase/home/components/Activity.jsx:328
+msgid "made changes to the metric "
 msgstr ""
 
-#: frontend/src/metabase/lib/greeting.js:5
-#: frontend/src/metabase/lib/greeting.js:29
-msgid "How's it going"
+#: frontend/src/metabase/home/components/Activity.jsx:312
+#: frontend/src/metabase/home/components/Activity.jsx:403
+msgid " in the "
 msgstr ""
 
-#: frontend/src/metabase/lib/greeting.js:6
-msgid "Howdy"
+#: frontend/src/metabase/home/components/Activity.jsx:335
+msgid "removed the metric "
 msgstr ""
 
-#: frontend/src/metabase/lib/greeting.js:7
-msgid "Greetings"
+#: frontend/src/metabase/home/components/Activity.jsx:338
+msgid "created a pulse"
 msgstr ""
 
-#: frontend/src/metabase/lib/greeting.js:8
-msgid "Good to see you"
+#: frontend/src/metabase/home/components/Activity.jsx:341
+msgid "deleted a pulse"
 msgstr ""
 
-#: frontend/src/metabase/lib/greeting.js:12
-msgid "What do you want to know?"
+#: frontend/src/metabase/home/components/Activity.jsx:347
+#: frontend/src/metabase/home/components/Activity.jsx:378
+msgid "added the filter"
 msgstr ""
 
-#: frontend/src/metabase/lib/greeting.js:13
-msgid "What's on your mind?"
+#: frontend/src/metabase/home/components/Activity.jsx:388
+#: frontend/src/metabase/home/components/Activity.jsx:419
+msgid "made changes to the filter"
 msgstr ""
 
-#: frontend/src/metabase/lib/greeting.js:14
-msgid "What do you want to find out?"
+#: frontend/src/metabase/home/components/Activity.jsx:426
+msgid "removed the filter {0}"
 msgstr ""
 
-#: frontend/src/metabase/lib/query.js:607
-#: frontend/src/metabase/lib/schema_metadata.js:412
-#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:244
-msgid "Raw data"
+#: frontend/src/metabase/home/components/Activity.jsx:429
+msgid "joined!"
 msgstr ""
 
-#: frontend/src/metabase/lib/query.js:611
-msgid "Cumulative count"
+#: frontend/src/metabase/home/components/Activity.jsx:529
+msgid "Hmmm, looks like nothing has happened yet."
 msgstr ""
 
-#: frontend/src/metabase/lib/query.js:614
-msgid "Average of "
+#: frontend/src/metabase/home/components/Activity.jsx:532
+msgid "Save a question and get this baby going!"
 msgstr ""
 
-#: frontend/src/metabase/lib/query.js:619
-msgid "Distinct values of "
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:19
+msgid "Ask questions and explore"
 msgstr ""
 
-#: frontend/src/metabase/lib/query.js:624
-msgid "Standard deviation of "
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:20
+msgid ""
+"Click on charts or tables to explore, or ask a new question using the easy "
+"interface or the powerful SQL editor."
 msgstr ""
 
-#: frontend/src/metabase/lib/query.js:629
-msgid "Sum of "
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:30
+msgid "Make your own charts"
 msgstr ""
 
-#: frontend/src/metabase/lib/query.js:634
-msgid "Cumulative sum of "
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:31
+msgid "Create line charts, scatter plots, maps, and more."
 msgstr ""
 
-#: frontend/src/metabase/lib/query.js:639
-msgid "Maximum of "
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:41
+msgid "Share what you find"
 msgstr ""
 
-#: frontend/src/metabase/lib/query.js:644
-msgid "Minimum of "
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:42
+msgid ""
+"Create powerful and flexible dashboards, and send regular updates via email "
+"or Slack."
 msgstr ""
 
-#: frontend/src/metabase/lib/query.js:658
-msgid "Grouped by "
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:97
+msgid "Let's go"
 msgstr ""
 
-#: frontend/src/metabase/lib/query.js:672
-msgid "Filtered by "
+#: frontend/src/metabase/home/components/NextStep.jsx:34
+msgid "Setup Tip"
 msgstr ""
 
-#: frontend/src/metabase/lib/query.js:700
-msgid "Sorted by "
+#: frontend/src/metabase/home/components/NextStep.jsx:40
+msgid "View all"
 msgstr ""
 
-#: frontend/src/metabase/lib/request.js:216
-msgid "Background job result isn't available for an unknown reason"
+#: frontend/src/metabase/home/components/RecentViews.jsx:40
+msgid "Recently Viewed"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:198
-msgid "True"
+#: frontend/src/metabase/home/components/RecentViews.jsx:75
+msgid "You haven't looked at any dashboards or questions recently"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:198
-msgid "False"
+#: frontend/src/metabase/home/containers/ArchiveApp.jsx:82
+msgid "{0} items selected"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:273
-msgid "Select longitude field"
+#: frontend/src/metabase/home/containers/ArchiveApp.jsx:102
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:172
+msgid "Unarchive"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:274
-msgid "Enter upper latitude"
+#: frontend/src/metabase/home/containers/HomepageApp.jsx:74
+#: frontend/src/metabase/nav/containers/Navbar.jsx:327
+msgid "Activity"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:275
-msgid "Enter left longitude"
+#: frontend/src/metabase/home/containers/SearchApp.jsx:28
+msgid "Results for \"{0}\""
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:276
-msgid "Enter lower latitude"
+#: frontend/src/metabase/home/containers/SearchApp.jsx:50
+msgid "No results"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:277
-msgid "Enter right longitude"
+#: frontend/src/metabase/home/containers/SearchApp.jsx:51
+msgid "Metabase couldn't find any results for your search."
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:306
-#: frontend/src/metabase/lib/schema_metadata.js:326
-#: frontend/src/metabase/lib/schema_metadata.js:336
-#: frontend/src/metabase/lib/schema_metadata.js:342
-#: frontend/src/metabase/lib/schema_metadata.js:350
-#: frontend/src/metabase/lib/schema_metadata.js:356
-#: frontend/src/metabase/lib/schema_metadata.js:361
-msgid "Is"
+#: frontend/src/metabase/home/containers/SearchApp.jsx:142
+msgid "Pulse"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:307
-#: frontend/src/metabase/lib/schema_metadata.js:327
-#: frontend/src/metabase/lib/schema_metadata.js:337
-#: frontend/src/metabase/lib/schema_metadata.js:351
-#: frontend/src/metabase/lib/schema_metadata.js:357
-msgid "Is not"
+#: frontend/src/metabase/lib/core.js:7
+msgid "Entity Key"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:308
-#: frontend/src/metabase/lib/schema_metadata.js:322
-#: frontend/src/metabase/lib/schema_metadata.js:330
-#: frontend/src/metabase/lib/schema_metadata.js:338
-#: frontend/src/metabase/lib/schema_metadata.js:346
-#: frontend/src/metabase/lib/schema_metadata.js:352
-#: frontend/src/metabase/lib/schema_metadata.js:362
-msgid "Is empty"
+#: frontend/src/metabase/lib/core.js:8 frontend/src/metabase/lib/core.js:14
+#: frontend/src/metabase/lib/core.js:20
+msgid "Overall Row"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:309
-#: frontend/src/metabase/lib/schema_metadata.js:323
-#: frontend/src/metabase/lib/schema_metadata.js:331
-#: frontend/src/metabase/lib/schema_metadata.js:339
-#: frontend/src/metabase/lib/schema_metadata.js:347
-#: frontend/src/metabase/lib/schema_metadata.js:353
-#: frontend/src/metabase/lib/schema_metadata.js:363
-msgid "Not empty"
+#: frontend/src/metabase/lib/core.js:9
+msgid "The primary key for this table."
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:315
-msgid "Equal"
+#: frontend/src/metabase/lib/core.js:13
+msgid "Entity Name"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:316
-msgid "Not equal"
+#: frontend/src/metabase/lib/core.js:15
+msgid ""
+"The \"name\" of each record. Usually a column called \"name\", \"title\", "
+"etc."
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:317
-msgid "Greater than"
+#: frontend/src/metabase/lib/core.js:19
+msgid "Foreign Key"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:318
-msgid "Less than"
+#: frontend/src/metabase/lib/core.js:21
+msgid "Points to another table to make a connection."
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:319
-#: frontend/src/metabase/lib/schema_metadata.js:345
-#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:289
-#: frontend/src/metabase/query_builder/components/filters/pickers/TimePicker.jsx:96
-msgid "Between"
+#: frontend/src/metabase/lib/core.js:25
+msgid "Avatar Image URL"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:320
-msgid "Greater than or equal to"
+#: frontend/src/metabase/lib/core.js:26 frontend/src/metabase/lib/core.js:31
+#: frontend/src/metabase/lib/core.js:36 frontend/src/metabase/lib/core.js:41
+#: frontend/src/metabase/lib/core.js:46 frontend/src/metabase/lib/core.js:51
+#: frontend/src/metabase/lib/core.js:56 frontend/src/metabase/lib/core.js:61
+#: frontend/src/metabase/lib/core.js:66 frontend/src/metabase/lib/core.js:71
+#: frontend/src/metabase/lib/core.js:76 frontend/src/metabase/lib/core.js:81
+#: frontend/src/metabase/lib/core.js:86 frontend/src/metabase/lib/core.js:91
+#: frontend/src/metabase/lib/core.js:96 frontend/src/metabase/lib/core.js:101
+#: frontend/src/metabase/lib/core.js:106 frontend/src/metabase/lib/core.js:111
+#: frontend/src/metabase/lib/core.js:116 frontend/src/metabase/lib/core.js:121
+#: frontend/src/metabase/lib/core.js:126 frontend/src/metabase/lib/core.js:131
+#: frontend/src/metabase/lib/core.js:136 frontend/src/metabase/lib/core.js:141
+#: frontend/src/metabase/lib/core.js:146 frontend/src/metabase/lib/core.js:151
+#: frontend/src/metabase/lib/core.js:156 frontend/src/metabase/lib/core.js:161
+#: frontend/src/metabase/lib/core.js:166 frontend/src/metabase/lib/core.js:171
+#: frontend/src/metabase/lib/core.js:176 frontend/src/metabase/lib/core.js:181
+#: frontend/src/metabase/lib/core.js:186 frontend/src/metabase/lib/core.js:191
+#: frontend/src/metabase/lib/core.js:196 frontend/src/metabase/lib/core.js:201
+#: frontend/src/metabase/lib/core.js:206 frontend/src/metabase/lib/core.js:211
+#: frontend/src/metabase/lib/core.js:216 frontend/src/metabase/lib/core.js:221
+#: frontend/src/metabase/lib/core.js:226
+msgid "Common"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:321
-msgid "Less than or equal to"
+#: frontend/src/metabase/lib/core.js:30
+#: frontend/src/metabase/meta/Dashboard.js:82
+#: frontend/src/metabase/qb/components/actions/PivotByCategoryAction.jsx:9
+msgid "Category"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:328
-msgid "Contains"
+#: frontend/src/metabase/lib/core.js:35
+#: frontend/src/metabase/meta/Dashboard.js:62
+msgid "City"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:329
-msgid "Does not contain"
+#: frontend/src/metabase/lib/core.js:40
+#: frontend/src/metabase/meta/Dashboard.js:74
+msgid "Country"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:332
-msgid "Starts with"
+#: frontend/src/metabase/lib/core.js:55
+msgid "Enum"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:333
-msgid "Ends with"
+#: frontend/src/metabase/lib/core.js:60
+msgid "Image URL"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:343
-#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:268
-#: frontend/src/metabase/query_builder/components/filters/pickers/TimePicker.jsx:74
-msgid "Before"
+#: frontend/src/metabase/lib/core.js:65
+msgid "Field containing JSON"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:344
-#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:275
-#: frontend/src/metabase/query_builder/components/filters/pickers/TimePicker.jsx:85
-msgid "After"
+#: frontend/src/metabase/lib/core.js:70
+msgid "Latitude"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:358
-msgid "Inside"
+#: frontend/src/metabase/lib/core.js:75
+msgid "Longitude"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:414
-msgid "Just a table with the rows in the answer, no additional operations."
+#: frontend/src/metabase/lib/core.js:80
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:146
+#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:22
+msgid "Number"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:420
-msgid "Count of rows"
+#: frontend/src/metabase/lib/core.js:85
+#: frontend/src/metabase/meta/Dashboard.js:66
+msgid "State"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:422
-msgid "Total number of rows in the answer."
-msgstr ""
-
-#: frontend/src/metabase/lib/schema_metadata.js:428
-msgid "Sum of ..."
+#: frontend/src/metabase/lib/core.js:90
+msgid "UNIX Timestamp (Seconds)"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:430
-msgid "Sum of all the values of a column."
+#: frontend/src/metabase/lib/core.js:95
+msgid "UNIX Timestamp (Milliseconds)"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:436
-msgid "Average of ..."
+#: frontend/src/metabase/lib/core.js:105
+msgid "Zip Code"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:438
-msgid "Average of all the values of a column"
+#: frontend/src/metabase/lib/core.js:110
+msgid "Quantity"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:444
-msgid "Number of distinct values of ..."
+#: frontend/src/metabase/lib/core.js:115
+msgid "Income"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:446
-msgid "Number of unique values of a column among all the rows in the answer."
+#: frontend/src/metabase/lib/core.js:120
+msgid "Discount"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:452
-msgid "Cumulative sum of ..."
+#: frontend/src/metabase/lib/core.js:125
+msgid "Creation timestamp"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:454
-msgid ""
-"Additive sum of all the values of a column.\\ne.x. total revenue over time."
+#: frontend/src/metabase/lib/core.js:130
+msgid "Creation time"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:460
-msgid "Cumulative count of rows"
+#: frontend/src/metabase/lib/core.js:135
+msgid "Creation date"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:462
-msgid ""
-"Additive count of the number of rows.\\ne.x. total number of sales over time."
+#: frontend/src/metabase/lib/core.js:140
+msgid "Product"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:468
-msgid "Standard deviation of ..."
+#: frontend/src/metabase/lib/core.js:145
+msgid "User"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:470
-msgid ""
-"Number which expresses how much the values of a column vary among all rows "
-"in the answer."
+#: frontend/src/metabase/lib/core.js:150
+msgid "Source"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:476
-msgid "Minimum of ..."
+#: frontend/src/metabase/lib/core.js:155
+msgid "Price"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:478
-msgid "Minimum value of a column"
+#: frontend/src/metabase/lib/core.js:160
+msgid "Join timestamp"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:484
-msgid "Maximum of ..."
+#: frontend/src/metabase/lib/core.js:165
+msgid "Join time"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:486
-msgid "Maximum value of a column"
+#: frontend/src/metabase/lib/core.js:170
+msgid "Join date"
 msgstr ""
 
-#: frontend/src/metabase/lib/schema_metadata.js:494
-msgid "Break out by dimension"
+#: frontend/src/metabase/lib/core.js:175
+msgid "Share"
 msgstr ""
 
-#: frontend/src/metabase/lib/settings.js:87
-msgid "lower case letter"
+#: frontend/src/metabase/lib/core.js:180
+msgid "Owner"
 msgstr ""
 
-#: frontend/src/metabase/lib/settings.js:89
-msgid "upper case letter"
+#: frontend/src/metabase/lib/core.js:185
+msgid "Company"
 msgstr ""
 
-#: frontend/src/metabase/lib/settings.js:91
-msgid "number"
+#: frontend/src/metabase/lib/core.js:190
+msgid "Subscription"
 msgstr ""
 
-#: frontend/src/metabase/lib/settings.js:93
-msgid "special character"
+#: frontend/src/metabase/lib/core.js:195
+msgid "Score"
 msgstr ""
 
-#: frontend/src/metabase/lib/settings.js:99
-msgid "must be"
+#: frontend/src/metabase/lib/core.js:205
+#: frontend/src/metabase/public/components/widgets/DisplayOptionsPane.jsx:49
+#: frontend/src/metabase/visualizations/lib/settings.js:156
+msgid "Title"
 msgstr ""
 
-#: frontend/src/metabase/lib/settings.js:99
-#: frontend/src/metabase/lib/settings.js:100
-msgid "characters long"
+#: frontend/src/metabase/lib/core.js:210
+msgid "Comment"
 msgstr ""
 
-#: frontend/src/metabase/lib/settings.js:100
-msgid "Must be"
+#: frontend/src/metabase/lib/core.js:215
+msgid "Cost"
 msgstr ""
 
-#: frontend/src/metabase/lib/settings.js:116
-msgid "and include"
+#: frontend/src/metabase/lib/core.js:220
+msgid "Gross margin"
 msgstr ""
 
-#: frontend/src/metabase/lib/utils.js:54
-msgid "zero"
+#: frontend/src/metabase/lib/core.js:225
+msgid "Birthday"
 msgstr ""
 
-#: frontend/src/metabase/lib/utils.js:55
-msgid "one"
+#: frontend/src/metabase/lib/core.js:236
+msgid "Search box"
 msgstr ""
 
-#: frontend/src/metabase/lib/utils.js:56
-msgid "two"
+#: frontend/src/metabase/lib/core.js:237
+msgid "A list of all values"
 msgstr ""
 
-#: frontend/src/metabase/lib/utils.js:57
-msgid "three"
+#: frontend/src/metabase/lib/core.js:238
+msgid "Plain input box"
 msgstr ""
 
-#: frontend/src/metabase/lib/utils.js:58
-msgid "four"
+#: frontend/src/metabase/lib/core.js:244
+msgid "Everywhere"
 msgstr ""
 
-#: frontend/src/metabase/lib/utils.js:59
-msgid "five"
+#: frontend/src/metabase/lib/core.js:245
+msgid ""
+"The default setting. This field will be displayed normally in tables and "
+"charts."
 msgstr ""
 
-#: frontend/src/metabase/lib/utils.js:60
-msgid "six"
+#: frontend/src/metabase/lib/core.js:249
+msgid "Only in Detail Views"
 msgstr ""
 
-#: frontend/src/metabase/lib/utils.js:61
-msgid "seven"
+#: frontend/src/metabase/lib/core.js:250
+msgid ""
+"This field will only be displayed when viewing the details of a single "
+"record. Use this for information that's lengthy or that isn't useful in a "
+"table or chart."
 msgstr ""
 
-#: frontend/src/metabase/lib/utils.js:62
-msgid "eight"
+#: frontend/src/metabase/lib/core.js:254
+msgid "Do Not Include"
 msgstr ""
 
-#: frontend/src/metabase/lib/utils.js:63
-msgid "nine"
+#: frontend/src/metabase/lib/core.js:255
+msgid ""
+"Metabase will never retrieve this field. Use this for sensitive or "
+"irrelevant information."
 msgstr ""
 
-#: frontend/src/metabase/meta/Dashboard.js:31
-msgid "Month and Year"
+#: frontend/src/metabase/lib/expressions/config.js:7
+#: frontend/src/metabase/lib/query.js:615
+#: frontend/src/metabase/visualizations/lib/utils.js:113
+msgid "Count"
 msgstr ""
 
-#: frontend/src/metabase/meta/Dashboard.js:32
-msgid "Like January, 2016"
+#: frontend/src/metabase/lib/expressions/config.js:8
+msgid "CumulativeCount"
 msgstr ""
 
-#: frontend/src/metabase/meta/Dashboard.js:36
-msgid "Quarter and Year"
+#: frontend/src/metabase/lib/expressions/config.js:9
+#: frontend/src/metabase/qb/components/drill/SummarizeColumnDrill.js:17
+#: frontend/src/metabase/visualizations/lib/utils.js:114
+msgid "Sum"
 msgstr ""
 
-#: frontend/src/metabase/meta/Dashboard.js:37
-msgid "Like Q1, 2016"
+#: frontend/src/metabase/lib/expressions/config.js:10
+msgid "CumulativeSum"
 msgstr ""
 
-#: frontend/src/metabase/meta/Dashboard.js:41
-msgid "Single Date"
+#: frontend/src/metabase/lib/expressions/config.js:11
+#: frontend/src/metabase/visualizations/lib/utils.js:115
+msgid "Distinct"
 msgstr ""
 
-#: frontend/src/metabase/meta/Dashboard.js:42
-msgid "Like January 31, 2016"
+#: frontend/src/metabase/lib/expressions/config.js:12
+msgid "StandardDeviation"
 msgstr ""
 
-#: frontend/src/metabase/meta/Dashboard.js:46
-msgid "Date Range"
+#: frontend/src/metabase/lib/expressions/config.js:13
+#: frontend/src/metabase/visualizations/lib/utils.js:112
+msgid "Average"
 msgstr ""
 
-#: frontend/src/metabase/meta/Dashboard.js:47
-msgid "Like December 25, 2015 - February 14, 2016"
+#: frontend/src/metabase/lib/expressions/config.js:14
+#: frontend/src/metabase/qb/components/drill/SummarizeColumnDrill.js:25
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:363
+msgid "Min"
 msgstr ""
 
-#: frontend/src/metabase/meta/Dashboard.js:51
-msgid "Relative Date"
+#: frontend/src/metabase/lib/expressions/config.js:15
+#: frontend/src/metabase/qb/components/drill/SummarizeColumnDrill.js:29
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:371
+msgid "Max"
 msgstr ""
 
-#: frontend/src/metabase/meta/Dashboard.js:52
-msgid "Like \"the last 7 days\" or \"this month\""
+#: frontend/src/metabase/lib/expressions/parser.js:384
+msgid "sad sad panda, lexing errors detected"
 msgstr ""
 
-#: frontend/src/metabase/meta/Dashboard.js:56
-msgid "Date Filter"
+#: frontend/src/metabase/lib/formatting.js:447
+msgid "{0} second"
+msgid_plural "{0} seconds"
+msgstr[0] ""
+msgstr[1] ""
+
+#: frontend/src/metabase/lib/formatting.js:450
+msgid "{0} minute"
+msgid_plural "{0} minutes"
+msgstr[0] ""
+msgstr[1] ""
+
+#: frontend/src/metabase/lib/greeting.js:4
+msgid "Hey there"
 msgstr ""
 
-#: frontend/src/metabase/meta/Dashboard.js:57
-msgid "All Options"
+#: frontend/src/metabase/lib/greeting.js:5
+#: frontend/src/metabase/lib/greeting.js:29
+msgid "How's it going"
 msgstr ""
 
-#: frontend/src/metabase/meta/Dashboard.js:58
-msgid "Contains all of the above"
+#: frontend/src/metabase/lib/greeting.js:6
+msgid "Howdy"
 msgstr ""
 
-#: frontend/src/metabase/meta/Dashboard.js:70
-msgid "ZIP or Postal Code"
+#: frontend/src/metabase/lib/greeting.js:7
+msgid "Greetings"
 msgstr ""
 
-#: frontend/src/metabase/meta/Dashboard.js:78
-#: frontend/src/metabase/meta/Dashboard.js:108
-msgid "ID"
+#: frontend/src/metabase/lib/greeting.js:8
+msgid "Good to see you"
 msgstr ""
 
-#: frontend/src/metabase/meta/Dashboard.js:96
-#: frontend/src/metabase/qb/components/actions/PivotByTimeAction.jsx:8
-msgid "Time"
+#: frontend/src/metabase/lib/greeting.js:12
+msgid "What do you want to know?"
 msgstr ""
 
-#: frontend/src/metabase/meta/Dashboard.js:97
-msgid "Date range, relative date, time of day, etc."
+#: frontend/src/metabase/lib/greeting.js:13
+msgid "What's on your mind?"
 msgstr ""
 
-#: frontend/src/metabase/meta/Dashboard.js:102
-#: frontend/src/metabase/qb/components/actions/PivotByLocationAction.jsx:8
-msgid "Location"
+#: frontend/src/metabase/lib/greeting.js:14
+msgid "What do you want to find out?"
 msgstr ""
 
-#: frontend/src/metabase/meta/Dashboard.js:103
-msgid "City, State, Country, ZIP code."
+#: frontend/src/metabase/lib/groups.js:4
+msgid "All Users"
 msgstr ""
 
-#: frontend/src/metabase/meta/Dashboard.js:109
-msgid "User ID, product ID, event ID, etc."
+#: frontend/src/metabase/lib/groups.js:5
+msgid "Administrators"
 msgstr ""
 
-#: frontend/src/metabase/meta/Dashboard.js:114
-msgid "Other Categories"
+#: frontend/src/metabase/lib/groups.js:6
+msgid "MetaBot"
 msgstr ""
 
-#: frontend/src/metabase/meta/Dashboard.js:115
-msgid "Category, Type, Model, Rating, etc."
+#: frontend/src/metabase/lib/query.js:613
+#: frontend/src/metabase/lib/schema_metadata.js:441
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:246
+msgid "Raw data"
 msgstr ""
 
-#: frontend/src/metabase/nav/components/ProfileLink.jsx:106
-msgid "Account Settings"
+#: frontend/src/metabase/lib/query.js:617
+msgid "Cumulative count"
 msgstr ""
 
-#: frontend/src/metabase/nav/components/ProfileLink.jsx:121
-msgid "Admin Panel"
+#: frontend/src/metabase/lib/query.js:620
+msgid "Average of "
 msgstr ""
 
-#: frontend/src/metabase/nav/components/ProfileLink.jsx:134
-msgid "Exit Admin"
+#: frontend/src/metabase/lib/query.js:625
+msgid "Distinct values of "
 msgstr ""
 
-#: frontend/src/metabase/nav/components/ProfileLink.jsx:146
-#: frontend/src/metabase/query_builder/components/template_tags/TagEditorSidebar.jsx:105
-msgid "Help"
+#: frontend/src/metabase/lib/query.js:630
+msgid "Standard deviation of "
 msgstr ""
 
-#: frontend/src/metabase/nav/components/ProfileLink.jsx:159
-msgid "Logs"
+#: frontend/src/metabase/lib/query.js:635
+msgid "Sum of "
 msgstr ""
 
-#: frontend/src/metabase/nav/components/ProfileLink.jsx:170
-msgid "About Metabase"
+#: frontend/src/metabase/lib/query.js:640
+msgid "Cumulative sum of "
 msgstr ""
 
-#: frontend/src/metabase/nav/components/ProfileLink.jsx:180
-msgid "Sign out"
+#: frontend/src/metabase/lib/query.js:645
+msgid "Maximum of "
 msgstr ""
 
-#: frontend/src/metabase/nav/components/ProfileLink.jsx:201
-msgid "Thanks for using"
+#: frontend/src/metabase/lib/query.js:650
+msgid "Minimum of "
 msgstr ""
 
-#: frontend/src/metabase/nav/components/ProfileLink.jsx:205
-msgid "You're on version"
+#: frontend/src/metabase/lib/query.js:664
+msgid "Grouped by "
 msgstr ""
 
-#: frontend/src/metabase/nav/components/ProfileLink.jsx:208
-msgid "Built on"
+#: frontend/src/metabase/lib/query.js:678
+msgid "Filtered by "
 msgstr ""
 
-#: frontend/src/metabase/nav/components/ProfileLink.jsx:227
-msgid "is a Trademark of"
+#: frontend/src/metabase/lib/query.js:706
+msgid "Sorted by "
 msgstr ""
 
-#: frontend/src/metabase/nav/components/ProfileLink.jsx:229
-msgid "and is built with care in San Francisco, CA"
+#: frontend/src/metabase/lib/schema_metadata.js:211
+msgid "True"
 msgstr ""
 
-#: frontend/src/metabase/nav/containers/Navbar.jsx:91
-msgid "Metabase Admin"
+#: frontend/src/metabase/lib/schema_metadata.js:211
+msgid "False"
 msgstr ""
 
-#: frontend/src/metabase/nav/containers/Navbar.jsx:171
-#: frontend/src/metabase/routes.jsx:243
-msgid "Questions"
+#: frontend/src/metabase/lib/schema_metadata.js:295
+msgid "Select longitude field"
 msgstr ""
 
-#: frontend/src/metabase/nav/containers/Navbar.jsx:179
-#: frontend/src/metabase/pulse/components/PulseList.jsx:46
-#: frontend/src/metabase/routes.jsx:362
-msgid "Pulses"
+#: frontend/src/metabase/lib/schema_metadata.js:296
+msgid "Enter upper latitude"
 msgstr ""
 
-#: frontend/src/metabase/nav/containers/Navbar.jsx:187
-#: frontend/src/metabase/query_builder/components/dataref/MainPane.jsx:12
-#: frontend/src/metabase/reference/databases/DatabaseSidebar.jsx:20
-#: frontend/src/metabase/reference/databases/FieldSidebar.jsx:34
-#: frontend/src/metabase/reference/databases/TableSidebar.jsx:23
-#: frontend/src/metabase/reference/guide/BaseSidebar.jsx:17
-#: frontend/src/metabase/reference/guide/BaseSidebar.jsx:19
-#: frontend/src/metabase/reference/metrics/MetricSidebar.jsx:20
-#: frontend/src/metabase/reference/segments/SegmentFieldSidebar.jsx:24
-#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:20
-msgid "Data Reference"
+#: frontend/src/metabase/lib/schema_metadata.js:297
+msgid "Enter left longitude"
 msgstr ""
 
-#: frontend/src/metabase/nav/containers/Navbar.jsx:199
-#: frontend/src/metabase/routes.jsx:231
-msgid "New Question"
+#: frontend/src/metabase/lib/schema_metadata.js:298
+msgid "Enter lower latitude"
 msgstr ""
 
-#: frontend/src/metabase/new_query/containers/MetricSearch.jsx:73
-msgid "Which metric?"
+#: frontend/src/metabase/lib/schema_metadata.js:299
+msgid "Enter right longitude"
 msgstr ""
 
-#: frontend/src/metabase/new_query/containers/MetricSearch.jsx:88
-#: frontend/src/metabase/reference/metrics/MetricList.jsx:24
-msgid ""
-"Defining common metrics for your team makes it even easier to ask questions"
+#: frontend/src/metabase/lib/schema_metadata.js:335
+#: frontend/src/metabase/lib/schema_metadata.js:355
+#: frontend/src/metabase/lib/schema_metadata.js:365
+#: frontend/src/metabase/lib/schema_metadata.js:371
+#: frontend/src/metabase/lib/schema_metadata.js:379
+#: frontend/src/metabase/lib/schema_metadata.js:385
+#: frontend/src/metabase/lib/schema_metadata.js:390
+msgid "Is"
 msgstr ""
 
-#: frontend/src/metabase/new_query/containers/MetricSearch.jsx:92
-msgid "How to create metrics"
+#: frontend/src/metabase/lib/schema_metadata.js:336
+#: frontend/src/metabase/lib/schema_metadata.js:356
+#: frontend/src/metabase/lib/schema_metadata.js:366
+#: frontend/src/metabase/lib/schema_metadata.js:380
+#: frontend/src/metabase/lib/schema_metadata.js:386
+msgid "Is not"
 msgstr ""
 
-#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:140
-msgid ""
-"See data over time, as a map, or pivoted to help you understand trends or "
-"changes."
+#: frontend/src/metabase/lib/schema_metadata.js:337
+#: frontend/src/metabase/lib/schema_metadata.js:351
+#: frontend/src/metabase/lib/schema_metadata.js:359
+#: frontend/src/metabase/lib/schema_metadata.js:367
+#: frontend/src/metabase/lib/schema_metadata.js:375
+#: frontend/src/metabase/lib/schema_metadata.js:381
+#: frontend/src/metabase/lib/schema_metadata.js:391
+msgid "Is empty"
 msgstr ""
 
-#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:151
-msgid "Custom"
+#: frontend/src/metabase/lib/schema_metadata.js:338
+#: frontend/src/metabase/lib/schema_metadata.js:352
+#: frontend/src/metabase/lib/schema_metadata.js:360
+#: frontend/src/metabase/lib/schema_metadata.js:368
+#: frontend/src/metabase/lib/schema_metadata.js:376
+#: frontend/src/metabase/lib/schema_metadata.js:382
+#: frontend/src/metabase/lib/schema_metadata.js:392
+msgid "Not empty"
 msgstr ""
 
-#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:152
-#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:530
-msgid "New question"
+#: frontend/src/metabase/lib/schema_metadata.js:344
+msgid "Equal to"
 msgstr ""
 
-#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:154
-msgid ""
-"Use the simple question builder to see trends, lists of things, or to create "
-"your own metrics."
+#: frontend/src/metabase/lib/schema_metadata.js:345
+msgid "Not equal to"
 msgstr ""
 
-#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:163
-msgid "Native query"
+#: frontend/src/metabase/lib/schema_metadata.js:346
+msgid "Greater than"
 msgstr ""
 
-#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:164
-msgid ""
-"For more complicated questions, you can write your own SQL or native query."
+#: frontend/src/metabase/lib/schema_metadata.js:347
+msgid "Less than"
 msgstr ""
 
-#: frontend/src/metabase/parameters/components/ParameterValueWidget.jsx:243
-msgid "Select a default value…"
+#: frontend/src/metabase/lib/schema_metadata.js:348
+#: frontend/src/metabase/lib/schema_metadata.js:374
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:289
+#: frontend/src/metabase/query_builder/components/filters/pickers/TimePicker.jsx:96
+msgid "Between"
 msgstr ""
 
-#: frontend/src/metabase/parameters/components/widgets/DateAllOptionsWidget.jsx:147
-#: frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx:383
-msgid "Update filter"
+#: frontend/src/metabase/lib/schema_metadata.js:349
+msgid "Greater than or equal to"
 msgstr ""
 
-#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:9
-#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:144
-msgid "Today"
+#: frontend/src/metabase/lib/schema_metadata.js:350
+msgid "Less than or equal to"
 msgstr ""
 
-#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:14
-#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:148
-msgid "Yesterday"
+#: frontend/src/metabase/lib/schema_metadata.js:357
+msgid "Contains"
 msgstr ""
 
-#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:18
-msgid "Past 7 days"
+#: frontend/src/metabase/lib/schema_metadata.js:358
+msgid "Does not contain"
 msgstr ""
 
-#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:19
-msgid "Past 30 days"
+#: frontend/src/metabase/lib/schema_metadata.js:361
+msgid "Starts with"
 msgstr ""
 
-#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:24
-#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:29
-msgid "Week"
+#: frontend/src/metabase/lib/schema_metadata.js:362
+msgid "Ends with"
 msgstr ""
 
-#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:25
-#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:30
-msgid "Month"
+#: frontend/src/metabase/lib/schema_metadata.js:372
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:268
+#: frontend/src/metabase/query_builder/components/filters/pickers/TimePicker.jsx:74
+msgid "Before"
 msgstr ""
 
-#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:26
-#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:31
-msgid "Year"
+#: frontend/src/metabase/lib/schema_metadata.js:373
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:275
+#: frontend/src/metabase/query_builder/components/filters/pickers/TimePicker.jsx:85
+msgid "After"
 msgstr ""
 
-#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:152
-msgid "Past 7 Days"
+#: frontend/src/metabase/lib/schema_metadata.js:387
+msgid "Inside"
 msgstr ""
 
-#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:156
-msgid "Past 30 Days"
+#: frontend/src/metabase/lib/schema_metadata.js:443
+msgid "Just a table with the rows in the answer, no additional operations."
 msgstr ""
 
-#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:160
-msgid "Last Week"
+#: frontend/src/metabase/lib/schema_metadata.js:449
+msgid "Count of rows"
 msgstr ""
 
-#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:164
-msgid "Last Month"
+#: frontend/src/metabase/lib/schema_metadata.js:451
+msgid "Total number of rows in the answer."
 msgstr ""
 
-#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:168
-msgid "Last Year"
+#: frontend/src/metabase/lib/schema_metadata.js:457
+msgid "Sum of ..."
 msgstr ""
 
-#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:172
-msgid "This Week"
+#: frontend/src/metabase/lib/schema_metadata.js:459
+msgid "Sum of all the values of a column."
 msgstr ""
 
-#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:176
-msgid "This Month"
+#: frontend/src/metabase/lib/schema_metadata.js:465
+msgid "Average of ..."
 msgstr ""
 
-#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:180
-msgid "This Year"
+#: frontend/src/metabase/lib/schema_metadata.js:467
+msgid "Average of all the values of a column"
 msgstr ""
 
-#: frontend/src/metabase/parameters/components/widgets/ParameterFieldWidget.jsx:88
-#: frontend/src/metabase/parameters/components/widgets/TextWidget.jsx:54
-msgid "Enter a value..."
+#: frontend/src/metabase/lib/schema_metadata.js:473
+msgid "Number of distinct values of ..."
 msgstr ""
 
-#: frontend/src/metabase/parameters/components/widgets/TextWidget.jsx:88
-msgid "Enter a default value..."
+#: frontend/src/metabase/lib/schema_metadata.js:475
+msgid "Number of unique values of a column among all the rows in the answer."
 msgstr ""
 
-#: frontend/src/metabase/public/components/PublicError.jsx:18
-msgid "An error occurred"
+#: frontend/src/metabase/lib/schema_metadata.js:481
+msgid "Cumulative sum of ..."
 msgstr ""
 
-#: frontend/src/metabase/public/components/PublicNotFound.jsx:11
-msgid "Not found"
+#: frontend/src/metabase/lib/schema_metadata.js:483
+msgid ""
+"Additive sum of all the values of a column.\\ne.x. total revenue over time."
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:82
-msgid ""
-"You’ve made changes that need to be published before they will be reflected "
-"in your application embed."
+#: frontend/src/metabase/lib/schema_metadata.js:489
+msgid "Cumulative count of rows"
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:83
+#: frontend/src/metabase/lib/schema_metadata.js:491
 msgid ""
-"You will need to publish this {0} before you can embed it in another "
-"application."
+"Additive count of the number of rows.\\ne.x. total number of sales over time."
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:92
-msgid "Discard Changes"
+#: frontend/src/metabase/lib/schema_metadata.js:497
+msgid "Standard deviation of ..."
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:99
-msgid "Updating..."
+#: frontend/src/metabase/lib/schema_metadata.js:499
+msgid ""
+"Number which expresses how much the values of a column vary among all rows "
+"in the answer."
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:100
-msgid "Updated"
+#: frontend/src/metabase/lib/schema_metadata.js:505
+msgid "Minimum of ..."
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:101
-msgid "Failed!"
+#: frontend/src/metabase/lib/schema_metadata.js:507
+msgid "Minimum value of a column"
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:102
-msgid "Publish"
+#: frontend/src/metabase/lib/schema_metadata.js:513
+msgid "Maximum of ..."
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:111
-msgid "Code"
+#: frontend/src/metabase/lib/schema_metadata.js:515
+msgid "Maximum value of a column"
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:71
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:148
-msgid "Style"
+#: frontend/src/metabase/lib/schema_metadata.js:523
+msgid "Break out by dimension"
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:79
-msgid "Parameters"
+#: frontend/src/metabase/lib/settings.js:93
+msgid "lower case letter"
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:81
-msgid "Which parameters can users of this embed use?"
+#: frontend/src/metabase/lib/settings.js:95
+msgid "upper case letter"
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:84
-msgid "This {0} doesn't have any parameters to configure yet."
+#: frontend/src/metabase/lib/settings.js:97
+#: src/metabase/automagic_dashboards/core.clj
+msgid "number"
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:105
-msgid "Editable"
+#: frontend/src/metabase/lib/settings.js:99
+msgid "special character"
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:106
-msgid "Locked"
+#: frontend/src/metabase/lib/settings.js:105
+msgid "must be"
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:114
-msgid "Preview Locked Parameters"
+#: frontend/src/metabase/lib/settings.js:105
+#: frontend/src/metabase/lib/settings.js:106
+msgid "characters long"
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:116
-msgid ""
-"Try passing some values to your locked parameters here. Your server will "
-"have to provide the actual values in the signed token when using this for "
-"real."
+#: frontend/src/metabase/lib/settings.js:106
+msgid "Must be"
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:127
-msgid "Danger zone"
+#: frontend/src/metabase/lib/settings.js:122
+msgid "and include"
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:128
-msgid "This will disable embedding for this {0}."
+#: frontend/src/metabase/lib/utils.js:92
+msgid "zero"
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:129
-msgid "Unpublish"
+#: frontend/src/metabase/lib/utils.js:93
+msgid "one"
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/DisplayOptionsPane.jsx:17
-msgid "Light"
+#: frontend/src/metabase/lib/utils.js:94
+msgid "two"
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/DisplayOptionsPane.jsx:18
-msgid "Dark"
+#: frontend/src/metabase/lib/utils.js:95
+msgid "three"
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/DisplayOptionsPane.jsx:37
-msgid "Border"
+#: frontend/src/metabase/lib/utils.js:96
+msgid "four"
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/DisplayOptionsPane.jsx:49
-#: frontend/src/metabase/visualizations/lib/settings.js:159
-msgid "Title"
+#: frontend/src/metabase/lib/utils.js:97
+msgid "five"
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:62
-msgid "To embed this {0} in your application:"
+#: frontend/src/metabase/lib/utils.js:98
+msgid "six"
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:64
-msgid ""
-"Insert this code snippet in your server code to generate the signed "
-"embedding URL "
+#: frontend/src/metabase/lib/utils.js:99
+msgid "seven"
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:87
-msgid "Then insert this code snippet in your HTML template or single page app."
+#: frontend/src/metabase/lib/utils.js:100
+msgid "eight"
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:94
-msgid "Embed code snippet for your HTML or Frontend Application"
+#: frontend/src/metabase/lib/utils.js:101
+msgid "nine"
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:101
-msgid "More {0}"
+#: frontend/src/metabase/meta/Dashboard.js:31
+msgid "Month and Year"
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:72
-msgid "Enable sharing"
+#: frontend/src/metabase/meta/Dashboard.js:32
+msgid "Like January, 2016"
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:76
-msgid "Disable this public link?"
+#: frontend/src/metabase/meta/Dashboard.js:36
+msgid "Quarter and Year"
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:77
-msgid ""
-"This will cause the existing link to stop working. You can re-enable it, but "
-"when you do it will be a different link."
+#: frontend/src/metabase/meta/Dashboard.js:37
+msgid "Like Q1, 2016"
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:117
-msgid "Public link"
+#: frontend/src/metabase/meta/Dashboard.js:41
+msgid "Single Date"
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:118
-msgid ""
-"Share this {0} with people who don't have a Metabase account using the URL "
-"below:"
+#: frontend/src/metabase/meta/Dashboard.js:42
+msgid "Like January 31, 2016"
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:158
-msgid "Public embed"
+#: frontend/src/metabase/meta/Dashboard.js:46
+msgid "Date Range"
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:159
-msgid ""
-"Embed this {0} in blog posts or web pages by copying and pasting this "
-"snippet:"
+#: frontend/src/metabase/meta/Dashboard.js:47
+msgid "Like December 25, 2015 - February 14, 2016"
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:176
-msgid "Embed this {0} in an application"
+#: frontend/src/metabase/meta/Dashboard.js:51
+msgid "Relative Date"
 msgstr ""
 
-#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:177
-msgid ""
-"By integrating with your application server code, you can provide a secure "
-"stats {0} limited to a specific user, customer, organization, etc."
+#: frontend/src/metabase/meta/Dashboard.js:52
+msgid "Like \"the last 7 days\" or \"this month\""
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/CardPicker.jsx:63
-msgid "Raw data cannot be included in pulses"
+#: frontend/src/metabase/meta/Dashboard.js:56
+msgid "Date Filter"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/CardPicker.jsx:72
-msgid "Maps cannot be included in pulses"
+#: frontend/src/metabase/meta/Dashboard.js:57
+msgid "All Options"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/CardPicker.jsx:118
-#: frontend/src/metabase/questions/containers/AddToDashboard.jsx:65
-msgid "Everything else"
+#: frontend/src/metabase/meta/Dashboard.js:58
+msgid "Contains all of the above"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/CardPicker.jsx:143
-msgid "Type a question name to filter"
+#: frontend/src/metabase/meta/Dashboard.js:70
+msgid "ZIP or Postal Code"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseCardPreview.jsx:86
-msgid "Remove attachment"
+#: frontend/src/metabase/meta/Dashboard.js:78
+#: frontend/src/metabase/meta/Dashboard.js:108
+msgid "ID"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseCardPreview.jsx:87
-msgid "Attach file with results"
+#: frontend/src/metabase/meta/Dashboard.js:96
+#: frontend/src/metabase/qb/components/actions/PivotByTimeAction.jsx:8
+msgid "Time"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseCardPreview.jsx:119
-msgid "This question will be added as a file attachment"
+#: frontend/src/metabase/meta/Dashboard.js:97
+msgid "Date range, relative date, time of day, etc."
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseCardPreview.jsx:120
-msgid "This question won't be included in your Pulse"
+#: frontend/src/metabase/meta/Dashboard.js:102
+#: frontend/src/metabase/qb/components/actions/PivotByLocationAction.jsx:8
+msgid "Location"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEdit.jsx:89
-msgid "This pulse will no longer be emailed to {0} {1}"
+#: frontend/src/metabase/meta/Dashboard.js:103
+msgid "City, State, Country, ZIP code."
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEdit.jsx:97
-msgid "Slack channel {0} will no longer get this pulse {1}"
+#: frontend/src/metabase/meta/Dashboard.js:109
+msgid "User ID, product ID, event ID, etc."
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEdit.jsx:105
-msgid "Channel {0} will no longer receive this pulse {1}"
+#: frontend/src/metabase/meta/Dashboard.js:114
+msgid "Other Categories"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEdit.jsx:122
-msgid "Edit pulse"
+#: frontend/src/metabase/meta/Dashboard.js:115
+msgid "Category, Type, Model, Rating, etc."
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEdit.jsx:122
-msgid "New pulse"
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:43
+#: frontend/src/metabase/user/components/UserSettings.jsx:54
+msgid "Account settings"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEdit.jsx:126
-msgid "What's a Pulse?"
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:50
+msgid "Exit admin"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEdit.jsx:136
-msgid "Got it"
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:60
+msgid "Logs"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEdit.jsx:151
-msgid "Where should this data go?"
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:67
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorSidebar.jsx:105
+msgid "Help"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEdit.jsx:167
-msgid "Delete this pulse"
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:76
+msgid "About Metabase"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEdit.jsx:169
-msgid "Stop delivery and delete this pulse. There's no undo, so be careful."
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:82
+msgid "Sign out"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEdit.jsx:173
-msgid "Delete this Pulse"
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:107
+msgid "Thanks for using"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEdit.jsx:194
-msgid "Create pulse"
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:111
+msgid "You're on version"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEdit.jsx:195
-#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:268
-msgid "Saving…"
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:114
+msgid "Built on"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:77
-msgid "Attachment"
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:133
+msgid "is a Trademark of"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:91
-msgid "Heads up"
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:135
+msgid "and is built with care in San Francisco, CA"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:92
-msgid "Raw data questions can only be included as email attachments"
+#: frontend/src/metabase/nav/containers/Navbar.jsx:130
+#: frontend/src/metabase/routes.jsx:198
+msgid "Search"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:99
-msgid "Looks like this pulse is getting big"
+#: frontend/src/metabase/nav/containers/Navbar.jsx:195
+msgid "Metabase Admin"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:100
-msgid ""
-"We recommend keeping pulses small and focused to help keep them digestable "
-"and useful to the whole team."
+#: frontend/src/metabase/nav/containers/Navbar.jsx:296
+#: frontend/src/metabase/reference/databases/TableQuestions.jsx:36
+#: frontend/src/metabase/reference/metrics/MetricQuestions.jsx:37
+#: frontend/src/metabase/reference/segments/SegmentQuestions.jsx:37
+msgid "Ask a question"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:140
-msgid "Pick your data"
+#: frontend/src/metabase/nav/containers/Navbar.jsx:305
+msgid "New dashboard"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:142
-msgid "Choose questions you'd like to send in this pulse"
+#: frontend/src/metabase/nav/containers/Navbar.jsx:311
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:127
+msgid "New pulse"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:29
-msgid "Emails"
+#: frontend/src/metabase/nav/containers/Navbar.jsx:319
+msgid "Reference"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:30
-msgid "Slack messages"
+#: frontend/src/metabase/new_query/containers/MetricSearch.jsx:83
+msgid "Which metric?"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:221
-msgid "Sent"
+#: frontend/src/metabase/new_query/containers/MetricSearch.jsx:110
+#: frontend/src/metabase/reference/metrics/MetricList.jsx:24
+msgid ""
+"Defining common metrics for your team makes it even easier to ask questions"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:222
-msgid "{0} will be sent at"
+#: frontend/src/metabase/new_query/containers/MetricSearch.jsx:111
+msgid "No metrics"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:224
-msgid "Messages"
+#: frontend/src/metabase/new_query/containers/MetricSearch.jsx:113
+msgid "How to create metrics"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:235
-msgid "Send email now"
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:138
+msgid ""
+"See data over time, as a map, or pivoted to help you understand trends or "
+"changes."
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:236
-msgid "Send to {0} now"
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:149
+msgid "Custom"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:238
-msgid "Sending…"
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:150
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:517
+msgid "New question"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:239
-msgid "Sending failed"
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:152
+msgid ""
+"Use the simple question builder to see trends, lists of things, or to create "
+"your own metrics."
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:242
-msgid "Didn’t send because the pulse has no results."
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:161
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Native query"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:243
-msgid "Pulse sent"
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:162
+msgid ""
+"For more complicated questions, you can write your own SQL or native query."
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:282
-msgid "{0} needs to be set up by an administrator."
+#: frontend/src/metabase/parameters/components/ParameterValueWidget.jsx:240
+msgid "Select a default value…"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:297
-msgid "Slack"
+#: frontend/src/metabase/parameters/components/widgets/DateAllOptionsWidget.jsx:149
+#: frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx:390
+msgid "Update filter"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEditName.jsx:35
-msgid "Name your pulse"
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:9
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:144
+msgid "Today"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEditName.jsx:37
-msgid "Give your pulse a name to help others understand what it's about"
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:14
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:148
+msgid "Yesterday"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEditName.jsx:49
-msgid "Important metrics"
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:18
+msgid "Past 7 days"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEditSkip.jsx:22
-msgid "Skip if no results"
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:19
+msgid "Past 30 days"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseEditSkip.jsx:24
-msgid "Skip a scheduled Pulse if none of its questions have any results"
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:24
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:29
+#: src/metabase/api/table.clj
+msgid "Week"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseList.jsx:50
-#: frontend/src/metabase/pulse/components/PulseList.jsx:79
-msgid "Create a pulse"
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:25
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:30
+#: src/metabase/api/table.clj
+msgid "Month"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseList.jsx:91
-msgid "pulses"
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:26
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:31
+#: src/metabase/api/table.clj
+msgid "Year"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseListChannel.jsx:65
-msgid "Emailed"
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:152
+msgid "Past 7 Days"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseListChannel.jsx:68
-msgid "Slack'd"
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:156
+msgid "Past 30 Days"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseListChannel.jsx:70
-#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:243
-msgid "No channel"
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:160
+msgid "Last Week"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseListChannel.jsx:81
-msgid "to"
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:164
+msgid "Last Month"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseListChannel.jsx:105
-msgid "You get this {0}"
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:168
+msgid "Last Year"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseListChannel.jsx:121
-msgid "Get this {0}"
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:172
+msgid "This Week"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/PulseListItem.jsx:47
-msgid "Pulse by {0}"
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:176
+msgid "This Month"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/RecipientPicker.jsx:61
-msgid "Enter email addresses you'd like this data to go to"
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:180
+msgid "This Year"
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/WhatsAPulse.jsx:16
-msgid "Help everyone on your team stay in sync with your data."
+#: frontend/src/metabase/parameters/components/widgets/ParameterFieldWidget.jsx:88
+#: frontend/src/metabase/parameters/components/widgets/TextWidget.jsx:54
+msgid "Enter a value..."
 msgstr ""
 
-#: frontend/src/metabase/pulse/components/WhatsAPulse.jsx:29
-msgid ""
-"Pulses let you send data from Metabase to email or Slack on the schedule of "
-"your choice."
+#: frontend/src/metabase/parameters/components/widgets/TextWidget.jsx:90
+msgid "Enter a default value..."
 msgstr ""
 
-#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:100
-msgid "After {0}"
+#: frontend/src/metabase/public/components/PublicError.jsx:18
+msgid "An error occurred"
 msgstr ""
 
-#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:102
-msgid "Before {0}"
+#: frontend/src/metabase/public/components/PublicNotFound.jsx:11
+msgid "Not found"
 msgstr ""
 
-#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:104
-#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:299
-msgid "Is Empty"
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:82
+msgid ""
+"You’ve made changes that need to be published before they will be reflected "
+"in your application embed."
 msgstr ""
 
-#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:106
-#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:305
-msgid "Not Empty"
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:83
+msgid ""
+"You will need to publish this {0} before you can embed it in another "
+"application."
 msgstr ""
 
-#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:109
-#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:216
-msgid "All Time"
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:92
+msgid "Discard Changes"
 msgstr ""
 
-#: frontend/src/metabase/qb/components/actions/CommonMetricsAction.jsx:21
-msgid "View {0}"
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:99
+msgid "Updating..."
 msgstr ""
 
-#: frontend/src/metabase/qb/components/actions/CompoundQueryAction.jsx:14
-msgid "Analyze the results of this Query"
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:100
+msgid "Updated"
 msgstr ""
 
-#: frontend/src/metabase/qb/components/actions/CountByTimeAction.jsx:29
-msgid "Count of rows by time"
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:101
+msgid "Failed!"
 msgstr ""
 
-#: frontend/src/metabase/qb/components/actions/PivotByAction.jsx:55
-msgid "Break out by {0}"
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:102
+msgid "Publish"
 msgstr ""
 
-#: frontend/src/metabase/qb/components/actions/SummarizeBySegmentMetricAction.jsx:34
-msgid "Summarize this segment"
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:111
+msgid "Code"
 msgstr ""
 
-#: frontend/src/metabase/qb/components/actions/UnderlyingDataAction.jsx:14
-msgid "View this as a table"
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:70
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:157
+msgid "Style"
 msgstr ""
 
-#: frontend/src/metabase/qb/components/actions/UnderlyingRecordsAction.jsx:22
-msgid "View the underlying {0} records"
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:80
+msgid "Which parameters can users of this embed use?"
 msgstr ""
 
-#: frontend/src/metabase/qb/components/actions/XRayCard.jsx:19
-msgid "X-ray this question"
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:83
+msgid "This {0} doesn't have any parameters to configure yet."
 msgstr ""
 
-#: frontend/src/metabase/qb/components/actions/XRaySegment.jsx:23
-msgid "X-ray {0}"
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:104
+msgid "Editable"
 msgstr ""
 
-#: frontend/src/metabase/qb/components/drill/CountByColumnDrill.js:29
-#: frontend/src/metabase/xray/containers/FieldXray.jsx:148
-msgid "Distribution"
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:105
+msgid "Locked"
 msgstr ""
 
-#: frontend/src/metabase/qb/components/drill/ObjectDetailDrill.jsx:38
-msgid "View details"
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:113
+msgid "Preview Locked Parameters"
 msgstr ""
 
-#: frontend/src/metabase/qb/components/drill/QuickFilterDrill.jsx:54
-msgid "View this {0}'s {1}"
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:115
+msgid ""
+"Try passing some values to your locked parameters here. Your server will "
+"have to provide the actual values in the signed token when using this for "
+"real."
 msgstr ""
 
-#: frontend/src/metabase/qb/components/drill/SortAction.jsx:45
-msgid "Ascending"
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:126
+msgid "Danger zone"
 msgstr ""
 
-#: frontend/src/metabase/qb/components/drill/SortAction.jsx:57
-msgid "Descending"
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:127
+msgid "This will disable embedding for this {0}."
 msgstr ""
 
-#: frontend/src/metabase/qb/components/drill/SummarizeColumnByTimeDrill.js:44
-msgid "by time"
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:128
+msgid "Unpublish"
 msgstr ""
 
-#: frontend/src/metabase/qb/components/drill/SummarizeColumnDrill.js:21
-msgid "Avg"
+#: frontend/src/metabase/public/components/widgets/DisplayOptionsPane.jsx:17
+msgid "Light"
 msgstr ""
 
-#: frontend/src/metabase/qb/components/drill/SummarizeColumnDrill.js:33
-msgid "Distincts"
+#: frontend/src/metabase/public/components/widgets/DisplayOptionsPane.jsx:18
+msgid "Dark"
 msgstr ""
 
-#: frontend/src/metabase/qb/components/drill/UnderlyingRecordsDrill.jsx:30
-msgid "View {0} {1}"
+#: frontend/src/metabase/public/components/widgets/DisplayOptionsPane.jsx:37
+msgid "Border"
 msgstr ""
 
-#: frontend/src/metabase/qb/components/drill/UnderlyingRecordsDrill.jsx:30
-msgid "these"
+#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:62
+msgid "To embed this {0} in your application:"
 msgstr ""
 
-#: frontend/src/metabase/qb/components/drill/UnderlyingRecordsDrill.jsx:30
-msgid "this"
+#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:64
+msgid ""
+"Insert this code snippet in your server code to generate the signed "
+"embedding URL "
 msgstr ""
 
-#: frontend/src/metabase/qb/components/drill/ZoomDrill.jsx:26
-msgid "Zoom in"
+#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:87
+msgid "Then insert this code snippet in your HTML template or single page app."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AggregationPopover.jsx:19
-msgid "Custom Expression"
+#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:94
+msgid "Embed code snippet for your HTML or Frontend Application"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AggregationPopover.jsx:20
-msgid "Common Metrics"
+#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:101
+msgid "More {0}"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AggregationPopover.jsx:182
-msgid "Metabasics"
+#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:103
+msgid "examples on GitHub"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AggregationPopover.jsx:290
-msgid "Name (optional)"
+#: frontend/src/metabase/public/components/widgets/EmbedModalContent.jsx:290
+msgid "Sharing"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AggregationWidget.jsx:153
-msgid "Choose an aggregation"
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:72
+msgid "Enable sharing"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:113
-msgid "Set up your own alert"
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:76
+msgid "Disable this public link?"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:153
-msgid "Unsubscribing..."
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:77
+msgid ""
+"This will cause the existing link to stop working. You can re-enable it, but "
+"when you do it will be a different link."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:158
-msgid "Failed to unsubscribe"
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:117
+msgid "Public link"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:212
-msgid "Unsubscribe"
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:118
+msgid ""
+"Share this {0} with people who don't have a Metabase account using the URL "
+"below:"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:270
-msgid "Okay, you're unsubscribed"
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:158
+msgid "Public embed"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:342
-msgid "You're receiving {0}'s alerts"
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:159
+msgid ""
+"Embed this {0} in blog posts or web pages by copying and pasting this "
+"snippet:"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:343
-msgid "{0} set up an alert"
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:176
+msgid "Embed this {0} in an application"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:149
-msgid "alerts"
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:177
+msgid ""
+"By integrating with your application server code, you can provide a secure "
+"stats {0} limited to a specific user, customer, organization, etc."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:172
-msgid "Let's set up your alert"
+#: frontend/src/metabase/pulse/components/PulseCardPreview.jsx:94
+msgid "Remove attachment"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:203
-msgid "The wide world of alerts"
+#: frontend/src/metabase/pulse/components/PulseCardPreview.jsx:95
+msgid "Attach file with results"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:204
-msgid "There are a few different kinds of alerts you can get"
+#: frontend/src/metabase/pulse/components/PulseCardPreview.jsx:127
+msgid "This question will be added as a file attachment"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:217
-msgid "When a raw data question {0}"
+#: frontend/src/metabase/pulse/components/PulseCardPreview.jsx:128
+msgid "This question won't be included in your Pulse"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:228
-msgid "When a line or bar {0}"
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:92
+msgid "This pulse will no longer be emailed to {0} {1}"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:239
-msgid "When a progress bar {0}"
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:94
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:374
+msgid "{0} address"
+msgid_plural "{0} addresses"
+msgstr[0] ""
+msgstr[1] ""
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:102
+msgid "Slack channel {0} will no longer get this pulse {1}"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:246
-msgid "Set up an alert"
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:110
+msgid "Channel {0} will no longer receive this pulse {1}"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:309
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:127
+msgid "Edit pulse"
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:131
+msgid "What's a Pulse?"
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:141
+msgid "Got it"
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:157
+msgid "Where should this data go?"
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:173
+msgid "Unarchiving…"
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:174
+msgid "Unarchive failed"
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:175
+msgid "Unarchived"
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:182
+msgid "Create pulse"
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:90
+msgid "Attachment"
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:104
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:111
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:671
+msgid "Heads up"
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:105
+msgid ""
+"We'll show the first 10 columns and 20 rows of this table in your Pulse. If "
+"you email this, we'll add a file attachment with all columns and up to 2,000 "
+"rows."
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:112
+msgid "Raw data questions can only be included as email attachments"
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:119
+msgid "Looks like this pulse is getting big"
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:120
+msgid ""
+"We recommend keeping pulses small and focused to help keep them digestible "
+"and useful to the whole team."
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:160
+msgid "Pick your data"
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:162
+msgid "Choose questions you'd like to send in this pulse"
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:29
+msgid "Emails"
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:30
+msgid "Slack messages"
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:218
+msgid "Sent"
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:219
+msgid "{0} will be sent at"
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:221
+msgid "Messages"
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:232
+msgid "Send email now"
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:233
+msgid "Send to {0} now"
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:235
+msgid "Sending…"
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:236
+msgid "Sending failed"
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:239
+msgid "Didn’t send because the pulse has no results."
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:240
+msgid "Pulse sent"
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:279
+msgid "{0} needs to be set up by an administrator."
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:294
+msgid "Slack"
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/PulseEditCollection.jsx:12
+msgid "Which collection should this pulse live in?"
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/PulseEditName.jsx:35
+msgid "Name your pulse"
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/PulseEditName.jsx:37
+msgid "Give your pulse a name to help others understand what it's about"
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/PulseEditName.jsx:49
+msgid "Important metrics"
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/PulseEditSkip.jsx:22
+msgid "Skip if no results"
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/PulseEditSkip.jsx:24
+msgid "Skip a scheduled Pulse if none of its questions have any results"
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/RecipientPicker.jsx:65
+msgid "Enter email addresses you'd like this data to go to"
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/WhatsAPulse.jsx:16
+msgid "Help everyone on your team stay in sync with your data."
+msgstr ""
+
+#: frontend/src/metabase/pulse/components/WhatsAPulse.jsx:30
+msgid ""
+"Pulses let you send data from Metabase to email or Slack on the schedule of "
+"your choice."
+msgstr ""
+
+#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:100
+msgid "After {0}"
+msgstr ""
+
+#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:102
+msgid "Before {0}"
+msgstr ""
+
+#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:104
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:299
+msgid "Is Empty"
+msgstr ""
+
+#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:106
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:305
+msgid "Not Empty"
+msgstr ""
+
+#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:109
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:216
+msgid "All Time"
+msgstr ""
+
+#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:154
+msgid "Apply"
+msgstr ""
+
+#: frontend/src/metabase/qb/components/actions/CommonMetricsAction.jsx:21
+msgid "View {0}"
+msgstr ""
+
+#: frontend/src/metabase/qb/components/actions/CompareWithTable.jsx:29
+msgid "Compare this with all rows in the table"
+msgstr ""
+
+#: frontend/src/metabase/qb/components/actions/CompoundQueryAction.jsx:14
+msgid "Analyze the results of this Query"
+msgstr ""
+
+#: frontend/src/metabase/qb/components/actions/CountByTimeAction.jsx:29
+msgid "Count of rows by time"
+msgstr ""
+
+#: frontend/src/metabase/qb/components/actions/PivotByAction.jsx:55
+msgid "Break out by {0}"
+msgstr ""
+
+#: frontend/src/metabase/qb/components/actions/SummarizeBySegmentMetricAction.jsx:34
+msgid "Summarize this segment"
+msgstr ""
+
+#: frontend/src/metabase/qb/components/actions/UnderlyingDataAction.jsx:14
+msgid "View this as a table"
+msgstr ""
+
+#: frontend/src/metabase/qb/components/actions/UnderlyingRecordsAction.jsx:22
+msgid "View the underlying {0} records"
+msgstr ""
+
+#: frontend/src/metabase/qb/components/actions/XRayCard.jsx:15
+msgid "X-Ray this question"
+msgstr ""
+
+#: frontend/src/metabase/qb/components/drill/AutomaticDashboardDrill.jsx:27
+msgid "X-ray"
+msgstr ""
+
+#: frontend/src/metabase/qb/components/drill/CompareToRestDrill.js:27
+msgid "Compare to the rest"
+msgstr ""
+
+#: frontend/src/metabase/qb/components/drill/DistributionDrill.jsx:35
+msgid "Distribution"
+msgstr ""
+
+#: frontend/src/metabase/qb/components/drill/ObjectDetailDrill.jsx:38
+msgid "View details"
+msgstr ""
+
+#: frontend/src/metabase/qb/components/drill/QuickFilterDrill.jsx:54
+msgid "View this {0}'s {1}"
+msgstr ""
+
+#: frontend/src/metabase/qb/components/drill/SortAction.jsx:45
+msgid "Ascending"
+msgstr ""
+
+#: frontend/src/metabase/qb/components/drill/SortAction.jsx:57
+msgid "Descending"
+msgstr ""
+
+#: frontend/src/metabase/qb/components/drill/SummarizeColumnByTimeDrill.js:47
+msgid "over time"
+msgstr ""
+
+#: frontend/src/metabase/qb/components/drill/SummarizeColumnDrill.js:21
+msgid "Avg"
+msgstr ""
+
+#: frontend/src/metabase/qb/components/drill/SummarizeColumnDrill.js:33
+msgid "Distincts"
+msgstr ""
+
+#: frontend/src/metabase/qb/components/drill/UnderlyingRecordsDrill.jsx:32
+msgid "View this {0}"
+msgid_plural "View these {0}"
+msgstr[0] ""
+msgstr[1] ""
+
+#: frontend/src/metabase/query_builder/components/AggregationPopover.jsx:19
+msgid "Custom Expression"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AggregationPopover.jsx:20
+msgid "Common Metrics"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AggregationPopover.jsx:182
+msgid "Metabasics"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AggregationPopover.jsx:290
+msgid "Name (optional)"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AggregationWidget.jsx:153
+msgid "Choose an aggregation"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:115
+msgid "Set up your own alert"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:155
+msgid "Unsubscribing..."
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:160
+msgid "Failed to unsubscribe"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:216
+msgid "Unsubscribe"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:247
+msgid "No channel"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:274
+msgid "Okay, you're unsubscribed"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:346
+msgid "You're receiving {0}'s alerts"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:347
+msgid "{0} set up an alert"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:161
+msgid "alerts"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:184
+msgid "Let's set up your alert"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:215
+msgid "The wide world of alerts"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:216
+msgid "There are a few different kinds of alerts you can get"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:229
+msgid "When a raw data question {0}"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:230
+msgid "returns any results"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:240
+msgid "When a line or bar {0}"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:241
+msgid "crosses a goal line"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:251
+msgid "When a progress bar {0}"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:252
+msgid "reaches its goal"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:260
+msgid "Set up an alert"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:329
 msgid "Edit your alert"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:309
-msgid "Edit alert"
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:329
+msgid "Edit alert"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:372
+msgid "This alert will no longer be emailed to {0}."
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:380
+msgid "Slack channel {0} will no longer get this alert."
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:384
+msgid "Channel {0} will no longer receive this alert."
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:401
+msgid "Delete this alert"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:403
+msgid "Stop delivery and delete this alert. There's no undo, so be careful."
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:411
+msgid "Delete this alert?"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:495
+msgid "Alert me when the line…"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:496
+msgid "Alert me when the progress bar…"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:499
+msgid "Goes above the goal line"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:499
+msgid "Reaches the goal"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:502
+msgid "Goes below the goal line"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:502
+msgid "Goes below the goal"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:510
+msgid "The first time it crosses, or every time?"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:511
+msgid "The first time it reaches the goal, or every time?"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:513
+msgid "The first time"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:514
+msgid "Every time"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:617
+msgid "Where do you want to send these alerts?"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:628
+msgid "Email alerts to:"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:670
+msgid ""
+"{0} Goal-based alerts aren't yet supported for charts with more than one "
+"line, so this alert will be sent whenever the chart has {1}."
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:673
+msgid "results"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:677
+msgid ""
+"{0} This kind of alert is most useful when your saved question doesn’t {1} "
+"return any results, but you want to know when it does."
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:678
+msgid "Tip"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:680
+msgid "usually"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:57
+msgid "Pick a segment or table"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:73
+msgid "Select a database"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:88
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:87
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:187
+#: frontend/src/metabase/reference/components/MetricImportantFieldsDetail.jsx:35
+msgid "Select..."
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:128
+msgid "Select a table"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:785
+msgid "No tables found in this database."
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:822
+msgid "Is a question missing?"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:826
+msgid "Learn more about nested queries"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:860
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:30
+msgid "Fields"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:938
+msgid "No segments were found."
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:961
+msgid "Find a segment"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/ExpandableString.jsx:46
+msgid "View less"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/ExpandableString.jsx:56
+msgid "View more"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/ExtendedOptions.jsx:111
+msgid "Pick a field to sort by"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/ExtendedOptions.jsx:124
+msgid "Sort"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/ExtendedOptions.jsx:193
+msgid "Row limit"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/FieldName.jsx:76
+msgid "Unknown Field"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/FieldName.jsx:79
+msgid "field"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/Filter.jsx:113
+msgid "Matches"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:152
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:160
+msgid "Add filters to narrow your answer"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:284
+msgid "Add a grouping"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:322
+#: frontend/src/metabase/visualizations/components/LineAreaBarChart.jsx:102
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:55
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:92
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:131
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:58
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:66
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:73
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:54
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:61
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:66
+#: frontend/src/metabase/visualizations/visualizations/Table.jsx:60
+#: frontend/src/metabase/visualizations/visualizations/Table.jsx:72
+msgid "Data"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:352
+msgid "Filtered by"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:369
+msgid "View"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:386
+msgid "Grouped By"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/LimitWidget.jsx:27
+msgid "None"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:338
+msgid "This question is written in {0}."
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:346
+msgid "Hide Editor"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:347
+msgid "Hide Query"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:352
+msgid "Open Editor"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:353
+msgid "Show Query"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/QueryDefinitionTooltip.jsx:25
+msgid "This metric has been retired.  It's no longer available for use."
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:34
+#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:46
+msgid "Download full results"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:35
+msgid "Download this data"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:52
+msgid ""
+"Your answer has a large number of rows so it could take a while to download."
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:53
+msgid "The maximum download size is 1 million rows."
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:232
+msgid "Edit question"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:249
+msgid "SAVE CHANGES"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:263
+msgid "CANCEL"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:276
+msgid "Move question"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:283
+msgid "Which collection should this be in?"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:313
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:110
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorSidebar.jsx:83
+msgid "Variables"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:432
+msgid "Learn about your data"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:460
+msgid "Alerts are on"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:522
+msgid "started from"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:48
+msgid "SQL"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:48
+msgid "native query"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:52
+msgid "Not Supported"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:58
+msgid "View the {0}"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:59
+msgid "Switch to {0}"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:62
+msgid "Switch to Builder"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:87
+msgid "{0} for this question"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:111
+msgid "Convert this question to {0}"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:122
+msgid "This question will take approximately {0} to refresh"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:131
+msgid "Updated {0}"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:141
+msgid "row"
+msgid_plural "rows"
+msgstr[0] ""
+msgstr[1] ""
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:148
+msgid "Showing first {0} {1}"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:151
+msgid "Showing {0} {1}"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:281
+msgid "Doing science"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:294
+msgid "If you give me some data I can show you something cool. Run a Query!"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:299
+msgid "How do I use this thing?"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/RunButton.jsx:28
+msgid "Get Answer"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/SavedQuestionIntroModal.jsx:12
+msgid "It's okay to play around with saved questions"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/SavedQuestionIntroModal.jsx:14
+msgid ""
+"You won't make any permanent changes to a saved question unless you click "
+"the edit icon in the top-right."
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/SearchBar.jsx:28
+msgid "Search for"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/SelectionModule.jsx:158
+msgid "Advanced..."
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/SelectionModule.jsx:167
+msgid "Sorry. Something went wrong."
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/TimeGroupingPopover.jsx:40
+msgid "Group time by"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:46
+msgid "Your question took too long"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:47
+msgid ""
+"We didn't get an answer back from your database in time, so we had to stop. "
+"You can try again in a minute, or if the problem persists, you can email an "
+"admin to let them know."
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:55
+msgid "We're experiencing server issues"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:56
+msgid ""
+"Try refreshing the page after waiting a minute or two. If the problem "
+"persists we'd recommend you contact an admin."
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:88
+msgid "There was a problem with your question"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:89
+msgid ""
+"Most of the time this is caused by an invalid selection or bad input value. "
+"Double check your inputs and retry your query."
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/VisualizationResult.jsx:60
+msgid ""
+"This may be the answer you’re looking for. If not, try removing or changing "
+"your filters to make them less specific."
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/VisualizationResult.jsx:66
+msgid "You can also {0} when there are some results."
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/VisualizationResult.jsx:68
+msgid "get an alert"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/VisualizationResult.jsx:77
+msgid "Back to last run"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/VisualizationSettings.jsx:53
+msgid "Visualization"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/dataref/DetailPane.jsx:17
+#: frontend/src/metabase/query_builder/components/dataref/TablePane.jsx:151
+msgid "No description set."
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/dataref/DetailPane.jsx:21
+msgid "Use for current question"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/dataref/DetailPane.jsx:33
+#: frontend/src/metabase/reference/components/UsefulQuestions.jsx:16
+msgid "Potentially useful questions"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/dataref/FieldPane.jsx:156
+msgid "Group by {0}"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/dataref/FieldPane.jsx:165
+msgid "Sum of all values of {0}"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/dataref/FieldPane.jsx:173
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:63
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:51
+msgid "All distinct values of {0}"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/dataref/FieldPane.jsx:177
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:39
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:51
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:39
+msgid "Number of {0} grouped by {1}"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/dataref/MainPane.jsx:11
+#: frontend/src/metabase/reference/databases/DatabaseSidebar.jsx:20
+#: frontend/src/metabase/reference/databases/FieldSidebar.jsx:27
+#: frontend/src/metabase/reference/databases/TableSidebar.jsx:23
+#: frontend/src/metabase/reference/guide/BaseSidebar.jsx:17
+#: frontend/src/metabase/reference/guide/BaseSidebar.jsx:19
+#: frontend/src/metabase/reference/metrics/MetricSidebar.jsx:20
+#: frontend/src/metabase/reference/segments/SegmentFieldSidebar.jsx:24
+#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:20
+msgid "Data Reference"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/dataref/MainPane.jsx:13
+msgid "Learn more about your data structure to ask more useful questions"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/dataref/MetricPane.jsx:58
+#: frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx:84
+msgid "Could not find the table metadata prior to creating a new question"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/dataref/MetricPane.jsx:80
+msgid "See {0}"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/dataref/MetricPane.jsx:94
+msgid "Metric Definition"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx:118
+msgid "Filter by {0}"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx:127
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:36
+msgid "Number of {0}"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx:134
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:46
+msgid "See all {0}"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx:148
+msgid "Segment Definition"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/dataref/TablePane.jsx:50
+msgid "An error occurred loading the table"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/dataref/TablePane.jsx:74
+msgid "See the raw data for {0}"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/dataref/TablePane.jsx:205
+msgid "More"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:31
+msgid "Aggregations"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:32
+msgid "Operators"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:200
+msgid "Invalid expression"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:275
+msgid "unknown error"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:46
+msgid "Field formula"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:57
+msgid ""
+"Think of this as being kind of like writing a formula in a spreadsheet "
+"program: you can use numbers, fields in this table, mathematical symbols "
+"like +, and some functions. So you could type something like Subtotal - Cost."
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:62
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:126
+msgid "Learn more"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:66
+msgid "Give it a name"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:72
+msgid "Something nice and descriptive"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/expressions/Expressions.jsx:30
+msgid "Custom fields"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/expressions/Expressions.jsx:60
+msgid "Add a custom field"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:17
+msgid "Include {0}"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:19
+msgid "Case sensitive"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:23
+msgid "today"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:24
+msgid "this week"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:25
+msgid "this month"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:26
+msgid "this year"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:27
+msgid "this minute"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:28
+msgid "this hour"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx:285
+msgid "not implemented {0}"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx:286
+msgid "true"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx:286
+msgid "false"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx:390
+msgid "Add filter"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/filters/FilterWidgetList.jsx:64
+msgid "Item"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:224
+msgid "Previous"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:255
+msgid "Current"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:282
+msgid "On"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/NumberPicker.jsx:47
+msgid "Enter desired number"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/SelectPicker.jsx:83
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:100
+msgid "Empty"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/SelectPicker.jsx:116
+msgid "Find a value"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/SpecificDatePicker.jsx:113
+msgid "Hide calendar"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/SpecificDatePicker.jsx:113
+msgid "Show calendar"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/TextPicker.jsx:97
+msgid "You can enter multiple values separated by commas"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/TextPicker.jsx:38
+msgid "Enter desired text"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:83
+msgid "Try it"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:105
+msgid "What's this for?"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:107
+msgid ""
+"Variables in native queries let you dynamically replace values in your "
+"queries using filter widgets or through the URL."
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:112
+msgid ""
+"{0} creates a variable in this SQL template called \"variable_name\". "
+"Variables can be given types in the side panel, which changes their "
+"behavior. All variable types other than \"Field Filter\" will automatically "
+"cause a filter widget to be placed on this question; with Field Filters, "
+"this is optional. When this filter widget is filled in, that value replaces "
+"the variable in the SQL template."
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:121
+msgid "Field Filters"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:123
+msgid ""
+"Giving a variable the \"Field Filter\" type allows you to link SQL cards to "
+"dashboard filter widgets or use more types of filter widgets on your SQL "
+"question. A Field Filter variable inserts SQL similar to that generated by "
+"the GUI query builder when adding filters on existing columns."
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:126
+msgid ""
+"When adding a Field Filter variable, you'll need to map it to a specific "
+"field. You can then choose to display a filter widget on your question, but "
+"even if you don't, you can now map your Field Filter variable to a dashboard "
+"filter when adding this question to a dashboard. Field Filters should be "
+"used inside of a \"WHERE\" clause."
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:130
+msgid "Optional Clauses"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:132
+msgid ""
+"brackets around a {0} create an optional clause in the template. If "
+"\"variable\" is set, then the entire clause is placed into the template. If "
+"not, then the entire clause is ignored."
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:142
+msgid ""
+"To use multiple optional clauses you can include at least one non-optional "
+"WHERE clause followed by optional clauses starting with \"AND\"."
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:154
+msgid "Read the full documentation"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:124
+msgid "Filter label"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:136
+msgid "Variable type"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:145
+msgid "Text"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:147
+msgid "Date"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:148
+msgid "Field Filter"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:154
+msgid "Field to map to"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:176
+msgid "Filter widget type"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:199
+msgid "Required?"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:210
+msgid "Default filter widget value"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/containers/ArchiveQuestionModal.jsx:46
+msgid "Archive this question?"
+msgstr ""
+
+#: frontend/src/metabase/query_builder/containers/ArchiveQuestionModal.jsx:57
+msgid "This question will be removed from any dashboards or pulses using it."
+msgstr ""
+
+#: frontend/src/metabase/query_builder/containers/QueryBuilder.jsx:136
+msgid "Question"
+msgstr ""
+
+#: frontend/src/metabase/questions/containers/AddToDashboard.jsx:11
+msgid "Pick a question to add"
+msgstr ""
+
+#: frontend/src/metabase/reference/components/EditHeader.jsx:19
+msgid "You are editing this page"
+msgstr ""
+
+#: frontend/src/metabase/reference/components/EditableReferenceHeader.jsx:101
+#: frontend/src/metabase/reference/components/ReferenceHeader.jsx:63
+msgid "See this {0}"
+msgstr ""
+
+#: frontend/src/metabase/reference/components/EditableReferenceHeader.jsx:120
+msgid "A subset of"
+msgstr ""
+
+#: frontend/src/metabase/reference/components/Field.jsx:47
+#: frontend/src/metabase/reference/components/Field.jsx:86
+#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:32
+#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:68
+msgid "Select a field type"
+msgstr ""
+
+#: frontend/src/metabase/reference/components/Field.jsx:56
+#: frontend/src/metabase/reference/components/Field.jsx:71
+#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:41
+#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:57
+msgid "No field type"
+msgstr ""
+
+#: frontend/src/metabase/reference/components/FieldToGroupBy.jsx:22
+msgid "by"
+msgstr ""
+
+#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:25
+#: frontend/src/metabase/reference/databases/FieldList.jsx:152
+#: frontend/src/metabase/reference/segments/SegmentFieldList.jsx:153
+msgid "Field type"
+msgstr ""
+
+#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:72
+msgid "Select a Foreign Key"
+msgstr ""
+
+#: frontend/src/metabase/reference/components/Formula.jsx:53
+msgid "View the {0} formula"
+msgstr ""
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:80
+msgid "Why this {0} is important"
+msgstr ""
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:81
+msgid "Why this {0} is interesting"
+msgstr ""
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:87
+msgid "Nothing important yet"
+msgstr ""
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:88
+#: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:168
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:233
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:211
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:215
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:219
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:229
+msgid "Nothing interesting yet"
+msgstr ""
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:93
+msgid "Things to be aware of about this {0}"
+msgstr ""
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:97
+#: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:178
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:243
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:221
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:225
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:229
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:239
+msgid "Nothing to be aware of yet"
+msgstr ""
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:103
+msgid "Explore this metric"
+msgstr ""
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:105
+msgid "View this metric"
+msgstr ""
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:112
+msgid "By {0}"
+msgstr ""
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:146
+msgid "Remove item"
+msgstr ""
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:155
+msgid "Why is this dashboard the most important?"
+msgstr ""
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:156
+msgid "What is useful or interesting about this {0}?"
+msgstr ""
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:160
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:174
+msgid "Write something helpful here"
+msgstr ""
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:169
+msgid "Is there anything users of this dashboard should be aware of?"
+msgstr ""
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:170
+msgid "Anything users should be aware of about this {0}?"
+msgstr ""
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:182
+#: frontend/src/metabase/reference/components/MetricImportantFieldsDetail.jsx:26
+msgid "Which 2-3 fields do you usually group this metric by?"
+msgstr ""
+
+#: frontend/src/metabase/reference/components/GuideHeader.jsx:23
+msgid ""
+"This is the perfect place to start if you’re new to your company’s data, or "
+"if you just want to check in on what’s going on."
+msgstr ""
+
+#: frontend/src/metabase/reference/components/MetricImportantFieldsDetail.jsx:65
+msgid "Most useful fields to group this metric by"
+msgstr ""
+
+#: frontend/src/metabase/reference/components/RevisionMessageModal.jsx:32
+msgid "Reason for changes"
+msgstr ""
+
+#: frontend/src/metabase/reference/components/RevisionMessageModal.jsx:36
+msgid ""
+"Leave a note to explain what changes you made and why they were required"
+msgstr ""
+
+#: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:166
+msgid "Why this database is interesting"
+msgstr ""
+
+#: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:176
+msgid "Things to be aware of about this database"
+msgstr ""
+
+#: frontend/src/metabase/reference/databases/DatabaseList.jsx:46
+#: frontend/src/metabase/reference/guide/BaseSidebar.jsx:39
+msgid "Databases and tables"
+msgstr ""
+
+#: frontend/src/metabase/reference/databases/DatabaseSidebar.jsx:27
+#: frontend/src/metabase/reference/databases/FieldSidebar.jsx:38
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:170
+#: frontend/src/metabase/reference/databases/TableSidebar.jsx:31
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:184
+#: frontend/src/metabase/reference/metrics/MetricSidebar.jsx:27
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:188
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:187
+#: frontend/src/metabase/reference/segments/SegmentFieldSidebar.jsx:31
+#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:27
+msgid "Details"
+msgstr ""
+
+#: frontend/src/metabase/reference/databases/DatabaseSidebar.jsx:33
+#: frontend/src/metabase/reference/databases/TableList.jsx:111
+msgid "Tables in {0}"
+msgstr ""
+
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:222
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:200
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:218
+msgid "Actual name in database"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:352
-msgid "This alert will no longer be emailed to {0}."
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:231
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:227
+msgid "Why this field is interesting"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:358
-msgid "Slack channel {0} will no longer get this alert."
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:241
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:237
+msgid "Things to be aware of about this field"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:362
-msgid "Channel {0} will no longer receive this alert."
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:253
+#: frontend/src/metabase/reference/databases/FieldList.jsx:155
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:249
+#: frontend/src/metabase/reference/segments/SegmentFieldList.jsx:156
+msgid "Data type"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:379
-msgid "Delete this alert"
+#: frontend/src/metabase/reference/databases/FieldList.jsx:39
+#: frontend/src/metabase/reference/segments/SegmentFieldList.jsx:39
+msgid "Fields in this table will appear here as they're added"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:381
-msgid "Stop delivery and delete this alert. There's no undo, so be careful."
+#: frontend/src/metabase/reference/databases/FieldList.jsx:134
+#: frontend/src/metabase/reference/segments/SegmentFieldList.jsx:135
+msgid "Fields in {0}"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:389
-msgid "Delete this alert?"
+#: frontend/src/metabase/reference/databases/FieldList.jsx:149
+#: frontend/src/metabase/reference/segments/SegmentFieldList.jsx:150
+msgid "Field name"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:473
-msgid "Alert me when the line…"
+#: frontend/src/metabase/reference/databases/FieldSidebar.jsx:46
+msgid "X-ray this field"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:474
-msgid "Alert me when the progress bar…"
+#: frontend/src/metabase/reference/databases/NoDatabasesEmptyState.jsx:8
+msgid "Metabase is no fun without any data"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:477
-msgid "Goes above the goal line"
+#: frontend/src/metabase/reference/databases/NoDatabasesEmptyState.jsx:9
+msgid "Your databases will appear here once you connect one"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:477
-msgid "Reaches the goal"
+#: frontend/src/metabase/reference/databases/NoDatabasesEmptyState.jsx:10
+msgid "Databases will appear here once your admins have added some"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:480
-msgid "Goes below the goal line"
+#: frontend/src/metabase/reference/databases/NoDatabasesEmptyState.jsx:12
+msgid "Connect a database"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:480
-msgid "Goes below the goal"
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:38
+msgid "Count of {0}"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:488
-msgid "The first time it crosses, or every time?"
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:47
+msgid "See raw data for {0}"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:489
-msgid "The first time it reaches the goal, or every time?"
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:209
+msgid "Why this table is interesting"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:491
-msgid "The first time"
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:219
+msgid "Things to be aware of about this table"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:492
-msgid "Every time"
+#: frontend/src/metabase/reference/databases/TableList.jsx:30
+msgid "Tables in this database will appear here as they're added"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:595
-msgid "Where do you want to send these alerts?"
+#: frontend/src/metabase/reference/databases/TableQuestions.jsx:34
+msgid "Questions about this table will appear here as they're added"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:606
-msgid "Email alerts to:"
+#: frontend/src/metabase/reference/databases/TableQuestions.jsx:71
+#: frontend/src/metabase/reference/metrics/MetricQuestions.jsx:75
+#: frontend/src/metabase/reference/metrics/MetricSidebar.jsx:33
+#: frontend/src/metabase/reference/segments/SegmentQuestions.jsx:74
+msgid "Questions about {0}"
+msgstr ""
+
+#: frontend/src/metabase/reference/databases/TableQuestions.jsx:95
+#: frontend/src/metabase/reference/metrics/MetricQuestions.jsx:99
+#: frontend/src/metabase/reference/segments/SegmentQuestions.jsx:98
+msgid "Created {0} by {1}"
+msgstr ""
+
+#: frontend/src/metabase/reference/databases/TableSidebar.jsx:37
+msgid "Fields in this table"
+msgstr ""
+
+#: frontend/src/metabase/reference/databases/TableSidebar.jsx:45
+msgid "Questions about this table"
+msgstr ""
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:157
+msgid "Help your team get started with your data."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:648
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:159
 msgid ""
-"{0} Goal-based alerts aren't yet supported for charts with more than one "
-"line, so this alert will be sent whenever the chart has {1}."
+"Show your team what’s most important by choosing your top dashboard, "
+"metrics, and segments."
+msgstr ""
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:165
+msgid "Get started"
+msgstr ""
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:173
+msgid "Our most important dashboard"
+msgstr ""
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:188
+msgid "Numbers that we pay attention to"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/AlertModals.jsx:655
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:213
 msgid ""
-"{0} This kind of alert is most useful when your saved question doesn’t {1} "
-"return any results, but you want to know when it does."
+"Metrics are important numbers your company cares about. They often represent "
+"a core indicator of how the business is performing."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/DataSelector.jsx:57
-msgid "Pick a segment or table"
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:221
+msgid "See all metrics"
+msgstr ""
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:235
+msgid "Segments and tables"
+msgstr ""
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:236
+msgid "Tables"
+msgstr ""
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:262
+msgid ""
+"Segments and tables are the building blocks of your company's data. Tables "
+"are collections of the raw information while segments are specific slices "
+"with specific meanings, like {0}"
+msgstr ""
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:267
+msgid "Tables are the building blocks of your company's data."
+msgstr ""
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:277
+msgid "See all segments"
+msgstr ""
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:293
+msgid "See all tables"
+msgstr ""
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:301
+msgid "Other things to know about our data"
+msgstr ""
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:302
+msgid "Find out more"
+msgstr ""
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:307
+msgid ""
+"A good way to get to know your data is by spending a bit of time exploring "
+"the different tables and other info available to you. It may take a while, "
+"but you'll start to recognize names and meanings over time."
+msgstr ""
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:313
+msgid "Explore our data"
+msgstr ""
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:321
+msgid "Have questions?"
+msgstr ""
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:326
+msgid "Contact {0}"
+msgstr ""
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:248
+msgid "Help new Metabase users find their way around."
+msgstr ""
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:251
+msgid ""
+"The Getting Started guide highlights the dashboard, metrics, segments, and "
+"tables that matter most, and informs your users of important things they "
+"should know before digging into the data."
+msgstr ""
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:258
+msgid "Is there an important dashboard for your team?"
+msgstr ""
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:260
+msgid "Create a dashboard now"
+msgstr ""
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:266
+msgid "What is your most important dashboard?"
+msgstr ""
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:285
+msgid "Do you have any commonly referenced metrics?"
+msgstr ""
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:287
+msgid "Learn how to define a metric"
+msgstr ""
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:300
+msgid "What are your 3-5 most commonly referenced metrics?"
+msgstr ""
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:344
+msgid "Add another metric"
+msgstr ""
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:357
+msgid "Do you have any commonly referenced segments or tables?"
+msgstr ""
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:359
+msgid "Learn how to create a segment"
+msgstr ""
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:372
+msgid ""
+"What are 3-5 commonly referenced segments or tables that would be useful for "
+"this audience?"
+msgstr ""
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:418
+msgid "Add another segment or table"
+msgstr ""
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:427
+msgid ""
+"Is there anything your users should understand or know before they start "
+"accessing the data?"
+msgstr ""
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:433
+msgid "What should a user of this data know before they start accessing it?"
+msgstr ""
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:437
+msgid ""
+"E.g., expectations around data privacy and use, common pitfalls or "
+"misunderstandings, information about data warehouse performance, legal "
+"notices, etc."
+msgstr ""
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:448
+msgid ""
+"Is there someone your users could contact for help if they're confused about "
+"this guide?"
+msgstr ""
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:457
+msgid "Who should users contact for help if they're confused about this data?"
+msgstr ""
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:75
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:95
+msgid "Please enter a revision message"
+msgstr ""
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:213
+msgid "Why this Metric is interesting"
+msgstr ""
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:223
+msgid "Things to be aware of about this Metric"
+msgstr ""
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:233
+msgid "How this Metric is calculated"
+msgstr ""
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:235
+msgid "Nothing on how it's calculated yet"
+msgstr ""
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:293
+msgid "Other fields you can group this metric by"
+msgstr ""
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:294
+msgid "Fields you can group this metric by"
+msgstr ""
+
+#: frontend/src/metabase/reference/metrics/MetricList.jsx:23
+msgid "Metrics are the official numbers that your team cares about"
+msgstr ""
+
+#: frontend/src/metabase/reference/metrics/MetricList.jsx:25
+msgid "Metrics will appear here once your admins have created some"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/DataSelector.jsx:73
-msgid "Select a database"
+#: frontend/src/metabase/reference/metrics/MetricList.jsx:27
+msgid "Learn how to create metrics"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/DataSelector.jsx:88
-#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:87
-#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:187
-#: frontend/src/metabase/reference/components/MetricImportantFieldsDetail.jsx:35
-msgid "Select..."
+#: frontend/src/metabase/reference/metrics/MetricQuestions.jsx:35
+msgid "Questions about this metric will appear here as they're added"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/DataSelector.jsx:128
-msgid "Select a table"
+#: frontend/src/metabase/reference/metrics/MetricRevisions.jsx:29
+msgid "There are no revisions for this metric"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/DataSelector.jsx:784
-msgid "No tables found in this database."
+#: frontend/src/metabase/reference/metrics/MetricRevisions.jsx:88
+#: frontend/src/metabase/reference/metrics/MetricSidebar.jsx:47
+#: frontend/src/metabase/reference/segments/SegmentRevisions.jsx:88
+msgid "Revision history for {0}"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/DataSelector.jsx:821
-msgid "Is a question missing?"
+#: frontend/src/metabase/reference/metrics/MetricSidebar.jsx:39
+msgid "X-ray this metric"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/DataSelector.jsx:825
-msgid "Learn more about nested queries"
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:217
+msgid "Why this Segment is interesting"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/DataSelector.jsx:857
-msgid "Fields"
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:227
+msgid "Things to be aware of about this Segment"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/DataSelector.jsx:935
-msgid "No segments were found."
+#: frontend/src/metabase/reference/segments/SegmentList.jsx:23
+msgid "Segments are interesting subsets of tables"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/DataSelector.jsx:958
-msgid "Find a segment"
+#: frontend/src/metabase/reference/segments/SegmentList.jsx:24
+msgid ""
+"Defining common segments for your team makes it even easier to ask questions"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/ExpandableString.jsx:44
-msgid "View less"
+#: frontend/src/metabase/reference/segments/SegmentList.jsx:25
+msgid "Segments will appear here once your admins have created some"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/ExpandableString.jsx:54
-msgid "View more"
+#: frontend/src/metabase/reference/segments/SegmentList.jsx:27
+msgid "Learn how to create segments"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/ExtendedOptions.jsx:111
-msgid "Pick a field to sort by"
+#: frontend/src/metabase/reference/segments/SegmentQuestions.jsx:35
+msgid "Questions about this segment will appear here as they're added"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/ExtendedOptions.jsx:124
-msgid "Sort"
+#: frontend/src/metabase/reference/segments/SegmentRevisions.jsx:29
+msgid "There are no revisions for this segment"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/ExtendedOptions.jsx:189
-msgid "Row limit"
+#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:33
+msgid "Fields in this segment"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/FieldName.jsx:76
-msgid "Unknown Field"
+#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:39
+msgid "Questions about this segment"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/FieldName.jsx:79
-msgid "field"
+#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:45
+msgid "X-ray this segment"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:150
-#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:158
-msgid "Add filters to narrow your answer"
+#: frontend/src/metabase/routes.jsx:182
+msgid "Login"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:235
-#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:288
-msgid "and"
+#: frontend/src/metabase/routes.jsx:217
+msgid "Dashboard"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:282
-msgid "Add a grouping"
+#: frontend/src/metabase/routes.jsx:227
+msgid "New Question"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:320
-#: frontend/src/metabase/visualizations/components/LineAreaBarChart.jsx:102
-#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:58
-#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:54
-msgid "Data"
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:125
+msgid "Select the type of Database you use"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:350
-msgid "Filtered by"
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:141
+msgid "Add your data"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:367
-msgid "View"
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:145
+msgid "I'll add my own data later"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:384
-msgid "Grouped By"
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:146
+msgid "Connecting to {0}"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:332
-msgid "This question is written in {0}."
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:165
+msgid ""
+"You’ll need some info about your database, like the username and password. "
+"If you don’t have that right now, Metabase also comes with a sample dataset "
+"you can get started with."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:340
-msgid "Hide Editor"
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:196
+msgid "I'll add my data later"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:341
-msgid "Hide Query"
+#: frontend/src/metabase/setup/components/DatabaseSchedulingStep.jsx:41
+msgid "Control automatic scans"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:346
-msgid "Open Editor"
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:53
+msgid "Usage data preferences"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:347
-msgid "Show Query"
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:56
+msgid "Thanks for helping us improve"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/QueryDefinitionTooltip.jsx:25
-msgid "This metric has been retired.  It's no longer available for use."
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:57
+msgid "We won't collect any usage events"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:25
-#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:32
-msgid "Download full results"
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:76
+msgid ""
+"In order to help us improve Metabase, we'd like to collect certain data "
+"about usage through Google Analytics."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:26
-msgid "Download this data"
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:85
+msgid "Here's a full list of everything we track and why."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:34
-msgid "Warning"
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:98
+msgid "Allow Metabase to anonymously collect usage events"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:35
-msgid ""
-"Your answer has a large number of rows so it could take a while to download."
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:105
+msgid "Metabase {0} collects anything about your data or question results."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:36
-msgid "The maximum download size is 1 million rows."
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:106
+msgid "never"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:250
-msgid "Edit question"
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:108
+msgid "All collection is completely anonymous."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:267
-msgid "SAVE CHANGES"
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:110
+msgid "Collection can be turned off at any point in your admin settings."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:281
-msgid "CANCEL"
+#: frontend/src/metabase/setup/components/Setup.jsx:45
+msgid "If you feel stuck"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:296
-msgid "Move question"
+#: frontend/src/metabase/setup/components/Setup.jsx:52
+msgid "our getting started guide"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:327
-#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:110
-#: frontend/src/metabase/query_builder/components/template_tags/TagEditorSidebar.jsx:83
-msgid "Variables"
+#: frontend/src/metabase/setup/components/Setup.jsx:53
+msgid "is just a click away."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:445
-msgid "Learn about your data"
+#: frontend/src/metabase/setup/components/Setup.jsx:95
+msgid "Welcome to Metabase"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:473
-msgid "Alerts are on"
+#: frontend/src/metabase/setup/components/Setup.jsx:96
+msgid ""
+"Looks like everything is working. Now let’s get to know you, connect to your "
+"data, and start finding you some answers!"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:535
-msgid "started from"
+#: frontend/src/metabase/setup/components/Setup.jsx:100
+msgid "Let's get started"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:48
-msgid "SQL"
+#: frontend/src/metabase/setup/components/Setup.jsx:145
+msgid "You're all set up!"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:48
-msgid "native query"
+#: frontend/src/metabase/setup/components/Setup.jsx:156
+msgid "Take me to Metabase"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:52
-msgid "Not Supported"
+#: frontend/src/metabase/setup/components/UserStep.jsx:155
+msgid "What should we call you?"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:58
-msgid "View the {0}"
+#: frontend/src/metabase/setup/components/UserStep.jsx:156
+msgid "Hi, {0}. nice to meet you!"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:59
-msgid "Switch to {0}"
+#: frontend/src/metabase/setup/components/UserStep.jsx:243
+msgid "Create a password"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:62
-msgid "Switch to Builder"
+#: frontend/src/metabase/setup/components/UserStep.jsx:259
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:116
+msgid "Shhh..."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:87
-msgid "{0} for this question"
+#: frontend/src/metabase/setup/components/UserStep.jsx:269
+msgid "Confirm password"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:111
-msgid "Convert this question to {0}"
+#: frontend/src/metabase/setup/components/UserStep.jsx:278
+msgid "Shhh... but one more time so we get it right"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:122
-msgid "This question will take approximately {0} to refresh"
+#: frontend/src/metabase/setup/components/UserStep.jsx:287
+msgid "Your company or team name"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:131
-msgid "Updated {0}"
+#: frontend/src/metabase/setup/components/UserStep.jsx:296
+msgid "Department of awesome"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:146
-msgid "Showing first {0} {1}"
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:26
+msgid "Metabot is admiring your integers…"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:149
-msgid "Showing {0} {1}"
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:27
+msgid "Metabot is performing billions of differential equations…"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:281
-msgid "Doing science"
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:28
+msgid "Metabot is doing science…"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:294
-msgid "If you give me some data I can show you something cool. Run a Query!"
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:29
+msgid "Metabot is checking out your metrics…"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:299
-msgid "How do I use this thing?"
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:30
+msgid "Metabot is looking for trends and outliers…"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/RunButton.jsx:28
-msgid "Get Answer"
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:31
+msgid "Metabot is consulting the quantum abacus…"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/SavedQuestionIntroModal.jsx:12
-msgid "It's okay to play around with saved questions"
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:32
+msgid "Metabot is feeling pretty good about all this…"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/SavedQuestionIntroModal.jsx:14
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:52
 msgid ""
-"You won't make any permanent changes to a saved question unless you click "
-"the edit icon in the top-right."
+"We’ll show you some interesting explorations of your data in\n"
+"just a few minutes."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/SearchBar.jsx:28
-msgid "Search for"
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:72
+msgid ""
+"This seems to be taking a while. In the meantime, you can check out one of "
+"these example explorations to see what Metabase can do for you."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/SelectionModule.jsx:158
-msgid "Advanced..."
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:85
+msgid ""
+"Once you connect your own data, I can show you some automatic explorations "
+"called x-rays. Here are some examples with sample data."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/SelectionModule.jsx:167
-msgid "Sorry. Something went wrong."
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:86
+msgid ""
+"I took a look at the data you just connected, and I have some explorations "
+"of interesting things I found. Hope you like them!"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/TimeGroupingPopover.jsx:40
-msgid "Group time by"
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:98
+msgid "I'm done exploring for now"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:45
-msgid "Your question took too long"
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:20
+msgid "Welcome to the Query Builder!"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:46
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:22
 msgid ""
-"We didn't get an answer back from your database in time, so we had to stop. "
-"You can try again in a minute, or if the problem persists, you can email an "
-"admin to let them know."
+"The Query Builder lets you assemble questions (or \"queries\") to ask about "
+"your data."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:54
-msgid "We're experiencing server issues"
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:26
+msgid "Tell me more"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:55
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:43
 msgid ""
-"Try refreshing the page after waiting a minute or two. If the problem "
-"persists we'd recommend you contact an admin."
+"Start by picking the table with the data that you have a question about."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:87
-msgid "There was a problem with your question"
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:45
+msgid "Go ahead and select the \"Orders\" table from the dropdown menu."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:88
-msgid ""
-"Most of the time this is caused by an invalid selection or bad input value. "
-"Double check your inputs and retry your query."
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:78
+msgid "Filter your data to get just what you want."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:93
-msgid "Show error details"
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:79
+msgid "Click the plus button and select the \"Created At\" field."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:99
-msgid "Here's the full error message"
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:93
+msgid "Here we can pick how many days we want to see data for, try 10"
+msgstr ""
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:116
+msgid ""
+"Here's where you can choose to add or average your data, count the number of "
+"rows in the table, or just view the raw data."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/VisualizationResult.jsx:61
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:118
 msgid ""
-"This may be the answer you’re looking for. If not, try removing or changing "
-"your filters to make them less specific."
+"Try it: click on <strong>Raw Data</strong> to change it to <strong>Count of "
+"rows</strong> so we can count how many orders there are in this table."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/VisualizationResult.jsx:67
-msgid "You can also {0} when there are any results."
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:142
+msgid ""
+"Add a grouping to break out your results by category, day, month, and more."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/VisualizationResult.jsx:78
-msgid "Back to last run"
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:144
+msgid ""
+"Let's do it: click on <strong>Add a grouping</strong>, and choose "
+"<strong>Created At: by Week</strong>."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/VisualizationSettings.jsx:53
-msgid "Visualization"
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:152
+msgid "Click on \"by day\" to change it to \"Week.\""
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/dataref/DetailPane.jsx:17
-#: frontend/src/metabase/query_builder/components/dataref/TablePane.jsx:146
-msgid "No description set."
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:173
+msgid "Run Your Query."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/dataref/DetailPane.jsx:21
-msgid "Use for current question"
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:175
+msgid ""
+"You're doing so well! Click <strong>Run query</strong> to get your results!"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/dataref/DetailPane.jsx:33
-#: frontend/src/metabase/reference/components/UsefulQuestions.jsx:16
-msgid "Potentially useful questions"
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:192
+msgid "You can view your results as a chart instead of a table."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/dataref/FieldPane.jsx:156
-msgid "Group by {0}"
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:194
+msgid ""
+"Everbody likes charts! Click the <strong>Visualization</strong> dropdown and "
+"select <strong>Line</strong>."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/dataref/FieldPane.jsx:165
-msgid "Sum of all values of {0}"
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:216
+msgid "Well done!"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/dataref/FieldPane.jsx:173
-#: frontend/src/metabase/reference/databases/FieldDetail.jsx:63
-#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:51
-msgid "All distinct values of {0}"
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:218
+msgid "That's all! If you still have questions, check out our"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/dataref/FieldPane.jsx:177
-#: frontend/src/metabase/reference/databases/FieldDetail.jsx:39
-#: frontend/src/metabase/reference/databases/FieldDetail.jsx:51
-#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:39
-msgid "Number of {0} grouped by {1}"
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:223
+msgid "User's Guide"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/dataref/MainPane.jsx:14
-msgid "Learn more about your data structure to ask more useful questions"
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:223
+msgid "Have fun exploring your data!"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/dataref/MetricPane.jsx:58
-#: frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx:84
-msgid "Could not find the table metadata prior to creating a new question"
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:226
+msgid "Thanks"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/dataref/MetricPane.jsx:80
-msgid "See {0}"
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:235
+msgid "Save Your Questions"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/dataref/MetricPane.jsx:94
-msgid "Metric Definition"
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:237
+msgid ""
+"By the way, you can save your questions so you can refer to them later. "
+"Saved Questions can also be put into dashboards or Pulses."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx:118
-msgid "Filter by {0}"
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:241
+msgid "Sounds good"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx:127
-#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:36
-msgid "Number of {0}"
+#: frontend/src/metabase/tutorial/Tutorial.jsx:248
+msgid "Whoops!"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx:134
-#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:46
-msgid "See all {0}"
+#: frontend/src/metabase/tutorial/Tutorial.jsx:249
+msgid ""
+"Sorry, it looks like something went wrong. Please try restarting the "
+"tutorial in a minute."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx:148
-msgid "Segment Definition"
+#: frontend/src/metabase/user/actions.js:34
+msgid "Password updated successfully!"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/dataref/TablePane.jsx:47
-msgid "An error occurred loading the table"
+#: frontend/src/metabase/user/actions.js:53
+msgid "Account updated successfully!"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/dataref/TablePane.jsx:71
-msgid "See the raw data for {0}"
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:107
+msgid "Current password"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/dataref/TablePane.jsx:199
-msgid "More"
+#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:137
+msgid "Sign in with Google Email address"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:192
-msgid "Invalid expression"
+#: frontend/src/metabase/user/components/UserSettings.jsx:65
+msgid "User Details"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:266
-msgid "unknown error"
+#: frontend/src/metabase/visualizations/components/ChartSettings.jsx:225
+msgid "Reset to defaults"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:46
-msgid "Field formula"
+#: frontend/src/metabase/visualizations/components/ChoroplethMap.jsx:123
+msgid "unknown map"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:57
-msgid ""
-"Think of this as being kind of like writing a formula in a spreadsheet "
-"program: you can use numbers, fields in this table, mathematical symbols "
-"like +, and some functions. So you could type something like Subtotal "
-"&minus; Cost."
+#: frontend/src/metabase/visualizations/components/LeafletGridHeatMap.jsx:26
+msgid "Grid map requires binned longitude/latitude."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:62
-#: frontend/src/metabase/questions/containers/QuestionIndex.jsx:40
-#: frontend/src/metabase/reference/components/GuideDetail.jsx:126
-msgid "Learn more"
+#: frontend/src/metabase/visualizations/components/LegendVertical.jsx:112
+msgid "more"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:66
-msgid "Give it a name"
+#: frontend/src/metabase/visualizations/components/LineAreaBarChart.jsx:101
+msgid "Which fields do you want to use for the X and Y axes?"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:72
-msgid "Something nice and descriptive"
+#: frontend/src/metabase/visualizations/components/LineAreaBarChart.jsx:103
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:59
+msgid "Choose fields"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/expressions/Expressions.jsx:60
-msgid "Add a custom field"
+#: frontend/src/metabase/visualizations/components/PinMap.jsx:204
+msgid "Save as default view"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/filters/FilterList.jsx:64
-msgid "Item"
+#: frontend/src/metabase/visualizations/components/PinMap.jsx:226
+msgid "Draw box to filter"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:17
-msgid "Include {0}"
+#: frontend/src/metabase/visualizations/components/PinMap.jsx:226
+msgid "Cancel filter"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:19
-msgid "Case sensitive"
+#: frontend/src/metabase/visualizations/components/PinMap.jsx:47
+msgid "Pin Map"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:23
-msgid "today"
+#: frontend/src/metabase/visualizations/components/TableInteractive.jsx:423
+msgid "Unset"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:24
-msgid "this week"
+#: frontend/src/metabase/visualizations/components/TableSimple.jsx:227
+msgid "Rows {0}-{1} of {2}"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:25
-msgid "this month"
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:35
+msgid "There was a problem displaying this chart."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:26
-msgid "this year"
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:36
+msgid "Sorry, you don't have permission to see this card."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:27
-msgid "this minute"
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:184
+msgid "Data truncated to {0} rows."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:28
-msgid "this hour"
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:349
+msgid "Could not find visualization"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx:278
-msgid "not implemented {0}"
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:356
+msgid "Could not display this chart with this data."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx:279
-msgid "true"
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:454
+msgid "No results!"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx:279
-msgid "false"
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:475
+msgid "Still Waiting..."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx:383
-msgid "Add filter"
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:478
+msgid "This usually takes an average of {0}."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/filters/FilterWidget.jsx:140
-msgid "Matches"
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:484
+msgid "(This is a bit long for a dashboard)"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:224
-msgid "Previous"
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:488
+msgid "This is usually pretty fast but seems to be taking awhile right now."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:255
-msgid "Current"
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingFieldPicker.jsx:14
+msgid "Select a field"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:282
-msgid "On"
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingFieldsPicker.jsx:42
+msgid "error"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/filters/pickers/NumberPicker.jsx:47
-msgid "Enter desired number"
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingOrderedColumns.jsx:107
+msgid "Click and drag to change their order"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/filters/pickers/SelectPicker.jsx:83
-#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:98
-msgid "Empty"
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingOrderedColumns.jsx:119
+msgid "Add fields from the list below"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/filters/pickers/SelectPicker.jsx:116
-msgid "Find a value"
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:24
+msgid "less than"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/filters/pickers/SpecificDatePicker.jsx:113
-msgid "Hide calendar"
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:25
+msgid "greater than"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/filters/pickers/SpecificDatePicker.jsx:113
-msgid "Show calendar"
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:26
+msgid "less than or equal to"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/filters/pickers/TextPicker.jsx:97
-msgid "You can enter multiple values separated by commas"
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:27
+msgid "greater than or equal to"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/filters/pickers/TextPicker.jsx:38
-msgid "Enter desired text"
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:28
+msgid "equal to"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:83
-msgid "Try it"
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:29
+msgid "not equal to"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:105
-msgid "What's this for?"
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:169
+msgid "Conditional formatting"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:107
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:171
 msgid ""
-"Variables in native queries let you dynamically replace values in your "
-"queries using filter widgets or through the URL."
+"You can add rules to make the cells in this table change color if\n"
+"they meet certain conditions."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:112
-msgid ""
-"{0} creates a variable in this SQL template called \"variable_name\". "
-"Variables can be given types in the side panel, which changes their "
-"behavior. All variable types other than \"Field Filter\" will automatically "
-"cause a filter widget to be placed on this question; with Field Filters, "
-"this is optional. When this filter widget is filled in, that value replaces "
-"the variable in the SQL template."
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:181
+msgid "Add a rule"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:121
-msgid "Field Filters"
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:186
+msgid "Rules will be applied in this order"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:123
-msgid ""
-"Giving a variable the \"Field Filter\" type allows you to link SQL cards to "
-"dashboard filter widgets or use more types of filter widgets on your SQL "
-"question. A Field Filter variable inserts SQL similar to that generated by "
-"the GUI query builder when adding filters on existing columns."
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:187
+msgid "Click and drag to reorder."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:126
-msgid ""
-"When adding a Field Filter variable, you'll need to map it to a specific "
-"field. You can then choose to display a filter widget on your question, but "
-"even if you don't, you can now map your Field Filter variable to a dashboard "
-"filter when adding this question to a dashboard. Field Filters should be "
-"used inside of a \"WHERE\" clause."
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:220
+msgid "No columns selected"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:130
-msgid "Optional Clauses"
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:277
+msgid "Cells in this column will be tinted based on their values."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:132
-msgid ""
-"brackets around a {0} create an optional clause in the template. If "
-"\"variable\" is set, then the entire clause is placed into the template. If "
-"not, then the entire clause is ignored."
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:279
+msgid "When a cell in these columns is {0} it will be tinted this color."
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:142
-msgid ""
-"To use multiple optional clauses you can include at least one non-optional "
-"WHERE clause followed by optional clauses starting with \"AND\"."
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:290
+msgid "Which columns should be affected?"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:154
-msgid "Read the full documentation"
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:300
+msgid "Formatting style"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:124
-msgid "Filter label"
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:304
+msgid "Single color"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:136
-msgid "Variable type"
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:305
+msgid "Color range"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:145
-msgid "Text"
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:312
+msgid "When a cell in this column is…"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:147
-msgid "Date"
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:325
+msgid "…turn its background this color:"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:148
-msgid "Field Filter"
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:331
+msgid "Highlight the whole row"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:154
-msgid "Field to map to"
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:339
+msgid "Colors"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:176
-msgid "Filter widget type"
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:344
+msgid "Start the range at"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:199
-msgid "Required?"
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:349
+msgid "Smallest value in this column"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:210
-msgid "Default filter widget value"
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:351
+msgid "Smallest value in each column"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/containers/ArchiveQuestionModal.jsx:46
-msgid "Archive this question?"
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:353
+msgid "Smallest value in all of these columns"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/containers/ArchiveQuestionModal.jsx:56
-msgid "This question will be removed from any dashboards or pulses using it."
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:357
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:379
+msgid "Custom value"
 msgstr ""
 
-#: frontend/src/metabase/query_builder/containers/QueryBuilder.jsx:134
-msgid "Question"
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:366
+msgid "End the range at"
 msgstr ""
 
-#: frontend/src/metabase/questions/components/ActionHeader.jsx:25
-msgid "Select all {0}"
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:371
+msgid "Largest value in this column"
 msgstr ""
 
-#: frontend/src/metabase/questions/components/ActionHeader.jsx:37
-msgid "{0} selected"
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:373
+msgid "Largest value in each column"
 msgstr ""
 
-#: frontend/src/metabase/questions/components/ActionHeader.jsx:44
-msgid "Labels"
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:375
+msgid "Largest value in all of these columns"
 msgstr ""
 
-#: frontend/src/metabase/questions/components/CollectionButtons.jsx:58
-msgid "Set collection permissions"
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:407
+msgid "Add rule"
 msgstr ""
 
-#: frontend/src/metabase/questions/components/CollectionButtons.jsx:104
-#: frontend/src/metabase/questions/containers/CollectionEditorForm.jsx:37
-msgid "New collection"
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:407
+msgid "Update rule"
 msgstr ""
 
-#: frontend/src/metabase/questions/components/ExpandingSearchField.jsx:93
-msgid "Search for a question"
+#: frontend/src/metabase/visualizations/index.js:30
+msgid "Visualization is null"
 msgstr ""
 
-#: frontend/src/metabase/questions/components/Item.jsx:92
-msgid "Move to a collection"
+#: frontend/src/metabase/visualizations/index.js:35
+msgid "Visualization must define an 'identifier' static variable: "
 msgstr ""
 
-#: frontend/src/metabase/questions/components/Item.jsx:209
-msgid "Created"
+#: frontend/src/metabase/visualizations/index.js:41
+msgid "Visualization with that identifier is already registered: "
 msgstr ""
 
-#: frontend/src/metabase/questions/components/Item.jsx:209
-msgid " by {0}"
+#: frontend/src/metabase/visualizations/index.js:69
+msgid "No visualization for {0}"
 msgstr ""
 
-#: frontend/src/metabase/questions/components/LabelIconPicker.jsx:47
-msgid "Colors"
+#: frontend/src/metabase/visualizations/lib/LineAreaBarRenderer.js:71
+msgid ""
+"\"{0}\" is an unaggregated field: if it has more than one value at a point "
+"on the x-axis, the values will be summed."
 msgstr ""
 
-#: frontend/src/metabase/questions/components/LabelPicker.jsx:17
-msgid "Apply labels to {0} questions"
+#: frontend/src/metabase/visualizations/lib/LineAreaBarRenderer.js:87
+msgid "This chart type requires at least 2 columns."
 msgstr ""
 
-#: frontend/src/metabase/questions/components/LabelPicker.jsx:17
-msgid "Label as"
+#: frontend/src/metabase/visualizations/lib/LineAreaBarRenderer.js:92
+msgid "This chart type doesn't support more than {0} series of data."
 msgstr ""
 
-#: frontend/src/metabase/questions/components/LabelPicker.jsx:51
-#: frontend/src/metabase/questions/containers/EditLabels.jsx:73
-msgid "Add and edit labels"
+#: frontend/src/metabase/visualizations/lib/LineAreaBarRenderer.js:509
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:42
+msgid "Goal"
 msgstr ""
 
-#: frontend/src/metabase/questions/components/LabelPicker.jsx:53
-#: frontend/src/metabase/questions/containers/EditLabels.jsx:77
-msgid "In an upcoming release, Labels will be removed in favor of Collections."
+#: frontend/src/metabase/visualizations/lib/errors.js:9
+msgid ""
+"Doh! The data from your query doesn't fit the chosen display choice. This "
+"visualization requires at least {0} {1} of data."
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/AddToDashboard.jsx:88
-msgid "Pick a question to add"
+#: frontend/src/metabase/visualizations/lib/errors.js:9
+msgid "column"
+msgid_plural "columns"
+msgstr[0] ""
+msgstr[1] ""
+
+#: frontend/src/metabase/visualizations/lib/errors.js:21
+msgid ""
+"No dice. We have {0} data {1} to show and that's not enough for this "
+"visualization."
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/AddToDashboard.jsx:113
-msgid "Last modified"
-msgstr ""
+#: frontend/src/metabase/visualizations/lib/errors.js:21
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
 
-#: frontend/src/metabase/questions/containers/AddToDashboard.jsx:114
-msgid "Alphabetical order"
+#: frontend/src/metabase/visualizations/lib/errors.js:33
+msgid ""
+"Bummer. We can't actually do a pin map for this data because we require both "
+"a latitude and longitude column."
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/ArchiveCollectionWidget.jsx:44
-msgid "Archive collection"
+#: frontend/src/metabase/visualizations/lib/errors.js:42
+msgid "Please configure this chart in the chart settings"
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/ArchiveCollectionWidget.jsx:48
-msgid "Archive this collection?"
+#: frontend/src/metabase/visualizations/lib/errors.js:44
+msgid "Edit Settings"
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/ArchiveCollectionWidget.jsx:54
-msgid "The saved questions in this collection will also be archived."
+#: frontend/src/metabase/visualizations/lib/fill_data.js:38
+msgid "xValues missing!"
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/CollectionEditorForm.jsx:21
-msgid "Name must be 100 characters or less"
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:56
+#: frontend/src/metabase/visualizations/visualizations/RowChart.jsx:31
+msgid "X-axis"
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/CollectionEditorForm.jsx:24
-msgid "Color is required"
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:82
+msgid "Add a series breakout..."
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/CollectionEditorForm.jsx:79
-msgid "My new fantastic collection"
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:93
+#: frontend/src/metabase/visualizations/visualizations/RowChart.jsx:35
+msgid "Y-axis"
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/CollectionEditorForm.jsx:91
-#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:48
-msgid "Color"
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:118
+msgid "Add another series..."
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/CollectionPage.jsx:65
-msgid "Edit collection"
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:132
+msgid "Bubble size"
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/CollectionPage.jsx:75
-msgid "Set permissions"
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:156
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:169
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:177
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:200
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:206
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:217
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:233
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:81
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:71
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:76
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:82
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:41
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:47
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:72
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:85
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:98
+msgid "Display"
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/CollectionPage.jsx:86
-msgid "No questions have been added to this collection yet."
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:161
+#: frontend/src/metabase/visualizations/visualizations/LineChart.jsx:17
+msgid "Line"
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/EditLabels.jsx:75
-msgid "Heads up!"
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:162
+msgid "Curve"
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/EditLabels.jsx:83
-msgid "Create Label"
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:163
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:67
+msgid "Step"
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/EditLabels.jsx:105
-msgid "Update Label"
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:170
+msgid "Show point markers on lines"
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/EditLabels.jsx:121
-msgid "Delete label \"{0}\""
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:178
+msgid "Stacking"
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/EditLabels.jsx:140
-msgid "Create labels to group and manage questions."
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:182
+msgid "Don't stack"
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/EntityList.jsx:70
-#: frontend/src/metabase/questions/selectors.js:154
-msgid "All questions"
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:183
+msgid "Stack"
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/EntityList.jsx:72
-msgid "No questions have been saved yet."
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:184
+msgid "Stack - 100%"
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/EntityList.jsx:78
-msgid "You haven't favorited any questions yet."
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:201
+msgid "Show goal"
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/EntityList.jsx:82
-#: frontend/src/metabase/questions/selectors.js:156
-msgid "Recently viewed"
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:207
+msgid "Goal value"
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/EntityList.jsx:84
-msgid "You haven't viewed any questions recently."
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:218
+msgid "Replace missing values with"
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/EntityList.jsx:90
-msgid "You haven't saved any questions yet."
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:223
+msgid "Zero"
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/EntityList.jsx:94
-#: frontend/src/metabase/questions/selectors.js:158
-msgid "Most popular"
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:224
+msgid "Nothing"
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/EntityList.jsx:96
-msgid "The most-viewed questions across your company will show up here."
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:225
+msgid "Linear Interpolated"
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/EntityList.jsx:102
-msgid "If you no longer need a question, you can archive it."
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:283
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:316
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:329
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:344
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:356
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:362
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:370
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:400
+msgid "Axes"
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/EntityList.jsx:108
-msgid "There aren't any questions matching that criteria."
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:284
+msgid "X-axis scale"
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/LabelEditorForm.jsx:21
-msgid "Icon is required"
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:301
+msgid "Timeseries"
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/MoveToCollection.jsx:52
-msgid "Which collection should this be in?"
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:304
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:322
+msgid "Linear"
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/QuestionIndex.jsx:33
-msgid "Create collections for your saved questions"
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:306
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:323
+msgid "Power"
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/QuestionIndex.jsx:35
-msgid ""
-"Collections help you organize your questions and allow you to decide who "
-"gets to see what."
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:307
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:324
+msgid "Log"
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/QuestionIndex.jsx:45
-msgid "Create a collection"
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:309
+msgid "Histogram"
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/QuestionIndex.jsx:55
-msgid "Explore your data, create charts or maps, and save what you find."
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:311
+msgid "Ordinal"
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/QuestionIndex.jsx:58
-#: frontend/src/metabase/reference/databases/TableQuestions.jsx:36
-#: frontend/src/metabase/reference/metrics/MetricQuestions.jsx:37
-#: frontend/src/metabase/reference/segments/SegmentQuestions.jsx:37
-msgid "Ask a question"
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:317
+msgid "Y-axis scale"
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/QuestionIndex.jsx:89
-msgid "Set permissions for collections"
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:330
+msgid "Show x-axis line and marks"
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/SearchResults.jsx:44
-msgid "Search results"
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:336
+msgid "Compact"
 msgstr ""
 
-#: frontend/src/metabase/questions/containers/SearchResults.jsx:52
-msgid "No matching questions found"
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:337
+msgid "Rotate 45°"
 msgstr ""
 
-#: frontend/src/metabase/questions/questions.js:127
-msgid "{0} question was {1}"
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:338
+msgid "Rotate 90°"
 msgstr ""
 
-#: frontend/src/metabase/questions/questions.js:128
-msgid "{0} questions were {1}"
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:345
+msgid "Show y-axis line and marks"
 msgstr ""
 
-#: frontend/src/metabase/questions/questions.js:134
-msgid "to the"
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:357
+msgid "Auto y-axis range"
 msgstr ""
 
-#: frontend/src/metabase/questions/questions.js:138
-msgid "collection"
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:401
+msgid "Use a split y-axis when necessary"
 msgstr ""
 
-#: frontend/src/metabase/reference/components/EditHeader.jsx:19
-msgid "You are editing this page"
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:407
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:413
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:422
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:428
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:437
+msgid "Labels"
 msgstr ""
 
-#: frontend/src/metabase/reference/components/EditableReferenceHeader.jsx:96
-#: frontend/src/metabase/reference/components/ReferenceHeader.jsx:59
-msgid "See this {0}"
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:408
+msgid "Show label on x-axis"
 msgstr ""
 
-#: frontend/src/metabase/reference/components/EditableReferenceHeader.jsx:115
-msgid "A subset of"
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:414
+msgid "X-axis label"
 msgstr ""
 
-#: frontend/src/metabase/reference/components/Field.jsx:47
-#: frontend/src/metabase/reference/components/Field.jsx:86
-#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:32
-#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:68
-msgid "Select a field type"
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:423
+msgid "Show label on y-axis"
 msgstr ""
 
-#: frontend/src/metabase/reference/components/Field.jsx:56
-#: frontend/src/metabase/reference/components/Field.jsx:71
-#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:41
-#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:57
-msgid "No field type"
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:429
+msgid "Y-axis label"
 msgstr ""
 
-#: frontend/src/metabase/reference/components/FieldToGroupBy.jsx:22
-msgid "by"
+#: frontend/src/metabase/visualizations/lib/utils.js:116
+msgid "Standard Deviation"
 msgstr ""
 
-#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:25
-#: frontend/src/metabase/reference/databases/FieldList.jsx:152
-#: frontend/src/metabase/reference/segments/SegmentFieldList.jsx:153
-msgid "Field type"
+#: frontend/src/metabase/visualizations/visualizations/AreaChart.jsx:18
+msgid "Area"
 msgstr ""
 
-#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:72
-msgid "Select a Foreign Key"
+#: frontend/src/metabase/visualizations/visualizations/AreaChart.jsx:21
+msgid "area chart"
 msgstr ""
 
-#: frontend/src/metabase/reference/components/Formula.jsx:53
-msgid "View the {0} formula"
+#: frontend/src/metabase/visualizations/visualizations/BarChart.jsx:16
+msgid "Bar"
 msgstr ""
 
-#: frontend/src/metabase/reference/components/GuideDetail.jsx:80
-msgid "Why this {0} is important"
+#: frontend/src/metabase/visualizations/visualizations/BarChart.jsx:19
+msgid "bar chart"
 msgstr ""
 
-#: frontend/src/metabase/reference/components/GuideDetail.jsx:81
-msgid "Why this {0} is interesting"
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:57
+msgid "Which fields do you want to use?"
 msgstr ""
 
-#: frontend/src/metabase/reference/components/GuideDetail.jsx:87
-msgid "Nothing important yet"
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:31
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:85
+msgid "Funnel"
 msgstr ""
 
-#: frontend/src/metabase/reference/components/GuideDetail.jsx:88
-#: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:168
-#: frontend/src/metabase/reference/databases/FieldDetail.jsx:233
-#: frontend/src/metabase/reference/databases/TableDetail.jsx:211
-#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:215
-#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:219
-#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:229
-msgid "Nothing interesting yet"
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:74
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:67
+msgid "Measure"
 msgstr ""
 
-#: frontend/src/metabase/reference/components/GuideDetail.jsx:93
-msgid "Things to be aware of about this {0}"
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:80
+msgid "Funnel type"
 msgstr ""
 
-#: frontend/src/metabase/reference/components/GuideDetail.jsx:97
-#: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:178
-#: frontend/src/metabase/reference/databases/FieldDetail.jsx:243
-#: frontend/src/metabase/reference/databases/TableDetail.jsx:221
-#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:225
-#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:229
-#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:239
-msgid "Nothing to be aware of yet"
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:86
+msgid "Bar chart"
 msgstr ""
 
-#: frontend/src/metabase/reference/components/GuideDetail.jsx:103
-msgid "Explore this metric"
+#: frontend/src/metabase/visualizations/visualizations/LineChart.jsx:20
+msgid "line chart"
 msgstr ""
 
-#: frontend/src/metabase/reference/components/GuideDetail.jsx:105
-msgid "View this metric"
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:211
+msgid "Please select longitude and latitude columns in the chart settings."
 msgstr ""
 
-#: frontend/src/metabase/reference/components/GuideDetail.jsx:112
-msgid "By {0}"
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:217
+msgid "Please select a region map."
 msgstr ""
 
-#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:146
-msgid "Remove item"
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:221
+msgid "Please select region and metric columns in the chart settings."
 msgstr ""
 
-#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:155
-msgid "Why is this dashboard the most important?"
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:31
+msgid "Map"
 msgstr ""
 
-#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:156
-msgid "What is useful or interesting about this {0}?"
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:45
+msgid "Map type"
 msgstr ""
 
-#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:160
-#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:174
-msgid "Write something helpful here"
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:49
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:146
+msgid "Region map"
 msgstr ""
 
-#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:169
-msgid "Is there anything users of this dashboard should be aware of?"
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:50
+msgid "Pin map"
 msgstr ""
 
-#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:170
-msgid "Anything users should be aware of about this {0}?"
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:96
+msgid "Pin type"
 msgstr ""
 
-#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:182
-#: frontend/src/metabase/reference/components/MetricImportantFieldsDetail.jsx:26
-msgid "Which 2-3 fields do you usually group this metric by?"
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:101
+msgid "Tiles"
 msgstr ""
 
-#: frontend/src/metabase/reference/components/GuideHeader.jsx:14
-msgid "Start here."
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:102
+msgid "Markers"
 msgstr ""
 
-#: frontend/src/metabase/reference/components/GuideHeader.jsx:24
-msgid ""
-"This is the perfect place to start if you’re new to your company’s data, or "
-"if you just want to check in on what’s going on."
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:118
+msgid "Latitude field"
 msgstr ""
 
-#: frontend/src/metabase/reference/components/MetricImportantFieldsDetail.jsx:65
-msgid "Most useful fields to group this metric by"
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:128
+msgid "Longitude field"
 msgstr ""
 
-#: frontend/src/metabase/reference/components/RevisionMessageModal.jsx:32
-msgid "Reason for changes"
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:138
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:165
+msgid "Metric field"
 msgstr ""
 
-#: frontend/src/metabase/reference/components/RevisionMessageModal.jsx:36
-msgid ""
-"Leave a note to explain what changes you made and why they were required"
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:170
+msgid "Region field"
 msgstr ""
 
-#: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:166
-msgid "Why this database is interesting"
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:179
+msgid "Radius"
 msgstr ""
 
-#: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:176
-msgid "Things to be aware of about this database"
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:185
+msgid "Blur"
 msgstr ""
 
-#: frontend/src/metabase/reference/databases/DatabaseList.jsx:46
-#: frontend/src/metabase/reference/guide/BaseSidebar.jsx:45
-msgid "Databases and tables"
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:191
+msgid "Min Opacity"
 msgstr ""
 
-#: frontend/src/metabase/reference/databases/DatabaseSidebar.jsx:27
-#: frontend/src/metabase/reference/databases/FieldSidebar.jsx:45
-#: frontend/src/metabase/reference/databases/TableDetail.jsx:170
-#: frontend/src/metabase/reference/databases/TableSidebar.jsx:31
-#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:184
-#: frontend/src/metabase/reference/metrics/MetricSidebar.jsx:27
-#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:188
-#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:187
-#: frontend/src/metabase/reference/segments/SegmentFieldSidebar.jsx:31
-#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:27
-msgid "Details"
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:197
+msgid "Max Zoom"
 msgstr ""
 
-#: frontend/src/metabase/reference/databases/DatabaseSidebar.jsx:33
-#: frontend/src/metabase/reference/databases/TableList.jsx:111
-msgid "Tables in {0}"
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:175
+msgid "No relationships found."
 msgstr ""
 
-#: frontend/src/metabase/reference/databases/FieldDetail.jsx:222
-#: frontend/src/metabase/reference/databases/TableDetail.jsx:200
-#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:218
-msgid "Actual name in database"
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:213
+msgid "via {0}"
 msgstr ""
 
-#: frontend/src/metabase/reference/databases/FieldDetail.jsx:231
-#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:227
-msgid "Why this field is interesting"
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:290
+msgid "This {0} is connected to:"
 msgstr ""
 
-#: frontend/src/metabase/reference/databases/FieldDetail.jsx:241
-#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:237
-msgid "Things to be aware of about this field"
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:47
+msgid "Object Detail"
 msgstr ""
 
-#: frontend/src/metabase/reference/databases/FieldDetail.jsx:253
-#: frontend/src/metabase/reference/databases/FieldList.jsx:155
-#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:249
-#: frontend/src/metabase/reference/segments/SegmentFieldList.jsx:156
-msgid "Data type"
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:50
+msgid "object"
 msgstr ""
 
-#: frontend/src/metabase/reference/databases/FieldList.jsx:39
-#: frontend/src/metabase/reference/segments/SegmentFieldList.jsx:39
-msgid "Fields in this table will appear here as they're added"
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:254
+msgid "Total"
 msgstr ""
 
-#: frontend/src/metabase/reference/databases/FieldList.jsx:134
-#: frontend/src/metabase/reference/segments/SegmentFieldList.jsx:135
-msgid "Fields in {0}"
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:53
+msgid "Which columns do you want to use?"
 msgstr ""
 
-#: frontend/src/metabase/reference/databases/FieldList.jsx:149
-#: frontend/src/metabase/reference/segments/SegmentFieldList.jsx:150
-msgid "Field name"
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:40
+msgid "Pie"
 msgstr ""
 
-#: frontend/src/metabase/reference/databases/FieldSidebar.jsx:52
-msgid "X-ray this Field"
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:62
+msgid "Dimension"
 msgstr ""
 
-#: frontend/src/metabase/reference/databases/NoDatabasesEmptyState.jsx:8
-msgid "Metabase is no fun without any data"
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:72
+msgid "Show legend"
 msgstr ""
 
-#: frontend/src/metabase/reference/databases/NoDatabasesEmptyState.jsx:9
-msgid "Your databases will appear here once you connect one"
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:77
+msgid "Show percentages in legend"
 msgstr ""
 
-#: frontend/src/metabase/reference/databases/NoDatabasesEmptyState.jsx:10
-msgid "Databases will appear here once your admins have added some"
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:83
+msgid "Minimum slice percentage"
 msgstr ""
 
-#: frontend/src/metabase/reference/databases/NoDatabasesEmptyState.jsx:12
-msgid "Connect a database"
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:136
+msgid "Goal met"
 msgstr ""
 
-#: frontend/src/metabase/reference/databases/TableDetail.jsx:38
-msgid "Count of {0}"
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:138
+msgid "Goal exceeded"
 msgstr ""
 
-#: frontend/src/metabase/reference/databases/TableDetail.jsx:47
-msgid "See raw data for {0}"
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:208
+msgid "Goal {0}"
 msgstr ""
 
-#: frontend/src/metabase/reference/databases/TableDetail.jsx:209
-msgid "Why this table is interesting"
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:35
+msgid "Progress visualization requires a number."
 msgstr ""
 
-#: frontend/src/metabase/reference/databases/TableDetail.jsx:219
-msgid "Things to be aware of about this table"
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:23
+msgid "Progress"
 msgstr ""
 
-#: frontend/src/metabase/reference/databases/TableList.jsx:30
-msgid "Tables in this database will appear here as they're added"
+#: frontend/src/metabase/visualizations/visualizations/RowChart.jsx:13
+msgid "Row Chart"
 msgstr ""
 
-#: frontend/src/metabase/reference/databases/TableQuestions.jsx:34
-msgid "Questions about this table will appear here as they're added"
+#: frontend/src/metabase/visualizations/visualizations/RowChart.jsx:16
+msgid "row chart"
 msgstr ""
 
-#: frontend/src/metabase/reference/databases/TableQuestions.jsx:71
-#: frontend/src/metabase/reference/metrics/MetricQuestions.jsx:75
-#: frontend/src/metabase/reference/metrics/MetricSidebar.jsx:33
-#: frontend/src/metabase/reference/segments/SegmentQuestions.jsx:74
-msgid "Questions about {0}"
+#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:75
+msgid "Separator style"
 msgstr ""
 
-#: frontend/src/metabase/reference/databases/TableQuestions.jsx:95
-#: frontend/src/metabase/reference/metrics/MetricQuestions.jsx:99
-#: frontend/src/metabase/reference/segments/SegmentQuestions.jsx:98
-msgid "Created {0} by {1}"
+#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:88
+msgid "Number of decimal places"
 msgstr ""
 
-#: frontend/src/metabase/reference/databases/TableSidebar.jsx:37
-msgid "Fields in this table"
+#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:92
+msgid "Add a prefix"
 msgstr ""
 
-#: frontend/src/metabase/reference/databases/TableSidebar.jsx:45
-msgid "Questions about this table"
+#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:96
+msgid "Add a suffix"
 msgstr ""
 
-#: frontend/src/metabase/reference/databases/TableSidebar.jsx:52
-msgid "X-ray this table"
+#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:100
+msgid "Multiply by a number"
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/BaseSidebar.jsx:27
-msgid "Start here"
+#: frontend/src/metabase/visualizations/visualizations/ScatterPlot.jsx:16
+msgid "Scatter"
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:160
-msgid "Help your team get started with your data."
+#: frontend/src/metabase/visualizations/visualizations/ScatterPlot.jsx:19
+msgid "scatter plot"
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:162
-msgid ""
-"Show your team what’s most important by choosing your top dashboard, "
-"metrics, and segments."
+#: frontend/src/metabase/visualizations/visualizations/Table.jsx:61
+msgid "Pivot the table"
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:168
-msgid "Get started"
+#: frontend/src/metabase/visualizations/visualizations/Table.jsx:73
+msgid "Visible fields"
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:176
-msgid "Our most important dashboard"
+#: frontend/src/metabase/visualizations/visualizations/Table.jsx:93
+msgid "Formatting"
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:191
-msgid "Numbers that we pay attention to"
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:167
+msgid "Write here, and use Markdown if you''d like"
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:216
-msgid ""
-"Metrics are important numbers your company cares about. They often represent "
-"a core indicator of how the business is performing."
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:73
+msgid "Vertical Alignment"
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:224
-msgid "See all metrics"
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:77
+msgid "Top"
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:238
-msgid "Segments and tables"
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:78
+msgid "Middle"
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:239
-msgid "Tables"
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:79
+msgid "Bottom"
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:265
-msgid ""
-"Segments and tables are the building blocks of your company's data. Tables "
-"are collections of the raw information while segments are specific slices "
-"with specific meanings, like {0}"
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:86
+msgid "Horizontal Alignment"
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:270
-msgid "Tables are the building blocks of your company's data."
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:90
+msgid "Left"
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:280
-msgid "See all segments"
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:91
+msgid "Center"
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:296
-msgid "See all tables"
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:92
+msgid "Right"
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:304
-msgid "Other things to know about our data"
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:99
+msgid "Show background"
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:305
-msgid "Find out more"
+#: frontend/src/metabase-lib/lib/Dimension.js:497
+msgid "{0} bin"
+msgid_plural "{0} bins"
+msgstr[0] ""
+msgstr[1] ""
+
+#: frontend/src/metabase-lib/lib/Dimension.js:503
+msgid "Auto binned"
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:310
+#: src/metabase/api/alert.clj
 msgid ""
-"A good way to get to know your data is by spending a bit of time exploring "
-"the different tables and other info available to you. It may take a while, "
-"but you'll start to recognize names and meanings over time."
+"DELETE /api/alert/:id is deprecated. Instead, change its `archived` value "
+"via PUT /api/alert/:id."
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:316
-msgid "Explore our data"
+#: src/metabase/api/automagic_dashboards.clj
+msgid "invalid show value"
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:324
-msgid "Have questions?"
+#: src/metabase/api/automagic_dashboards.clj
+msgid "invalid value for prefix"
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:329
-msgid "Contact {0}"
+#: src/metabase/api/automagic_dashboards.clj
+msgid "invalid value for rule name"
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:250
-msgid "Help new Metabase users find their way around."
+#: src/metabase/api/automagic_dashboards.clj
+msgid "value couldn''t be parsed as base64 encoded JSON"
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:253
-msgid ""
-"The Getting Started guide highlights the dashboard, metrics, segments, and "
-"tables that matter most, and informs your users of important things they "
-"should know before digging into the data."
+#: src/metabase/api/automagic_dashboards.clj
+msgid "Invalid entity type"
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:260
-msgid "Is there an important dashboard for your team?"
+#: src/metabase/api/automagic_dashboards.clj
+msgid ""
+"Invalid comparison entity type. Can only be one of \"table\", \"segment\", "
+"or \"adhoc\""
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:262
-msgid "Create a dashboard now"
+#: src/metabase/api/card.clj
+msgid "Error running query to determine Card result metadata:"
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:268
-msgid "What is your most important dashboard?"
+#: src/metabase/api/card.clj
+msgid ""
+"DELETE /api/card/:id is deprecated. Instead, change its `archived` value via "
+"PUT /api/card/:id."
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:287
-msgid "Do you have any commonly referenced metrics?"
+#: src/metabase/api/common.clj src/metabase/api/common/internal.clj
+msgid "Invalid field: {0}"
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:289
-msgid "Learn how to define a metric"
+#: src/metabase/api/common.clj
+msgid "Invalid value ''{0}'' for ''{1}'': {2}"
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:302
-msgid "What are your 3-5 most commonly referenced metrics?"
+#: src/metabase/api/common.clj
+msgid "must be one of: {0}"
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:346
-msgid "Add another metric"
+#: src/metabase/api/common.clj
+msgid "Invalid Request."
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:359
-msgid "Do you have any commonly referenced segments or tables?"
+#: src/metabase/api/common.clj
+msgid "Not found."
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:361
-msgid "Learn how to create a segment"
+#: src/metabase/api/common.clj
+msgid "You don''t have permissions to do that."
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:374
-msgid ""
-"What are 3-5 commonly referenced segments or tables that would be useful for "
-"this audience?"
+#: src/metabase/api/common.clj
+msgid "Internal server error."
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:420
-msgid "Add another segment or table"
+#: src/metabase/api/common.clj
+msgid "Warning: endpoint {0}/{1} does not have a docstring."
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:429
-msgid ""
-"Is there anything your users should understand or know before they start "
-"accessing the data?"
+#: src/metabase/api/common.clj
+msgid "starting streaming request"
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:435
-msgid "What should a user of this data know before they start accessing it?"
+#: src/metabase/api/common.clj
+msgid "connection closed, canceling request"
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:439
-msgid ""
-"E.g., expectations around data privacy and use, common pitfalls or "
-"misunderstandings, information about data warehouse performance, legal "
-"notices, etc."
+#. a newline padding character as it's harmless and will allow us to check if the client is connected. If
+#. sending this character fails because the connection is closed, the chan will then close.  Newlines are
+#. no-ops when reading JSON which this depends upon.
+#: src/metabase/api/common.clj
+msgid "Response not ready, writing one byte & sleeping..."
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:450
-msgid ""
-"Is there someone your users could contact for help if they're confused about "
-"this guide?"
+#: src/metabase/api/common.clj
+msgid "Public sharing is not enabled."
 msgstr ""
 
-#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:459
-msgid "Who should users contact for help if they're confused about this data?"
+#: src/metabase/api/common.clj
+msgid "Embedding is not enabled."
 msgstr ""
 
-#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:75
-#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:95
-msgid "Please enter a revision message"
+#: src/metabase/api/common.clj
+msgid "The object has been archived."
 msgstr ""
 
-#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:213
-msgid "Why this Metric is interesting"
+#: src/metabase/api/common/internal.clj
+msgid "Attempted to return a boolean as an API response. This is not allowed!"
 msgstr ""
 
-#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:223
-msgid "Things to be aware of about this Metric"
+#: src/metabase/api/dataset.clj
+msgid "Source query for this query is Card {0}"
 msgstr ""
 
-#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:233
-msgid "How this Metric is calculated"
+#: src/metabase/api/dataset.clj
+msgid "Invalid export format: {0}"
 msgstr ""
 
-#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:235
-msgid "Nothing on how it's calculated yet"
+#: src/metabase/api/geojson.clj
+msgid "Invalid JSON URL or resource: {0}"
 msgstr ""
 
-#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:293
-msgid "Other fields you can group this metric by"
+#: src/metabase/api/geojson.clj
+msgid ""
+"JSON containing information about custom GeoJSON files for use in map "
+"visualizations instead of the default US State or World GeoJSON."
 msgstr ""
 
-#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:294
-msgid "Fields you can group this metric by"
+#: src/metabase/api/geojson.clj
+msgid "Invalid custom GeoJSON key: {0}"
 msgstr ""
 
-#: frontend/src/metabase/reference/metrics/MetricList.jsx:23
-msgid "Metrics are the official numbers that your team cares about"
+#. ...but if we *still* couldn't find a match, throw an Exception, because we don't want people
+#. trying to inject new params
+#: src/metabase/api/public.clj
+msgid "Invalid param: {0}"
 msgstr ""
 
-#: frontend/src/metabase/reference/metrics/MetricList.jsx:25
-msgid "Metrics will appear here once your admins have created some"
+#: src/metabase/api/pulse.clj
+msgid ""
+"DELETE /api/pulse/:id is deprecated. Instead, change its `archived` value "
+"via PUT /api/pulse/:id."
 msgstr ""
 
-#: frontend/src/metabase/reference/metrics/MetricList.jsx:27
-msgid "Learn how to create metrics"
+#: src/metabase/api/routes.clj
+msgid "API endpoint does not exist."
 msgstr ""
 
-#: frontend/src/metabase/reference/metrics/MetricQuestions.jsx:35
-msgid "Questions about this metric will appear here as they're added"
+#: src/metabase/api/session.clj
+msgid "Password did not match stored password."
 msgstr ""
 
-#: frontend/src/metabase/reference/metrics/MetricRevisions.jsx:29
-msgid "There are no revisions for this metric"
+#: src/metabase/api/session.clj
+msgid "did not match stored password"
 msgstr ""
 
-#: frontend/src/metabase/reference/metrics/MetricRevisions.jsx:88
-#: frontend/src/metabase/reference/metrics/MetricSidebar.jsx:41
-#: frontend/src/metabase/reference/segments/SegmentRevisions.jsx:88
-msgid "Revision history for {0}"
+#: src/metabase/api/session.clj
+msgid ""
+"Problem connecting to LDAP server, will fallback to local authentication {0}"
 msgstr ""
 
-#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:217
-msgid "Why this Segment is interesting"
+#: src/metabase/api/session.clj
+msgid "Invalid reset token"
 msgstr ""
 
-#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:227
-msgid "Things to be aware of about this Segment"
+#: src/metabase/api/session.clj
+msgid ""
+"Client ID for Google Auth SSO. If this is set, Google Auth is considered to "
+"be enabled."
 msgstr ""
 
-#: frontend/src/metabase/reference/segments/SegmentList.jsx:23
-msgid "Segments are interesting subsets of tables"
+#: src/metabase/api/session.clj
+msgid ""
+"When set, allow users to sign up on their own if their Google account email "
+"address is from this domain."
 msgstr ""
 
-#: frontend/src/metabase/reference/segments/SegmentList.jsx:24
-msgid ""
-"Defining common segments for your team makes it even easier to ask questions"
+#: src/metabase/api/session.clj
+msgid "Invalid Google Auth token."
 msgstr ""
 
-#: frontend/src/metabase/reference/segments/SegmentList.jsx:25
-msgid "Segments will appear here once your admins have created some"
+#: src/metabase/api/session.clj
+msgid "Email is not verified."
 msgstr ""
 
-#: frontend/src/metabase/reference/segments/SegmentList.jsx:27
-msgid "Learn how to create segments"
+#: src/metabase/api/session.clj
+msgid ""
+"You''ll need an administrator to create a Metabase account before you can "
+"use Google to log in."
 msgstr ""
 
-#: frontend/src/metabase/reference/segments/SegmentQuestions.jsx:35
-msgid "Questions about this segment will appear here as they're added"
+#: src/metabase/api/session.clj
+msgid "Successfully authenticated Google Auth token for: {0} {1}"
 msgstr ""
 
-#: frontend/src/metabase/reference/segments/SegmentRevisions.jsx:29
-msgid "There are no revisions for this segment"
+#: src/metabase/api/setup.clj
+msgid "Add a database"
 msgstr ""
 
-#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:33
-#: frontend/src/metabase/xray/containers/SegmentXRay.jsx:170
-msgid "Fields in this segment"
+#: src/metabase/api/setup.clj
+msgid "Get connected"
 msgstr ""
 
-#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:39
-msgid "Questions about this segment"
+#: src/metabase/api/setup.clj
+msgid "Connect to your data so your whole team can start to explore."
 msgstr ""
 
-#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:45
-msgid "X-ray this segment"
+#: src/metabase/api/setup.clj
+msgid "Set up email"
 msgstr ""
 
-#: frontend/src/metabase/routes.jsx:193
-msgid "Login"
+#: src/metabase/api/setup.clj
+msgid ""
+"Add email credentials so you can more easily invite team members and get "
+"updates via Pulses."
 msgstr ""
 
-#: frontend/src/metabase/routes.jsx:221
-msgid "Dashboard"
+#: src/metabase/api/setup.clj
+msgid "Set Slack credentials"
 msgstr ""
 
-#: frontend/src/metabase/routes.jsx:247
-msgid "Search"
+#: src/metabase/api/setup.clj
+msgid ""
+"Does your team use Slack? If so, you can send automated updates via pulses "
+"and ask questions with MetaBot."
 msgstr ""
 
-#: frontend/src/metabase/routes.jsx:346
-#: frontend/src/metabase/xray/containers/TableXRay.jsx:119
-msgid "XRay"
+#: src/metabase/api/setup.clj
+msgid "Invite team members"
 msgstr ""
 
-#: frontend/src/metabase/routes.jsx:382
-msgid "Data Model"
+#: src/metabase/api/setup.clj
+msgid "Share answers and data with the rest of your team."
 msgstr ""
 
-#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:141
-msgid "Add your data"
+#: src/metabase/api/setup.clj
+msgid "Hide irrelevant tables"
 msgstr ""
 
-#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:145
-msgid "I'll add my own data later"
+#: src/metabase/api/setup.clj
+msgid "Curate your data"
 msgstr ""
 
-#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:146
-msgid "Connecting to {0}"
+#: src/metabase/api/setup.clj
+msgid "If your data contains technical or irrelevant info you can hide it."
 msgstr ""
 
-#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:165
-msgid ""
-"You’ll need some info about your database, like the username and password. "
-"If you don’t have that right now, Metabase also comes with a sample dataset "
-"you can get started with."
+#: src/metabase/api/setup.clj
+msgid "Organize questions"
 msgstr ""
 
-#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:196
-msgid "I'll add my data later"
+#: src/metabase/api/setup.clj
+msgid ""
+"Have a lot of saved questions in {0}? Create collections to help manage them "
+"and add context."
 msgstr ""
 
-#: frontend/src/metabase/setup/components/DatabaseSchedulingStep.jsx:41
-msgid "Control automatic scans"
+#: src/metabase/api/setup.clj
+msgid "Metabase"
 msgstr ""
 
-#: frontend/src/metabase/setup/components/PreferencesStep.jsx:53
-msgid "Usage data preferences"
+#: src/metabase/api/setup.clj
+msgid "Create metrics"
 msgstr ""
 
-#: frontend/src/metabase/setup/components/PreferencesStep.jsx:56
-msgid "Thanks for helping us improve"
+#: src/metabase/api/setup.clj
+msgid ""
+"Define canonical metrics to make it easier for the rest of your team to get "
+"the right answers."
 msgstr ""
 
-#: frontend/src/metabase/setup/components/PreferencesStep.jsx:57
-msgid "We won't collect any usage events"
+#: src/metabase/api/setup.clj
+msgid "Create segments"
 msgstr ""
 
-#: frontend/src/metabase/setup/components/PreferencesStep.jsx:76
+#: src/metabase/api/setup.clj
 msgid ""
-"In order to help us improve Metabase, we'd like to collect certain data "
-"about usage through Google Analytics."
+"Keep everyone on the same page by creating canonical sets of filters anyone "
+"can use while asking questions."
 msgstr ""
 
-#: frontend/src/metabase/setup/components/PreferencesStep.jsx:85
-msgid "Here's a full list of everything we track and why."
+#: src/metabase/api/table.clj
+msgid "Table ''{0}'' is now visible. Resyncing."
 msgstr ""
 
-#: frontend/src/metabase/setup/components/PreferencesStep.jsx:98
-msgid "Allow Metabase to anonymously collect usage events"
+#: src/metabase/api/table.clj
+msgid "Auto bin"
 msgstr ""
 
-#: frontend/src/metabase/setup/components/PreferencesStep.jsx:105
-msgid "Metabase {0} collects anything about your data or question results."
+#: src/metabase/api/table.clj
+msgid "Don''t bin"
 msgstr ""
 
-#: frontend/src/metabase/setup/components/PreferencesStep.jsx:108
-msgid "All collection is completely anonymous."
+#: src/metabase/api/table.clj
+msgid "Day"
 msgstr ""
 
-#: frontend/src/metabase/setup/components/PreferencesStep.jsx:110
-msgid "Collection can be turned off at any point in your admin settings."
+#. note the order of these options corresponds to the order they will be shown to the user in the UI
+#: src/metabase/api/table.clj
+msgid "Minute"
 msgstr ""
 
-#: frontend/src/metabase/setup/components/Setup.jsx:45
-msgid "If you feel stuck"
+#: src/metabase/api/table.clj
+msgid "Hour"
 msgstr ""
 
-#: frontend/src/metabase/setup/components/Setup.jsx:50
-msgid "our getting started guide"
+#: src/metabase/api/table.clj
+msgid "Quarter"
 msgstr ""
 
-#: frontend/src/metabase/setup/components/Setup.jsx:51
-msgid "is just a click away."
+#: src/metabase/api/table.clj
+msgid "Minute of Hour"
 msgstr ""
 
-#: frontend/src/metabase/setup/components/Setup.jsx:93
-msgid "Welcome to Metabase"
+#: src/metabase/api/table.clj
+msgid "Hour of Day"
 msgstr ""
 
-#: frontend/src/metabase/setup/components/Setup.jsx:94
-msgid ""
-"Looks like everything is working. Now let’s get to know you, connect to your "
-"data, and start finding you some answers!"
+#: src/metabase/api/table.clj
+msgid "Day of Week"
 msgstr ""
 
-#: frontend/src/metabase/setup/components/Setup.jsx:98
-msgid "Let's get started"
+#: src/metabase/api/table.clj
+msgid "Day of Month"
 msgstr ""
 
-#: frontend/src/metabase/setup/components/Setup.jsx:143
-msgid "You're all set up!"
+#: src/metabase/api/table.clj
+msgid "Day of Year"
 msgstr ""
 
-#: frontend/src/metabase/setup/components/Setup.jsx:154
-msgid "Take me to Metabase"
+#: src/metabase/api/table.clj
+msgid "Week of Year"
 msgstr ""
 
-#: frontend/src/metabase/setup/components/UserStep.jsx:152
-msgid "What should we call you?"
+#: src/metabase/api/table.clj
+msgid "Month of Year"
 msgstr ""
 
-#: frontend/src/metabase/setup/components/UserStep.jsx:153
-msgid "Hi, {0}. nice to meet you!"
+#: src/metabase/api/table.clj
+msgid "Quarter of Year"
 msgstr ""
 
-#: frontend/src/metabase/setup/components/UserStep.jsx:238
-msgid "Create a password"
+#: src/metabase/api/table.clj
+msgid "10 bins"
 msgstr ""
 
-#: frontend/src/metabase/setup/components/UserStep.jsx:254
-#: frontend/src/metabase/user/components/SetUserPassword.jsx:112
-msgid "Shhh..."
+#: src/metabase/api/table.clj
+msgid "50 bins"
 msgstr ""
 
-#: frontend/src/metabase/setup/components/UserStep.jsx:264
-msgid "Confirm password"
+#: src/metabase/api/table.clj
+msgid "100 bins"
 msgstr ""
 
-#: frontend/src/metabase/setup/components/UserStep.jsx:273
-msgid "Shhh... but one more time so we get it right"
+#: src/metabase/api/table.clj
+msgid "Bin every 0.1 degrees"
 msgstr ""
 
-#: frontend/src/metabase/setup/components/UserStep.jsx:282
-msgid "Your company or team name"
+#: src/metabase/api/table.clj
+msgid "Bin every 1 degree"
 msgstr ""
 
-#: frontend/src/metabase/setup/components/UserStep.jsx:291
-msgid "Department of awesome"
+#: src/metabase/api/table.clj
+msgid "Bin every 10 degrees"
 msgstr ""
 
-#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:20
-msgid "Welcome to the Query Builder!"
+#: src/metabase/api/table.clj
+msgid "Bin every 20 degrees"
 msgstr ""
 
-#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:22
-msgid ""
-"The Query Builder lets you assemble questions (or \"queries\") to ask about "
-"your data."
+#. returns `true` if successful -- see JavaDoc
+#: src/metabase/api/tiles.clj src/metabase/pulse/render.clj
+msgid "No appropriate image writer found!"
 msgstr ""
 
-#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:26
-msgid "Tell me more"
+#: src/metabase/api/user.clj
+msgid "Email address already in use."
 msgstr ""
 
-#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:43
-msgid ""
-"Start by picking the table with the data that you have a question about."
+#: src/metabase/api/user.clj
+msgid "Email address already associated to another user."
 msgstr ""
 
-#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:45
-msgid "Go ahead and select the \"Orders\" table from the dropdown menu."
+#: src/metabase/api/user.clj
+msgid "Not able to reactivate an active user"
 msgstr ""
 
-#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:78
-msgid "Filter your data to get just what you want."
+#: src/metabase/api/user.clj
+msgid "Invalid password"
 msgstr ""
 
-#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:79
-msgid "Click the plus button and select the \"Created At\" field."
+#: src/metabase/automagic_dashboards/comparison.clj
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Compare with {0}"
 msgstr ""
 
-#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:93
-msgid "Here we can pick how many days we want to see data for, try 10"
+#: src/metabase/automagic_dashboards/comparison.clj
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Compare with entire dataset"
 msgstr ""
 
-#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:116
-msgid ""
-"Here's where you can choose to add or average your data, count the number of "
-"rows in the table, or just view the raw data."
+#: src/metabase/automagic_dashboards/comparison.clj
+msgid "All {0}"
 msgstr ""
 
-#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:118
-msgid ""
-"Try it: click on <strong>Raw Data</strong> to change it to <strong>Count of "
-"rows</strong> so we can count how many orders there are in this table."
+#: src/metabase/automagic_dashboards/comparison.clj
+msgid "{0}, all {1}"
 msgstr ""
 
-#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:142
-msgid ""
-"Add a grouping to break out your results by category, day, month, and more."
+#: src/metabase/automagic_dashboards/comparison.clj
+msgid "Comparison of {0} and {1}"
 msgstr ""
 
-#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:144
-msgid ""
-"Let's do it: click on <strong>Add a grouping</strong>, and choose "
-"<strong>Created At: by Week</strong>."
+#: src/metabase/automagic_dashboards/comparison.clj
+msgid "Automatically generated comparison dashboard comparing {0} and {1}"
 msgstr ""
 
-#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:152
-msgid "Click on \"by day\" to change it to \"Week.\""
+#: src/metabase/automagic_dashboards/core.clj
+msgid "sum"
 msgstr ""
 
-#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:173
-msgid "Run Your Query."
+#: src/metabase/automagic_dashboards/core.clj
+msgid "average"
 msgstr ""
 
-#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:175
-msgid ""
-"You're doing so well! Click <strong>Run query</strong> to get your results!"
+#: src/metabase/automagic_dashboards/core.clj
+msgid "minumum"
 msgstr ""
 
-#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:192
-msgid "You can view your results as a chart instead of a table."
+#: src/metabase/automagic_dashboards/core.clj
+msgid "maximum"
 msgstr ""
 
-#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:194
-msgid ""
-"Everbody likes charts! Click the <strong>Visualization</strong> dropdown and "
-"select <strong>Line</strong>."
+#: src/metabase/automagic_dashboards/core.clj
+msgid "distinct count"
 msgstr ""
 
-#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:216
-msgid "Well done!"
+#: src/metabase/automagic_dashboards/core.clj
+msgid "standard deviation"
 msgstr ""
 
-#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:218
-msgid "That's all! If you still have questions, check out our"
+#: src/metabase/automagic_dashboards/core.clj
+msgid "cumulative count"
 msgstr ""
 
-#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:223
-msgid "User's Guide"
+#: src/metabase/automagic_dashboards/core.clj
+msgid "cumulative sum"
 msgstr ""
 
-#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:223
-msgid "Have fun exploring your data!"
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} and {1}"
 msgstr ""
 
-#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:226
-msgid "Thanks"
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} of {1}"
 msgstr ""
 
-#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:235
-msgid "Save Your Questions"
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} by {1}"
 msgstr ""
 
-#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:237
-msgid ""
-"By the way, you can save your questions so you can refer to them later. "
-"Saved Questions can also be put into dashboards or Pulses."
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} in the {1} segment"
 msgstr ""
 
-#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:241
-msgid "Sounds good"
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} segment"
 msgstr ""
 
-#: frontend/src/metabase/tutorial/Tutorial.jsx:240
-msgid "Whoops!"
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} metric"
 msgstr ""
 
-#: frontend/src/metabase/tutorial/Tutorial.jsx:241
-msgid ""
-"Sorry, it looks like something went wrong. Please try restarting the "
-"tutorial in a minute."
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} field"
 msgstr ""
 
-#: frontend/src/metabase/user/actions.js:34
-msgid "Password updated successfully!"
+#: src/metabase/automagic_dashboards/core.clj
+msgid "\"{0}\" question"
 msgstr ""
 
-#: frontend/src/metabase/user/actions.js:53
-msgid "Account updated successfully!"
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Applying heuristic %s to %s."
 msgstr ""
 
-#: frontend/src/metabase/user/components/SetUserPassword.jsx:103
-msgid "Current password"
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Dimensions bindings:n%s"
 msgstr ""
 
-#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:135
-msgid "Sign in with Google Email address"
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Using definitions:nMetrics:n%snFilters:n%s"
 msgstr ""
 
-#: frontend/src/metabase/user/components/UserSettings.jsx:54
-msgid "Account settings"
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Can''t create dashboard for {0}"
 msgstr ""
 
-#: frontend/src/metabase/user/components/UserSettings.jsx:65
-msgid "User Details"
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0}st"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/components/ChartSettings.jsx:150
-msgid "Customize this {0}"
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0}nd"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/components/ChartSettings.jsx:192
-msgid "Reset to defaults"
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0}rd"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/components/ChoroplethMap.jsx:120
-msgid "unknown map"
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0}th"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/components/LeafletGridHeatMap.jsx:25
-msgid "Grid map requires binned longitude/latitude."
+#: src/metabase/automagic_dashboards/core.clj
+msgid "at {0}"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/components/LegendVertical.jsx:112
-msgid "more"
+#: src/metabase/automagic_dashboards/core.clj
+msgid "on {0}"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/components/LineAreaBarChart.jsx:101
-msgid "Which fields do you want to use for the X and Y axes?"
+#: src/metabase/automagic_dashboards/core.clj
+msgid "in {0} week - {1}"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/components/LineAreaBarChart.jsx:103
-#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:59
-msgid "Choose fields"
+#: src/metabase/automagic_dashboards/core.clj
+msgid "in {0}"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/components/PinMap.jsx:206
-msgid "Save as default view"
+#: src/metabase/automagic_dashboards/core.clj
+msgid "in Q{0} - {1}"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/components/PinMap.jsx:228
-msgid "Draw box to filter"
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Q{0}"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/components/PinMap.jsx:228
-msgid "Cancel filter"
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} is {1}"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/components/PinMap.jsx:47
-msgid "Pin Map"
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} is between {1} and {2}"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/components/TableInteractive.jsx:310
-msgid "Unset"
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} is between {1} and {2}; and {3} is between {4} and {5}"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/components/TableSimple.jsx:214
-msgid "Rows {0}-{1} of {2}"
+#: src/metabase/automagic_dashboards/core.clj
+msgid "where {0}"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/components/Visualization.jsx:175
-msgid "Data truncated to {0} rows."
+#: src/metabase/automagic_dashboards/core.clj
+msgid "A closer look at {0}"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/components/Visualization.jsx:338
-msgid "Could not find visualization"
+#: src/metabase/automagic_dashboards/core.clj
+msgid "A closer look at the {0}"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/components/Visualization.jsx:345
-msgid "Could not display this chart with this data."
+#: src/metabase/automagic_dashboards/populate.clj
+msgid "Adding %s cards to dashboard %s:n%s"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/components/Visualization.jsx:442
-msgid "No results!"
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "0 <= score <= {0}"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/components/Visualization.jsx:463
-msgid "Still Waiting..."
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "1 <= width <= {0}"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/components/Visualization.jsx:466
-msgid "This usually takes an average of {0}."
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid metrics references"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/components/Visualization.jsx:472
-msgid "(This is a bit long for a dashboard)"
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid filters references"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/components/Visualization.jsx:476
-msgid "This is usually pretty fast, but seems to be taking awhile right now."
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid group references"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/components/settings/ChartSettingFieldPicker.jsx:14
-msgid "Select a field"
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid order_by references"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/components/settings/ChartSettingFieldPicker.jsx:15
-msgid "No valid fields"
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid dashboard filters references"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/components/settings/ChartSettingFieldsPicker.jsx:42
-msgid "error"
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid dimension references"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/index.js:32
-msgid "Visualization must define an 'identifier' static variable: "
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid card dimension references"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/index.js:38
-msgid "Visualization with that identifier is already registered: "
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Error parsing %s:n%s"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/index.js:66
-msgid "No visualization for {0}"
+#: src/metabase/cmd/reset_password.clj
+msgid "No user found with email address ''{0}''. "
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/LineAreaBarRenderer.js:72
-msgid ""
-"\"{0}\" is an unaggregated field: if it has more than one value at a point "
-"on the x-axis, the values will be summed."
+#: src/metabase/cmd/reset_password.clj
+msgid "Please check the spelling and try again."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/LineAreaBarRenderer.js:88
-msgid "This chart type requires at least 2 columns."
+#: src/metabase/cmd/reset_password.clj
+msgid "Resetting password for {0}..."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/LineAreaBarRenderer.js:93
-msgid "This chart type doesn't support more than {0} series of data."
+#: src/metabase/cmd/reset_password.clj
+msgid "OK [[[{0}]]]"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/LineAreaBarRenderer.js:499
-#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:42
-msgid "Goal"
+#: src/metabase/cmd/reset_password.clj
+msgid "FAIL [[[{0}]]]"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/errors.js:10
-msgid ""
-"Doh! The data from your query doesn't fit the chosen display choice. This "
-"visualization requires at least {0} {1} of data."
+#: src/metabase/core.clj
+msgid "Please use the following URL to setup your Metabase installation:"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/errors.js:21
-msgid ""
-"No dice. We have {0} data {1} to show and that's not enough for this "
-"visualization."
+#: src/metabase/core.clj
+msgid "Metabase Shutting Down ..."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/errors.js:32
-msgid ""
-"Bummer. We can't actually do a pin map for this data because we require both "
-"a latitude and longitude column."
+#: src/metabase/core.clj
+msgid "Metabase Shutdown COMPLETE"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/errors.js:41
-msgid "Please configure this chart in the chart settings"
+#: src/metabase/core.clj
+msgid "Starting Metabase version {0} ..."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/errors.js:43
-msgid "Edit Settings"
+#: src/metabase/core.clj
+msgid "System timezone is ''{0}'' ..."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/fill_data.js:38
-msgid "\"xValues missing!"
+#. startup database.  validates connection & runs any necessary migrations
+#: src/metabase/core.clj
+msgid ""
+"Setting up and migrating Metabase DB. Please sit tight, this may take a "
+"minute..."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:47
-#: frontend/src/metabase/visualizations/visualizations/RowChart.jsx:31
-msgid "X-axis"
+#: src/metabase/core.clj
+msgid "Looks like this is a new installation ... preparing setup wizard"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:73
-msgid "Add a series breakout..."
+#: src/metabase/core.clj
+msgid "Metabase Initialization COMPLETE"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:84
-#: frontend/src/metabase/visualizations/visualizations/RowChart.jsx:35
-msgid "Y-axis"
+#: src/metabase/core.clj
+msgid "Launching Embedded Jetty Webserver with config:"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:109
-msgid "Add another series..."
+#: src/metabase/core.clj
+msgid "Shutting Down Embedded Jetty Webserver"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:123
-msgid "Bubble size"
+#: src/metabase/core.clj
+msgid "Starting Metabase in STANDALONE mode"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:152
-#: frontend/src/metabase/visualizations/visualizations/LineChart.jsx:17
-msgid "Line"
+#: src/metabase/core.clj
+msgid "Metabase Initialization FAILED"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:153
-msgid "Curve"
+#: src/metabase/db.clj
+msgid "Database has migration lock; cannot run migrations."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:154
-#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:67
-msgid "Step"
+#: src/metabase/db.clj
+msgid ""
+"You can force-release these locks by running `java -jar metabase.jar migrate "
+"release-locks`."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:161
-msgid "Show point markers on lines"
+#: src/metabase/db.clj
+msgid "Checking if Database has unrun migrations..."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:169
-msgid "Stacking"
+#: src/metabase/db.clj
+msgid ""
+"Database has unrun migrations. Waiting for migration lock to be cleared..."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:173
-msgid "Don't stack"
+#: src/metabase/db.clj
+msgid "Migration lock is cleared. Running migrations..."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:174
-msgid "Stack"
+#: src/metabase/db.clj
+msgid ""
+"Migration lock cleared, but nothing to do here! Migrations were finished by "
+"another instance."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:175
-msgid "Stack - 100%"
+#: src/metabase/db.clj
+msgid "Unable to release the Liquibase lock after a migration failure"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:192
-msgid "Show goal"
+#. Set up liquibase and let it do its thing
+#: src/metabase/db.clj
+msgid "Setting up Liquibase..."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:198
-msgid "Goal value"
+#: src/metabase/db.clj
+msgid "Liquibase is ready."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:209
-msgid "Replace missing values with"
+#: src/metabase/db.clj
+msgid "Verifying {0} Database Connection ..."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:214
-msgid "Zero"
+#: src/metabase/db.clj
+msgid "Verify Database Connection ... "
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:215
-msgid "Nothing"
+#: src/metabase/db.clj
+msgid "Running Database Migrations..."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:216
-msgid "Linear Interpolated"
+#: src/metabase/db.clj
+msgid "Database Migrations Current ... "
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:271
-msgid "X-axis scale"
+#. 2. Create the new collections.
+#: src/metabase/db/migrations.clj
+msgid "Migrated Dashboards"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:288
-msgid "Timeseries"
+#: src/metabase/db/migrations.clj
+msgid "Migrated Pulses"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:291
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:309
-msgid "Linear"
+#: src/metabase/db/migrations.clj
+msgid "Migrated Questions"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:293
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:310
-msgid "Power"
+#. 4. move everything not in this Collection to a new Collection
+#: src/metabase/db/migrations.clj
+msgid ""
+"Moving instances of {0} that aren't in a Collection to {1} Collection {2}"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:294
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:311
-msgid "Log"
+#: src/metabase/driver.clj
+msgid "Hmm, we couldn''t connect to the database."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:296
-msgid "Histogram"
+#: src/metabase/driver.clj
+msgid "Make sure your host and port settings are correct"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:298
-msgid "Ordinal"
+#: src/metabase/driver.clj
+msgid "We couldn''t connect to the ssh tunnel host."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:304
-msgid "Y-axis scale"
+#: src/metabase/driver.clj
+msgid "Check the username, password."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:317
-msgid "Show x-axis line and marks"
+#: src/metabase/driver.clj
+msgid "Check the hostname and port."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:323
-msgid "Show y-axis line and marks"
+#: src/metabase/driver.clj
+msgid "Looks like the database name is incorrect."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:329
-msgid "Auto y-axis range"
+#: src/metabase/driver.clj
+msgid "It looks like your host is invalid."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:373
-msgid "Use a split y-axis when necessary"
+#: src/metabase/driver.clj
+msgid "Please double-check it and try again."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:380
-msgid "Show label on x-axis"
+#: src/metabase/driver.clj
+msgid "Looks like your password is incorrect."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:386
-msgid "X-axis label"
+#: src/metabase/driver.clj
+msgid "Looks like you forgot to enter your password."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:395
-msgid "Show label on y-axis"
+#: src/metabase/driver.clj
+msgid "Looks like your username is incorrect."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/settings/graph.js:401
-msgid "Y-axis label"
+#: src/metabase/driver.clj
+msgid "Looks like the username or password is incorrect."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/lib/utils.js:116
-msgid "Standard Deviation"
+#: src/metabase/driver.clj
+msgid "Host"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/AreaChart.jsx:18
-msgid "Area"
+#: src/metabase/driver.clj
+msgid "Port"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/AreaChart.jsx:21
-msgid "area chart"
+#: src/metabase/driver.clj
+msgid "Database username"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/BarChart.jsx:16
-msgid "Bar"
+#: src/metabase/driver.clj
+msgid "What username do you use to login to the database?"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/BarChart.jsx:19
-msgid "bar chart"
+#: src/metabase/driver.clj
+msgid "Database password"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:57
-msgid "Which fields do you want to use?"
+#: src/metabase/driver.clj
+msgid "Database name"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:31
-#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:85
-msgid "Funnel"
+#: src/metabase/driver.clj
+msgid "birds_of_the_world"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:74
-#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:67
-msgid "Measure"
+#: src/metabase/driver.clj
+msgid "Use a secure connection (SSL)?"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:80
-msgid "Funnel type"
+#: src/metabase/driver.clj
+msgid "Additional JDBC connection string options"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:86
-msgid "Bar chart"
+#. ## CONFIG
+#: src/metabase/driver.clj
+msgid ""
+"Connection timezone to use when executing queries. Defaults to system "
+"timezone."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/LineChart.jsx:20
-msgid "line chart"
+#: src/metabase/driver.clj
+msgid "Registered driver {0} {1}"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Map.jsx:206
-msgid "Please select longitude and latitude columns in the chart settings."
+#: src/metabase/driver.clj
+msgid "No -init-driver function found for ''{0}''"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Map.jsx:212
-msgid "Please select a region map."
+#: src/metabase/driver.clj
+msgid "Unable to parse date string ''{0}'' for database engine ''{1}''"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Map.jsx:216
-msgid "Please select region and metric columns in the chart settings."
+#. all-NULL columns in DBs like Mongo w/o explicit types
+#: src/metabase/driver.clj
+msgid ""
+"Don''t know how to map class ''{0}'' to a Field base_type, falling back to :"
+"type/*."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Map.jsx:33
-msgid "Map"
+#: src/metabase/driver.clj
+msgid "Failed to connect to database: {0}"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Map.jsx:47
-msgid "Map type"
+#: src/metabase/driver/bigquery.clj
+msgid "Invalid BigQuery identifier: ''{0}''"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Map.jsx:51
-#: frontend/src/metabase/visualizations/visualizations/Map.jsx:141
-msgid "Region map"
+#: src/metabase/driver/bigquery.clj
+msgid "BigQuery statements can't be parameterized!"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Map.jsx:52
-msgid "Pin map"
+#: src/metabase/driver/bigquery.clj
+msgid "Project ID"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Map.jsx:91
-msgid "Pin type"
+#: src/metabase/driver/bigquery.clj
+msgid "praxis-beacon-120871"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Map.jsx:96
-msgid "Tiles"
+#: src/metabase/driver/bigquery.clj
+msgid "Dataset ID"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Map.jsx:97
-msgid "Markers"
+#: src/metabase/driver/bigquery.clj
+msgid "toucanSightings"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Map.jsx:113
-msgid "Latitude field"
+#: src/metabase/driver/bigquery.clj src/metabase/driver/googleanalytics.clj
+msgid "Client ID"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Map.jsx:123
-msgid "Longitude field"
+#: src/metabase/driver/bigquery.clj src/metabase/driver/googleanalytics.clj
+msgid "Client Secret"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Map.jsx:133
-#: frontend/src/metabase/visualizations/visualizations/Map.jsx:160
-msgid "Metric field"
+#: src/metabase/driver/bigquery.clj src/metabase/driver/googleanalytics.clj
+msgid "Auth Code"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Map.jsx:165
-msgid "Region field"
+#: src/metabase/driver/bigquery.clj
+msgid "Use JVM Time Zone"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Map.jsx:174
-msgid "Radius"
+#: src/metabase/driver/crate.clj
+msgid "Hosts"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Map.jsx:180
-msgid "Blur"
+#: src/metabase/driver/druid.clj
+msgid "Broker node port"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Map.jsx:186
-msgid "Min Opacity"
+#: src/metabase/driver/generic_sql/query_processor.clj
+msgid "Failed to set timezone:"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Map.jsx:192
-msgid "Max Zoom"
+#: src/metabase/driver/googleanalytics.clj
+msgid ""
+"You must enable the Google Analytics API. Use this link to go to the Google "
+"Developers Console: {0}"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:169
-msgid "No relationships found."
+#: src/metabase/driver/googleanalytics.clj
+msgid "Google Analytics Account ID"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:207
-msgid "via {0}"
+#: src/metabase/driver/h2.clj
+msgid ""
+"Running SQL queries against H2 databases using the default (admin) database "
+"user is forbidden."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:284
-msgid "This {0} is connected to:"
+#: src/metabase/driver/h2.clj
+msgid "Connection String"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:47
-msgid "Object Detail"
+#: src/metabase/driver/h2.clj
+msgid "Users/camsaul/bird_sightings/toucans"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:50
-msgid "object"
+#: src/metabase/driver/mongo.clj
+msgid "carrierPigeonDeliveries"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:234
-msgid "Total"
+#: src/metabase/driver/mongo.clj
+msgid "Authentication Database"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:53
-msgid "Which columns do you want to use?"
+#: src/metabase/driver/mongo.clj
+msgid "Optional database to use when authenticating"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:40
-msgid "Pie"
+#: src/metabase/driver/mongo.clj
+msgid "Additional Mongo connection string options"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:62
-msgid "Dimension"
+#: src/metabase/driver/oracle.clj
+msgid "Oracle system ID (SID)"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:72
-msgid "Show legend"
+#: src/metabase/driver/oracle.clj
+msgid "Usually something like ORCL or XE."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:77
-msgid "Show percentages in legend"
+#: src/metabase/driver/oracle.clj
+msgid "Optional if using service name"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:83
-msgid "Minimum slice percentage"
+#: src/metabase/driver/oracle.clj
+msgid "Oracle service name"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:135
-msgid "Goal met"
+#: src/metabase/driver/oracle.clj
+msgid "Optional TNS alias"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:137
-msgid "Goal exceeded"
+#: src/metabase/driver/presto.clj
+msgid "hive"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:207
-msgid "Goal {0}"
+#: src/metabase/driver/redshift.clj
+msgid "my-cluster-name.abcd1234.us-east-1.redshift.amazonaws.com"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:35
-msgid "Progress visualization requires a number."
+#: src/metabase/driver/redshift.clj
+msgid "toucan_sightings"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:23
-msgid "Progress"
+#: src/metabase/driver/sparksql.clj
+msgid "default"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/RowChart.jsx:13
-msgid "Row Chart"
+#: src/metabase/driver/sparksql.clj
+msgid ""
+"Error: metabase.driver.FixedHiveDriver is registered, but JDBC does not seem "
+"to be using it."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/RowChart.jsx:16
-msgid "row chart"
+#: src/metabase/driver/sparksql.clj
+msgid "Found metabase.driver.FixedHiveDriver."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:75
-msgid "Separator style"
+#: src/metabase/driver/sparksql.clj
+msgid "Successfully registered metabase.driver.FixedHiveDriver with JDBC."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:88
-msgid "Number of decimal places"
+#: src/metabase/driver/sqlite.clj
+msgid "Filename"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:92
-msgid "Add a prefix"
+#: src/metabase/driver/sqlite.clj
+msgid "/home/camsaul/toucan_sightings.sqlite 😋"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:96
-msgid "Add a suffix"
+#: src/metabase/driver/sqlserver.clj
+msgid "BirdsOfTheWorld"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:100
-msgid "Multiply by a number"
+#: src/metabase/driver/sqlserver.clj
+msgid "Database instance name"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/ScatterPlot.jsx:16
-msgid "Scatter"
+#: src/metabase/driver/sqlserver.clj
+msgid "N/A"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/ScatterPlot.jsx:19
-msgid "scatter plot"
+#: src/metabase/driver/sqlserver.clj
+msgid "Windows domain"
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Table.jsx:56
-msgid "Pivot the table"
+#. CONFIG
+#. TODO - smtp-port should be switched to type :integer
+#: src/metabase/email.clj
+msgid "Email address you want to use as the sender of Metabase."
 msgstr ""
 
-#: frontend/src/metabase/visualizations/visualizations/Table.jsx:67
-msgid "Fields to include"
+#: src/metabase/email.clj
+msgid "The address of the SMTP server that handles your emails."
 msgstr ""
 
-#: frontend/src/metabase/xray/components/ComparisonHeader.jsx:10
-msgid "Comparing"
+#: src/metabase/email.clj
+msgid "SMTP username."
 msgstr ""
 
-#: frontend/src/metabase/xray/components/ComparisonHeader.jsx:13
-#: frontend/src/metabase/xray/containers/FieldXray.jsx:128
-#: frontend/src/metabase/xray/containers/SegmentXRay.jsx:155
-msgid "Fidelity"
+#: src/metabase/email.clj
+msgid "SMTP password."
 msgstr ""
 
-#: frontend/src/metabase/xray/components/InsightCard.jsx:15
-msgid "Was this helpful?"
+#: src/metabase/email.clj
+msgid "The port your SMTP server uses for outgoing emails."
 msgstr ""
 
-#: frontend/src/metabase/xray/components/InsightCard.jsx:42
-msgid "Most of the values for {0} are between {1} and {2}."
+#: src/metabase/email.clj
+msgid "SMTP secure connection protocol. (tls, ssl, starttls, or none)"
 msgstr ""
 
-#: frontend/src/metabase/xray/components/InsightCard.jsx:35
-msgid "Normal range of values"
+#: src/metabase/email.clj
+msgid "none"
 msgstr ""
 
-#: frontend/src/metabase/xray/components/InsightCard.jsx:70
-msgid "You have {0} missing (null) values in your data"
+#: src/metabase/email.clj
+msgid "SMTP host is not set."
 msgstr ""
 
-#: frontend/src/metabase/xray/components/InsightCard.jsx:51
-msgid "Missing data"
+#: src/metabase/email.clj
+msgid "Failed to send email"
 msgstr ""
 
-#: frontend/src/metabase/xray/components/InsightCard.jsx:103
-msgid ""
-"You have {0} zeros in your data. They may be stand-ins for missing data, or "
-"might indicate some other abnormality."
+#: src/metabase/email.clj
+msgid "Error testing SMTP connection"
 msgstr ""
 
-#: frontend/src/metabase/xray/components/InsightCard.jsx:84
-msgid "Zeros in your data"
+#: src/metabase/integrations/ldap.clj
+msgid "Enable LDAP authentication."
 msgstr ""
 
-#: frontend/src/metabase/xray/components/InsightCard.jsx:115
-msgid ""
-"Noisy data is highly variable, jumping all over the place with changes "
-"carrying relatively little information."
+#: src/metabase/integrations/ldap.clj
+msgid "Server hostname."
 msgstr ""
 
-#: frontend/src/metabase/xray/components/InsightCard.jsx:120
-msgid "Noisy data"
+#: src/metabase/integrations/ldap.clj
+msgid "Server port, usually 389 or 636 if SSL is used."
 msgstr ""
 
-#: frontend/src/metabase/xray/components/InsightCard.jsx:142
-msgid "A measure of how much changes in previous values predict future values."
+#: src/metabase/integrations/ldap.clj
+msgid "Use SSL, TLS or plain text."
 msgstr ""
 
-#: frontend/src/metabase/xray/components/InsightCard.jsx:147
-msgid "Autocorrelation"
+#: src/metabase/integrations/ldap.clj
+msgid ""
+"The Distinguished Name to bind as (if any), this user will be used to lookup "
+"information about other users."
 msgstr ""
 
-#: frontend/src/metabase/xray/components/InsightCard.jsx:168
-msgid "How variance in your data is changing over time."
+#: src/metabase/integrations/ldap.clj
+msgid "The password to bind with for the lookup user."
 msgstr ""
 
-#: frontend/src/metabase/xray/components/InsightCard.jsx:173
-msgid "Trending variation"
+#: src/metabase/integrations/ldap.clj
+msgid "Search base for users. (Will be searched recursively)"
 msgstr ""
 
-#: frontend/src/metabase/xray/components/InsightCard.jsx:204
-msgid "Your data has a {0} seasonal component."
+#: src/metabase/integrations/ldap.clj
+msgid ""
+"User lookup filter, the placeholder '{login}' will be replaced by the user "
+"supplied login."
 msgstr ""
 
-#: frontend/src/metabase/xray/components/InsightCard.jsx:196
-msgid "Seasonality"
+#: src/metabase/integrations/ldap.clj
+msgid ""
+"Attribute to use for the user's email. (usually ''mail'', ''email'' or "
+"''userPrincipalName'')"
 msgstr ""
 
-#: frontend/src/metabase/xray/components/InsightCard.jsx:210
-msgid "Data distribution with multiple peaks (modes)."
+#: src/metabase/integrations/ldap.clj
+msgid "Attribute to use for the user''s first name. (usually ''givenName'')"
 msgstr ""
 
-#: frontend/src/metabase/xray/components/InsightCard.jsx:215
-msgid "Multimodal"
+#: src/metabase/integrations/ldap.clj
+msgid "Attribute to use for the user''s last name. (usually ''sn'')"
 msgstr ""
 
-#: frontend/src/metabase/xray/components/InsightCard.jsx:236
-msgid "Outliers"
+#: src/metabase/integrations/ldap.clj
+msgid "Enable group membership synchronization with LDAP."
 msgstr ""
 
-#: frontend/src/metabase/xray/components/InsightCard.jsx:269
-msgid "Structural breaks"
+#: src/metabase/integrations/ldap.clj
+msgid ""
+"Search base for groups, not required if your LDAP directory provides a "
+"''memberOf'' overlay. (Will be searched recursively)"
 msgstr ""
 
-#: frontend/src/metabase/xray/components/InsightCard.jsx:301
-msgid "The mean does not change over time."
+#. Should be in the form: {"cn=Some Group,dc=...": [1, 2, 3]} where keys are LDAP groups and values are lists of MB groups IDs
+#: src/metabase/integrations/ldap.clj
+msgid "JSON containing LDAP to Metabase group mappings."
 msgstr ""
 
-#: frontend/src/metabase/xray/components/InsightCard.jsx:306
-msgid "Stationary data"
+#. Define a setting which captures our Slack api token
+#: src/metabase/integrations/slack.clj
+msgid ""
+"Slack API bearer token obtained from https://api.slack.com/web#authentication"
 msgstr ""
 
-#: frontend/src/metabase/xray/components/InsightCard.jsx:333
-msgid "Your data seems to be {0} {1}."
+#: src/metabase/metabot.clj
+msgid ""
+"Enable MetaBot, which lets you search for and view your saved questions "
+"directly via Slack."
 msgstr ""
 
-#: frontend/src/metabase/xray/components/InsightCard.jsx:326
-#: frontend/src/metabase/xray/containers/CardXRay.jsx:157
-#: frontend/src/metabase/xray/containers/CardXRay.jsx:189
-msgid "Trend"
+#: src/metabase/metabot.clj
+msgid "Last MetaBot checkin was {0} ago."
 msgstr ""
 
-#: frontend/src/metabase/xray/components/PreviewBanner.jsx:15
-msgid "Welcome to the x-ray preview! We'd love {0}"
+#: src/metabase/metabot.clj
+msgid "This instance will now handle MetaBot duties."
 msgstr ""
 
-#: frontend/src/metabase/xray/components/XRayComparison.jsx:191
-msgid "Overview"
+#: src/metabase/metabot.clj
+msgid "Here''s what I can {0}:"
 msgstr ""
 
-#: frontend/src/metabase/xray/components/XRayComparison.jsx:207
-msgid "Potentially interesting differences"
+#: src/metabase/metabot.clj
+msgid "I don''t know how to {0} `{1}`.n{2}"
 msgstr ""
 
-#: frontend/src/metabase/xray/components/XRayComparison.jsx:222
-msgid "Full breakdown"
+#: src/metabase/metabot.clj
+msgid "Uh oh! :cry:n> {0}"
 msgstr ""
 
-#: frontend/src/metabase/xray/components/XRayComparison.jsx:238
-#: frontend/src/metabase/xray/containers/FieldXray.jsx:68
-msgid "Field"
+#: src/metabase/metabot.clj
+msgid "Here''s your {0} most recent cards:n{1}"
 msgstr ""
 
-#: frontend/src/metabase/xray/containers/CardXRay.jsx:134
-msgid "Growth Trend"
+#: src/metabase/metabot.clj
+msgid ""
+"Could you be a little more specific? I found these cards with names that "
+"matched:n{0}"
 msgstr ""
 
-#: frontend/src/metabase/xray/containers/CardXRay.jsx:197
-msgid "Seasonal"
+#: src/metabase/metabot.clj
+msgid "I don''t know what Card `{0}` is. Give me a Card ID or name."
 msgstr ""
 
-#: frontend/src/metabase/xray/containers/CardXRay.jsx:206
-msgid "Residual"
+#: src/metabase/metabot.clj
+msgid ""
+"Show which card? Give me a part of a card name or its ID and I can show it "
+"to you. If you don''t know which card you want, try `metabot list`."
 msgstr ""
 
-#: frontend/src/metabase/xray/containers/FieldXray.jsx:125
-#: frontend/src/metabase/xray/containers/SegmentXRay.jsx:148
-msgid "X-ray"
+#: src/metabase/metabot.clj
+msgid "Ok, just a second..."
 msgstr ""
 
-#: frontend/src/metabase/xray/containers/FieldXray.jsx:163
-msgid "Values overview"
+#: src/metabase/metabot.clj
+msgid "Not Found"
 msgstr ""
 
-#: frontend/src/metabase/xray/containers/FieldXray.jsx:169
-msgid "Statistical overview"
+#: src/metabase/metabot.clj
+msgid "Loading Kanye quotes..."
 msgstr ""
 
-#: frontend/src/metabase/xray/containers/FieldXray.jsx:176
-msgid "Robots"
+#: src/metabase/metabot.clj
+msgid "Evaluating Metabot command:"
 msgstr ""
 
-#: frontend/src/metabase/xray/containers/SegmentXRay.jsx:64
-msgid "Segment"
+#: src/metabase/metabot.clj
+msgid "Go home websocket, you're drunk."
 msgstr ""
 
-#: frontend/src/metabase/xray/containers/TableXRay.jsx:126
-msgid "Fidelity:"
+#: src/metabase/metabot.clj
+msgid "Error launching metabot:"
 msgstr ""
 
-#: frontend/src/metabase/xray/costs.js:8
-msgid "Approximate"
+#: src/metabase/metabot.clj
+msgid "MetaBot WebSocket is closed. Reconnecting now."
 msgstr ""
 
-#: frontend/src/metabase/xray/costs.js:9
-msgid ""
-"Get a sense for this data by looking at a sample.\n"
-"This is faster but less precise."
+#: src/metabase/metabot.clj
+msgid "Error connecting websocket:"
 msgstr ""
 
-#: frontend/src/metabase/xray/costs.js:21
-msgid "Exact"
+#: src/metabase/metabot.clj
+msgid "This instance is performing MetaBot duties."
 msgstr ""
 
-#: frontend/src/metabase/xray/costs.js:22
-msgid ""
-"Go deeper into this data by performing a full scan.\n"
-"This is more precise but slower."
+#: src/metabase/metabot.clj
+msgid "Another instance is already handling MetaBot duties."
 msgstr ""
 
-#: frontend/src/metabase/xray/costs.js:34
-msgid "Extended"
+#: src/metabase/metabot.clj
+msgid "Starting MetaBot threads..."
 msgstr ""
 
-#: frontend/src/metabase/xray/costs.js:35
-msgid ""
-"Adds additional info about this entity by including related objects.\n"
-"This is the slowest but highest fidelity method."
+#: src/metabase/metabot.clj
+msgid "Stopping MetaBot...  🤖"
 msgstr ""
 
-#: frontend/src/metabase/xray/utils.js:7
-msgid "Very different"
+#: src/metabase/metabot.clj
+msgid "MetaBot already running. Killing the previous WebSocket listener first."
 msgstr ""
 
-#: frontend/src/metabase/xray/utils.js:9
-msgid "Somewhat different"
+#: src/metabase/middleware.clj
+msgid "Base-64 encoded public key for this site's SSL certificate."
 msgstr ""
 
-#: frontend/src/metabase/xray/utils.js:11
-msgid "Somewhat similar"
+#: src/metabase/middleware.clj
+msgid "Specify this to enable HTTP Public Key Pinning."
 msgstr ""
 
-#: frontend/src/metabase/xray/utils.js:13
-msgid "Very similar"
+#: src/metabase/middleware.clj
+msgid "See {0} for more information."
 msgstr ""
 
-#: frontend/src/metabase/xray/utils.js:27
-msgid "Generating your x-ray..."
+#: src/metabase/models/card.clj
+msgid "Cannot save Question: source query has circular references."
 msgstr ""
 
-#: frontend/src/metabase/xray/utils.js:28
-#: frontend/src/metabase/xray/utils.js:33
-msgid "Still working..."
+#: src/metabase/models/card.clj src/metabase/models/query/permissions.clj
+#: src/metabase/query_processor/middleware/permissions.clj
+msgid "Card {0} does not exist."
 msgstr ""
 
-#: frontend/src/metabase/xray/utils.js:32
-msgid "Generating your comparison..."
+#: src/metabase/models/card.clj
+msgid ""
+"You do not have permissions to run ad-hoc native queries against Database "
+"{0}."
 msgstr ""
 
-#: src/metabase/api/setup.clj
-msgid "Add a database"
+#: src/metabase/models/collection.clj
+msgid "Invalid color"
 msgstr ""
 
-#: src/metabase/api/setup.clj
-msgid "Get connected"
+#: src/metabase/models/collection.clj
+msgid "must be a valid 6-character hex color code"
 msgstr ""
 
-#: src/metabase/api/setup.clj
-msgid "Connect to your data so your whole team can start to explore."
+#: src/metabase/models/collection.clj
+msgid "Collection name cannot be blank!"
 msgstr ""
 
-#: src/metabase/api/setup.clj
-msgid "Set up email"
+#: src/metabase/models/collection.clj
+msgid "cannot be blank"
 msgstr ""
 
-#: src/metabase/api/setup.clj
-msgid ""
-"Add email credentials so you can more easily invite team members and get "
-"updates via Pulses."
+#: src/metabase/models/collection.clj
+msgid "Invalid Collection location: path is invalid."
 msgstr ""
 
-#: src/metabase/api/setup.clj
-msgid "Set Slack credentials"
+#: src/metabase/models/collection.clj
+msgid "You cannot move a Personal Collection."
 msgstr ""
 
-#: src/metabase/api/setup.clj
-msgid ""
-"Does your team use Slack? If so, you can send automated updates via pulses "
-"and ask questions with MetaBot."
+#: src/metabase/models/collection.clj
+msgid "Invalid Collection location: some or all ancestors do not exist."
 msgstr ""
 
-#: src/metabase/api/setup.clj
-msgid "Invite team members"
+#: src/metabase/models/collection.clj
+msgid "You cannot archive the Root Collection."
 msgstr ""
 
-#: src/metabase/api/setup.clj
-msgid "Share answers and data with the rest of your team."
+#: src/metabase/models/collection.clj
+msgid "You cannot archive a Personal Collection."
 msgstr ""
 
-#: src/metabase/api/setup.clj
-msgid "Hide irrelevant tables"
+#: src/metabase/models/collection.clj
+msgid "You cannot move the Root Collection."
 msgstr ""
 
-#: src/metabase/api/setup.clj
-msgid "Curate your data"
+#: src/metabase/models/collection.clj
+msgid ""
+"You cannot move a Collection into itself or into one of its descendants."
 msgstr ""
 
-#: src/metabase/api/setup.clj
-msgid "If your data contains technical or irrelevant info you can hide it."
+#. first move this Collection
+#: src/metabase/models/collection.clj
+msgid "Moving Collection {0} and its descendants from {1} to {2}"
 msgstr ""
 
-#: src/metabase/api/setup.clj
-msgid "Organize questions"
+#: src/metabase/models/collection.clj
+msgid "You're not allowed to change the owner of a Personal Collection."
 msgstr ""
 
-#: src/metabase/api/setup.clj
-msgid ""
-"Have a lot of saved questions in {0}? Create collections to help manage them "
-"and add context."
+#: src/metabase/models/collection.clj
+msgid "You're not allowed to move a Personal Collection."
 msgstr ""
 
-#: src/metabase/api/setup.clj
-msgid "Metabase"
+#: src/metabase/models/collection.clj
+msgid "You cannot move a Collection and archive it at the same time."
 msgstr ""
 
-#: src/metabase/api/setup.clj
-msgid "Create metrics"
+#: src/metabase/models/collection.clj
+msgid "You cannot delete a Personal Collection!"
 msgstr ""
 
-#: src/metabase/api/setup.clj
-msgid ""
-"Define canonical metrics to make it easier for the rest of your team to get "
-"the right answers."
+#: src/metabase/models/collection.clj
+msgid "{0} {1}''s Personal Collection"
 msgstr ""
 
-#: src/metabase/api/setup.clj
-msgid "Create segments"
+#: src/metabase/models/collection_revision.clj
+msgid "You cannot update a CollectionRevision!"
 msgstr ""
 
-#: src/metabase/api/setup.clj
+#: src/metabase/models/field_values.clj
 msgid ""
-"Keep everyone on the same page by creating canonical sets of filters anyone "
-"can use while asking questions."
+"Field {0} was previously automatically set to show a list widget, but now "
+"has {1} values."
 msgstr ""
 
-#: src/metabase/driver/googleanalytics.clj
-msgid ""
-"You must enable the Google Analytics API. Use this link to go to the Google "
-"Developers Console: {0}"
+#: src/metabase/models/field_values.clj
+msgid "Switching Field to use a search widget instead."
 msgstr ""
 
-#: src/metabase/driver/h2.clj
-msgid ""
-"Running SQL queries against H2 databases using the default (admin) database "
-"user is forbidden."
+#: src/metabase/models/field_values.clj
+msgid "Storing updated FieldValues for Field {0}..."
 msgstr ""
 
-#. CONFIG
-#. TODO - smtp-port should be switched to type :integer
-#: src/metabase/email.clj
-msgid "Email address you want to use as the sender of Metabase."
+#: src/metabase/models/field_values.clj
+msgid "Storing FieldValues for Field {0}..."
 msgstr ""
 
-#: src/metabase/email.clj
-msgid "The address of the SMTP server that handles your emails."
+#: src/metabase/models/humanization.clj
+msgid ""
+"Metabase can attempt to transform your table and field names into more "
+"sensible, human-readable versions, e.g. \"somehorriblename\" becomes \"Some "
+"Horrible Name\"."
 msgstr ""
 
-#: src/metabase/email.clj
-msgid "SMTP username."
+#: src/metabase/models/humanization.clj
+msgid ""
+"This doesn’t work all that well if the names are in a language other than "
+"English, however."
 msgstr ""
 
-#: src/metabase/email.clj
-msgid "SMTP password."
+#: src/metabase/models/humanization.clj
+msgid "Do you want us to take a guess?"
 msgstr ""
 
-#: src/metabase/email.clj
-msgid "The port your SMTP server uses for outgoing emails."
+#: src/metabase/models/permissions.clj
+msgid "You cannot create or revoke permissions for the 'Admin' group."
 msgstr ""
 
-#: src/metabase/email.clj
-msgid "SMTP secure connection protocol. (tls, ssl, starttls, or none)"
+#: src/metabase/models/permissions.clj
+msgid "Invalid permissions object path: ''{0}''."
 msgstr ""
 
-#: src/metabase/email.clj
-msgid "none"
+#: src/metabase/models/permissions.clj
+msgid "You cannot update a permissions entry!"
 msgstr ""
 
-#: src/metabase/email.clj
-msgid "SMTP host is not set."
+#: src/metabase/models/permissions.clj
+msgid "Delete it and create a new one."
 msgstr ""
 
-#: src/metabase/email.clj
-msgid "Failed to send email"
+#: src/metabase/models/permissions.clj
+msgid "Failed to grant permissions: {0}"
+msgstr ""
+
+#: src/metabase/models/permissions.clj
+msgid ""
+"You cannot edit permissions for a Personal Collection or its descendants."
 msgstr ""
 
-#: src/metabase/email.clj
-msgid "Error testing SMTP connection"
+#: src/metabase/models/permissions.clj
+msgid ""
+"Looks like someone else edited the permissions and your data is out of date."
 msgstr ""
 
-#: src/metabase/integrations/ldap.clj
-msgid "Enable LDAP authentication."
+#: src/metabase/models/permissions.clj
+msgid "Please fetch new data and try again."
 msgstr ""
 
-#: src/metabase/integrations/ldap.clj
-msgid "Server hostname."
+#: src/metabase/models/permissions_group.clj
+msgid "Created magic permissions group ''{0}'' (ID = {1})"
 msgstr ""
 
-#: src/metabase/integrations/ldap.clj
-msgid "Server port, usually 389 or 636 if SSL is used."
+#: src/metabase/models/permissions_group.clj
+msgid "A group with that name already exists."
 msgstr ""
 
-#: src/metabase/integrations/ldap.clj
-msgid "Use SSL, TLS or plain text."
+#: src/metabase/models/permissions_group.clj
+msgid "You cannot edit or delete the ''{0}'' permissions group!"
 msgstr ""
 
-#: src/metabase/integrations/ldap.clj
-msgid ""
-"The Distinguished Name to bind as (if any), this user will be used to lookup "
-"information about other users."
+#: src/metabase/models/permissions_group_membership.clj
+msgid "You cannot add or remove users to/from the 'MetaBot' group."
 msgstr ""
 
-#: src/metabase/integrations/ldap.clj
-msgid "The password to bind with for the lookup user."
+#: src/metabase/models/permissions_group_membership.clj
+msgid "You cannot add or remove users to/from the 'All Users' group."
 msgstr ""
 
-#: src/metabase/integrations/ldap.clj
-msgid "Search base for users. (Will be searched recursively)"
+#: src/metabase/models/permissions_group_membership.clj
+msgid "You cannot remove the last member of the 'Admin' group!"
 msgstr ""
 
-#: src/metabase/integrations/ldap.clj
-msgid ""
-"User lookup filter, the placeholder '{login}' will be replaced by the user "
-"supplied login."
+#: src/metabase/models/permissions_revision.clj
+msgid "You cannot update a PermissionsRevision!"
 msgstr ""
 
-#: src/metabase/integrations/ldap.clj
-msgid ""
-"Attribute to use for the user's email. (usually ''mail'', ''email'' or "
-"''userPrincipalName'')"
+#. if there's still not a Card, throw an Exception!
+#: src/metabase/models/pulse.clj
+msgid "Invalid Alert: Alert does not have a Card assoicated with it"
 msgstr ""
 
-#: src/metabase/integrations/ldap.clj
-msgid "Attribute to use for the user''s first name. (usually ''givenName'')"
+#: src/metabase/models/pulse.clj
+msgid "value must be a map with the keys `{0}`, `{1}`, and `{2}`."
 msgstr ""
 
-#: src/metabase/integrations/ldap.clj
-msgid "Attribute to use for the user''s last name. (usually ''sn'')"
+#: src/metabase/models/pulse.clj
+msgid "value must be a map with the following keys `({0})`"
 msgstr ""
 
-#: src/metabase/integrations/ldap.clj
-msgid "Enable group membership synchronization with LDAP."
+#: src/metabase/models/query/permissions.clj
+msgid "Error calculating permissions for query: {0}"
 msgstr ""
 
-#: src/metabase/integrations/ldap.clj
-msgid ""
-"Search base for groups, not required if your LDAP directory provides a "
-"''memberOf'' overlay. (Will be searched recursively)"
+#: src/metabase/models/query/permissions.clj
+msgid "Invalid query type: {0}"
 msgstr ""
 
-#. Should be in the form: {"cn=Some Group,dc=...": [1, 2, 3]} where keys are LDAP groups and values are lists of MB groups IDs
-#: src/metabase/integrations/ldap.clj
-msgid "JSON containing LDAP to Metabase group mappings."
+#: src/metabase/models/query_execution.clj
+msgid "You cannot update a QueryExecution!"
 msgstr ""
 
-#: src/metabase/metabot.clj
-msgid ""
-"Enable MetaBot, which lets you search for and view your saved questions "
-"directly via Slack."
+#: src/metabase/models/revision.clj
+msgid "You cannot update a Revision!"
 msgstr ""
 
-#: src/metabase/metabot.clj
-msgid "Here''s what I can {0}:"
+#: src/metabase/models/setting.clj
+msgid "Setting {0} does not exist.nFound: {1}"
 msgstr ""
 
-#: src/metabase/metabot.clj
-msgid "I don''t know how to {0} `{1}`.n{2}"
+#: src/metabase/models/setting.clj
+msgid "Updating value of settings-last-updated in DB..."
 msgstr ""
 
-#: src/metabase/metabot.clj
-msgid "Uh oh! :cry:n>"
+#. go ahead and log the Exception anyway on the off chance that it *wasn't* just a race condition issue
+#: src/metabase/models/setting.clj
+msgid "Error inserting a new Setting:"
 msgstr ""
 
-#: src/metabase/metabot.clj
-msgid "Here''s your {0} most recent cards:n{1}"
+#: src/metabase/models/setting.clj
+msgid "Checking whether settings cache is out of date (requires DB call)..."
 msgstr ""
 
-#: src/metabase/metabot.clj
+#: src/metabase/models/setting.clj
 msgid ""
-"Could you be a little more specific? I found these cards with names that "
-"matched:n{0}"
+"Settings have been changed on another instance, and will be reloaded here."
 msgstr ""
 
-#: src/metabase/metabot.clj
-msgid "I don''t know what Card `{0}` is. Give me a Card ID or name."
+#: src/metabase/models/setting.clj
+msgid "Refreshing Settings cache..."
 msgstr ""
 
-#: src/metabase/metabot.clj
+#: src/metabase/models/setting.clj
 msgid ""
-"Show which card? Give me a part of a card name or its ID and I can show it "
-"to you. If you don''t know which card you want, try `metabot list`."
+"Invalid value for string: must be either \"true\" or \"false\" (case-"
+"insensitive)."
 msgstr ""
 
-#: src/metabase/metabot.clj
-msgid "Ok, just a second..."
+#: src/metabase/models/setting.clj
+msgid ""
+"You cannot update `settings-last-updated` yourself! This is done "
+"automatically."
 msgstr ""
 
-#: src/metabase/metabot.clj
-msgid "Not Found"
+#: src/metabase/models/setting.clj
+msgid "Assuming Setting already exists in DB and updating existing value."
 msgstr ""
 
-#: src/metabase/metabot.clj
-msgid "Loading Kanye quotes..."
+#: src/metabase/models/user.clj
+msgid "value must be a map with each value either a string or number."
 msgstr ""
 
-#: src/metabase/metabot.clj
-msgid "Evaluating Metabot command:"
+#: src/metabase/plugins.clj
+msgid "Loading plugins in directory {0}..."
 msgstr ""
 
-#: src/metabase/metabot.clj
-msgid "Go home websocket, you're drunk."
+#: src/metabase/plugins.clj
+msgid "Loading plugin {0}... "
 msgstr ""
 
-#: src/metabase/metabot.clj
-msgid "Error launching metabot:"
+#: src/metabase/plugins.clj
+msgid ""
+"It looks like you have some external dependencies in your Metabase plugins "
+"directory."
 msgstr ""
 
-#: src/metabase/metabot.clj
-msgid "MetaBot WebSocket is closed. Reconnecting now."
+#: src/metabase/plugins.clj
+msgid ""
+"With Java 9 or higher, Metabase cannot automatically add them to your "
+"classpath."
 msgstr ""
 
-#: src/metabase/metabot.clj
-msgid "Error connecting websocket:"
+#: src/metabase/plugins.clj
+msgid ""
+"Instead, you should include them at launch with the -cp option. For example:"
 msgstr ""
 
-#: src/metabase/metabot.clj
-msgid "Stopping MetaBot...  🤖"
+#: src/metabase/plugins.clj
+msgid ""
+"See https://metabase.com/docs/latest/operations-guide/start.html#java-"
+"versions for more details."
 msgstr ""
 
-#: src/metabase/metabot.clj
-msgid "MetaBot already running. Killing the previous WebSocket listener first."
+#: src/metabase/plugins.clj
+msgid ""
+"(If you're already running Metabase this way, you can ignore this message.)"
 msgstr ""
 
 #: src/metabase/public_settings.clj
@@ -7483,7 +9287,7 @@ msgid "The maximum size of the cache, per saved question, in kilobytes:"
 msgstr ""
 
 #: src/metabase/public_settings.clj
-msgid "The absoulte maximum time to keep any cached query results, in seconds."
+msgid "The absolute maximum time to keep any cached query results, in seconds."
 msgstr ""
 
 #: src/metabase/public_settings.clj
@@ -7518,6 +9322,42 @@ msgid ""
 "width (in degrees)."
 msgstr ""
 
+#: src/metabase/public_settings/metastore.clj
+msgid "Unable to validate token."
+msgstr ""
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Error fetching token status:"
+msgstr ""
+
+#: src/metabase/public_settings/metastore.clj
+msgid "There was an error checking whether this token was valid."
+msgstr ""
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Token validation timed out."
+msgstr ""
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Invalid token: token isn't in the right format."
+msgstr ""
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Checking with the MetaStore to see whether {0} is valid..."
+msgstr ""
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Token for premium embedding. Go to the MetaStore to get yours!"
+msgstr ""
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Token is valid."
+msgstr ""
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Error setting premium embedding token"
+msgstr ""
+
 #: src/metabase/pulse.clj
 msgid "Unable to compare results to goal for alert."
 msgstr ""
@@ -7534,9 +9374,12 @@ msgstr ""
 msgid "Unrecognized channel type {0}"
 msgstr ""
 
-#. returns `true` if successful -- see JavaDoc
-#: src/metabase/pulse/render.clj
-msgid "No approprate image writer found!"
+#: src/metabase/pulse.clj
+msgid "Error sending notification!"
+msgstr ""
+
+#: src/metabase/pulse/color.clj
+msgid "Can't find JS color selector at ''{0}''"
 msgstr ""
 
 #: src/metabase/pulse/render.clj
@@ -7547,9 +9390,300 @@ msgstr ""
 msgid "Pulse card render error"
 msgstr ""
 
+#: src/metabase/query_processor/middleware/fetch_source_query.clj
+msgid "Trimming trailing comment from card with id {0}"
+msgstr ""
+
+#: src/metabase/query_processor/middleware/parameters/sql.clj
+msgid "Can't find field with ID: {0}"
+msgstr ""
+
+#: src/metabase/query_processor/middleware/parameters/sql.clj
+msgid "''{0}'' is a required param."
+msgstr ""
+
+#: src/metabase/query_processor/middleware/parameters/sql.clj
+msgid "Found ''{0}'' with no terminating ''{1}'' in query ''{2}''"
+msgstr ""
+
+#: src/metabase/query_processor/middleware/parameters/sql.clj
+msgid "Unable to substitute ''{0}'': param not specified.nFound: {1}"
+msgstr ""
+
+#: src/metabase/query_processor/middleware/permissions.clj
+msgid "You do not have permissions to view Card {0}."
+msgstr ""
+
+#: src/metabase/query_processor/middleware/permissions.clj
+msgid "You do not have permissions to run this query."
+msgstr ""
+
+#: src/metabase/sync/analyze.clj
+msgid ""
+"Fingerprint updates attempted {0}, updated {1}, no data found {2}, failed {3}"
+msgstr ""
+
+#: src/metabase/sync/analyze.clj
+msgid "Total number of fields classified {0}, {1} failed"
+msgstr ""
+
+#: src/metabase/sync/analyze.clj
+msgid "Total number of tables classified {0}, {1} updated"
+msgstr ""
+
+#: src/metabase/sync/analyze/fingerprint/fingerprinters.clj
+msgid "Error generating fingerprint for {0}"
+msgstr ""
+
+#: src/metabase/sync/field_values.clj
+msgid "Updated {0} field value sets, created {1}, deleted {2} with {3} errors"
+msgstr ""
+
+#: src/metabase/sync/sync_metadata.clj
+msgid "Total number of fields sync''d {0}, number of fields updated {1}"
+msgstr ""
+
+#: src/metabase/sync/sync_metadata.clj
+msgid "Total number of tables sync''d {0}, number of tables updated {1}"
+msgstr ""
+
+#: src/metabase/sync/sync_metadata.clj
+msgid "Found timezone id {0}"
+msgstr ""
+
+#: src/metabase/sync/sync_metadata.clj
+msgid ""
+"Total number of foreign keys sync''d {0}, {1} updated and {2} tables failed "
+"to update"
+msgstr ""
+
+#: src/metabase/sync/util.clj
+msgid "{0} Database {1} ''{2}''"
+msgstr ""
+
+#: src/metabase/sync/util.clj
+msgid "Table {0} ''{1}''"
+msgstr ""
+
+#: src/metabase/sync/util.clj
+msgid "Field {0} ''{1}''"
+msgstr ""
+
+#: src/metabase/sync/util.clj
+msgid "Field ''{0}''"
+msgstr ""
+
+#: src/metabase/sync/util.clj
+msgid "step ''{0}'' for {1}"
+msgstr ""
+
+#: src/metabase/sync/util.clj
+msgid "Completed {0} on {1}"
+msgstr ""
+
+#: src/metabase/sync/util.clj
+msgid "Start: {0}"
+msgstr ""
+
+#: src/metabase/sync/util.clj
+msgid "End: {0}"
+msgstr ""
+
+#: src/metabase/sync/util.clj
+msgid "Duration: {0}"
+msgstr ""
+
+#: src/metabase/sync/util.clj
+msgid "Completed step ''{0}''"
+msgstr ""
+
+#: src/metabase/task.clj
+msgid "Loading tasks namespace:"
+msgstr ""
+
+#: src/metabase/task.clj
+msgid "Starting Quartz Scheduler"
+msgstr ""
+
+#: src/metabase/task.clj
+msgid "Stopping Quartz Scheduler"
+msgstr ""
+
+#: src/metabase/task.clj
+msgid "Job already exists:"
+msgstr ""
+
 #. 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
 #: src/metabase/util.clj
 msgid "Loading Metabase..."
 msgstr ""
+
+#: src/metabase/util/date.clj
+msgid "Possible timezone conflict found on database {0}."
+msgstr ""
+
+#: src/metabase/util/date.clj
+msgid "JVM timezone is {0} and detected database timezone is {1}."
+msgstr ""
+
+#: src/metabase/util/date.clj
+msgid "Configure a report timezone to ensure proper date and time conversions."
+msgstr ""
+
+#: src/metabase/util/embed.clj
+msgid ""
+"Secret key used to sign JSON Web Tokens for requests to `/api/embed` "
+"endpoints."
+msgstr ""
+
+#: src/metabase/util/encryption.clj
+msgid "MB_ENCRYPTION_SECRET_KEY must be at least 16 characters."
+msgstr ""
+
+#: src/metabase/util/encryption.clj
+msgid "Saved credentials encryption is ENABLED for this Metabase instance."
+msgstr ""
+
+#: src/metabase/util/encryption.clj
+msgid "Saved credentials encryption is DISABLED for this Metabase instance."
+msgstr ""
+
+#: src/metabase/util/encryption.clj
+msgid "nFor more information, see"
+msgstr ""
+
+#: src/metabase/util/encryption.clj
+msgid ""
+"Cannot decrypt encrypted string. Have you changed or forgot to set "
+"MB_ENCRYPTION_SECRET_KEY?"
+msgstr ""
+
+#: src/metabase/util/schema.clj
+msgid "value must be an integer."
+msgstr ""
+
+#: src/metabase/util/schema.clj
+msgid "value must be a string."
+msgstr ""
+
+#: src/metabase/util/schema.clj
+msgid "value must be a boolean."
+msgstr ""
+
+#: src/metabase/util/schema.clj
+msgid "value must be a string that matches the regex `{0}`."
+msgstr ""
+
+#: src/metabase/util/schema.clj
+msgid "value must satisfy one of the following requirements: "
+msgstr ""
+
+#: src/metabase/util/schema.clj
+msgid "value may be nil, or if non-nil, {0}"
+msgstr ""
+
+#: src/metabase/util/schema.clj
+msgid "value must be one of: {0}."
+msgstr ""
+
+#: src/metabase/util/schema.clj
+msgid "value must be an array."
+msgstr ""
+
+#: src/metabase/util/schema.clj
+msgid "Each {0}"
+msgstr ""
+
+#: src/metabase/util/schema.clj
+msgid "The array cannot be empty."
+msgstr ""
+
+#: src/metabase/util/schema.clj
+msgid "value must be a non-blank string."
+msgstr ""
+
+#: src/metabase/util/schema.clj
+msgid "Integer greater than zero"
+msgstr ""
+
+#: src/metabase/util/schema.clj
+msgid "value must be an integer greater than zero."
+msgstr ""
+
+#: src/metabase/util/schema.clj
+msgid "Number greater than zero"
+msgstr ""
+
+#: src/metabase/util/schema.clj
+msgid "value must be a number greater than zero."
+msgstr ""
+
+#: src/metabase/util/schema.clj
+msgid "Keyword or string"
+msgstr ""
+
+#: src/metabase/util/schema.clj
+msgid "Valid field type"
+msgstr ""
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid field type."
+msgstr ""
+
+#: src/metabase/util/schema.clj
+msgid "Valid field type (keyword or string)"
+msgstr ""
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid field type (keyword or string)."
+msgstr ""
+
+#: src/metabase/util/schema.clj
+msgid "Valid entity type (keyword or string)"
+msgstr ""
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid entity type (keyword or string)."
+msgstr ""
+
+#: src/metabase/util/schema.clj
+msgid "Valid map"
+msgstr ""
+
+#: src/metabase/util/schema.clj
+msgid "value must be a map."
+msgstr ""
+
+#: src/metabase/util/schema.clj
+msgid "Valid email address"
+msgstr ""
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid email address."
+msgstr ""
+
+#: src/metabase/util/schema.clj
+msgid "Insufficient password strength"
+msgstr ""
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid integer."
+msgstr ""
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid integer greater than zero."
+msgstr ""
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid boolean string (''true'' or ''false'')."
+msgstr ""
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid JSON string."
+msgstr ""
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid embedding params map."
+msgstr ""
diff --git a/locales/nb.po b/locales/nb.po
new file mode 100644
index 0000000000000000000000000000000000000000..f850ede83e1d24b5fd0630208989185158724e38
--- /dev/null
+++ b/locales/nb.po
@@ -0,0 +1,9311 @@
+msgid ""
+msgstr ""
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: POEditor.com\n"
+"Project-Id-Version: Metabase\n"
+"Language: nb\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: frontend/src/metabase/admin/databases/components/CreatedDatabaseModal.jsx:19
+msgid "Your database has been added!"
+msgstr "Databasen din har blitt lagt til!"
+
+#: frontend/src/metabase/admin/databases/components/CreatedDatabaseModal.jsx:22
+msgid "We took a look at your data, and we have some automated explorations that we can show you!"
+msgstr "Vi har analysert dine data, og vi har noen automatiske utforskninger vi vil vise deg!"
+
+#: frontend/src/metabase/admin/databases/components/CreatedDatabaseModal.jsx:27
+msgid "I'm good thanks"
+msgstr "Jeg har det fint, takk"
+
+#: frontend/src/metabase/admin/databases/components/CreatedDatabaseModal.jsx:32
+msgid "Explore this data"
+msgstr "Utforsk disse dataene"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseEditForms.jsx:42
+msgid "Select a database type"
+msgstr "Velg en databasetype"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseEditForms.jsx:75
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:170
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:435
+#: frontend/src/metabase/admin/permissions/containers/CollectionPermissionsModal.jsx:71
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:182
+#: frontend/src/metabase/components/ActionButton.jsx:51
+#: frontend/src/metabase/components/ButtonWithStatus.jsx:7
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:180
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:197
+#: frontend/src/metabase/reference/components/EditHeader.jsx:54
+#: frontend/src/metabase/reference/components/EditHeader.jsx:69
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:171
+#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:164
+msgid "Save"
+msgstr "Lagre"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:122
+msgid "To do some of its magic, Metabase needs to scan your database. We will also rescan it periodically to keep the metadata up-to-date. You can control when the periodic rescans happen below."
+msgstr "For at Metabase skal klare å gjøre magi, trenger den å skanne databasen din. Vi kommer til å skanne denne periodisk for og holde metadata oppdatert.\n"
+"Du kan kontrollere når dette skal forekomme."
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:127
+msgid "Database syncing"
+msgstr "Synkroniserer database"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:128
+msgid "This is a lightweight process that checks for\n"
+"updates to this database’s schema. In most cases, you should be fine leaving this\n"
+"set to sync hourly."
+msgstr "Dette er en lett prosess som sjekker for oppdatering i database skjemaet. I de fleste tilfeller holder det med synkronisering hver time."
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:147
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:184
+msgid "Scan"
+msgstr "Skann"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:152
+msgid "Scanning for Filter Values"
+msgstr "Skanner etter filtreringsverdier"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:153
+msgid "Metabase can scan the values present in each\n"
+"field in this database to enable checkbox filters in dashboards and questions. This\n"
+"can be a somewhat resource-intensive process, particularly if you have a very large\n"
+"database."
+msgstr "Metabase kan skanne verdiene i hvert felt i denne databasen for og aktivere sjekkboks filtere i paneler og spørsmål. Dette er en ressurs krevende prosess, spesielt for veldig store databaser"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:159
+msgid "When should Metabase automatically scan and cache field values?"
+msgstr "NÃ¥r skal Metabase automatisk skanne og mellomlagre feltverdier?"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:164
+msgid "Regularly, on a schedule"
+msgstr "Regelmessig, etter en tidsplan"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:195
+msgid "Only when adding a new filter widget"
+msgstr "Bare når du legger til en ny filter-widget"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:199
+msgid "When a user adds a new filter to a dashboard or a SQL question, Metabase will\n"
+"scan the field(s) mapped to that filter in order to show the list of selectable values."
+msgstr "Når en bruker legger til nye filter på ett panel eller et SQL spørsmål, vil Metabase skanne feltene som er tilknyttet til det filteret for å vise liste over mulige filterverdier."
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:210
+msgid "Never, I'll do this manually if I need to"
+msgstr "Aldri, jeg vil gjøre det manuelt hvis jeg trenger"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:222
+#: frontend/src/metabase/admin/settings/components/SettingsBatchForm.jsx:27
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:222
+#: frontend/src/metabase/components/ActionButton.jsx:52
+#: frontend/src/metabase/components/ButtonWithStatus.jsx:8
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:429
+msgid "Saving..."
+msgstr "Lagrer..."
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:38
+#: frontend/src/metabase/components/form/FormMessage.jsx:4
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:144
+msgid "Server error encountered"
+msgstr "En tjenerfeil har oppstått"
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:54
+msgid "Delete this database?"
+msgstr "Slett denne databasen?"
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:62
+msgid "All saved questions, metrics, and segments that rely on this database will be lost."
+msgstr "Alle dine lagrede spørsmål, beregninger og segmenter"
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:63
+msgid "This cannot be undone."
+msgstr "Dette kan ikke angres"
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:66
+msgid "If you're sure, please type"
+msgstr "Hvis du er sikker, vennligst skriv"
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:66
+msgid "DELETE"
+msgstr "SLETT"
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:67
+msgid "in this box:"
+msgstr "I denne boksen:"
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:78
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:50
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:87
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:93
+#: frontend/src/metabase/admin/people/components/AddRow.jsx:27
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:250
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:302
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:322
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:343
+#: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:49
+#: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:52
+#: frontend/src/metabase/admin/permissions/containers/CollectionPermissionsModal.jsx:58
+#: frontend/src/metabase/admin/permissions/selectors.js:156
+#: frontend/src/metabase/admin/permissions/selectors.js:166
+#: frontend/src/metabase/admin/permissions/selectors.js:181
+#: frontend/src/metabase/admin/permissions/selectors.js:220
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:355
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:181
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:247
+#: frontend/src/metabase/components/ConfirmContent.jsx:18
+#: frontend/src/metabase/components/DeleteModalWithConfirm.jsx:72
+#: frontend/src/metabase/components/HeaderModal.jsx:49
+#: frontend/src/metabase/components/form/StandardForm.jsx:55
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:196
+#: frontend/src/metabase/dashboard/components/AddSeriesModal.jsx:289
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:162
+#: frontend/src/metabase/dashboard/components/RemoveFromDashboardModal.jsx:42
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:189
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:192
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:352
+#: frontend/src/metabase/query_builder/components/RunButton.jsx:24
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:83
+#: frontend/src/metabase/query_builder/containers/ArchiveQuestionModal.jsx:48
+#: frontend/src/metabase/reference/components/EditHeader.jsx:34
+#: frontend/src/metabase/reference/components/RevisionMessageModal.jsx:52
+#: frontend/src/metabase/visualizations/components/ChartSettings.jsx:209
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:397
+msgid "Cancel"
+msgstr "Kanseler"
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:84
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:123
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:397
+msgid "Delete"
+msgstr "Slett"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:128
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:76
+#: frontend/src/metabase/admin/permissions/selectors.js:316
+#: frontend/src/metabase/admin/permissions/selectors.js:323
+#: frontend/src/metabase/admin/permissions/selectors.js:419
+#: frontend/src/metabase/admin/routes.jsx:39
+#: frontend/src/metabase/nav/containers/Navbar.jsx:215
+#: frontend/src/metabase/reference/databases/DatabaseSidebar.jsx:18
+#: frontend/src/metabase/reference/databases/TableSidebar.jsx:18
+msgid "Databases"
+msgstr "Databaser"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:129
+msgid "Add Database"
+msgstr "Legg til database"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:60
+msgid "Connection"
+msgstr "Forbindelse"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:61
+msgid "Scheduling"
+msgstr "Planlegging"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:170
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:78
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:84
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:253
+#: frontend/src/metabase/admin/settings/components/SettingsBatchForm.jsx:26
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:221
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:182
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:354
+#: frontend/src/metabase/reference/components/RevisionMessageModal.jsx:47
+msgid "Save changes"
+msgstr "Lagre endringer"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:185
+#: frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx:38
+#: frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx:38
+msgid "Actions"
+msgstr "Handlinger"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:193
+msgid "Sync database schema now"
+msgstr "Synkroniser database skjemaer nå"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:194
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:206
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:788
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:796
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:112
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:120
+msgid "Starting…"
+msgstr "Starter..."
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:195
+msgid "Failed to sync"
+msgstr "Feilet ved synkronsering"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:196
+msgid "Sync triggered!"
+msgstr "Synkronisering utløst!"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:205
+msgid "Re-scan field values now"
+msgstr "Skanne felter verdier på nytt"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:207
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:789
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:113
+msgid "Failed to start scan"
+msgstr "Skanning feilet ved start"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:208
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:790
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:114
+msgid "Scan triggered!"
+msgstr "Skanning påbegynt!"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:215
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:399
+msgid "Danger Zone"
+msgstr "Faresone"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:221
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:224
+msgid "Discard saved field values"
+msgstr "Forkast lagrede feltverdier"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:239
+msgid "Remove this database"
+msgstr "Fjern denne databasen"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:75
+msgid "Add database"
+msgstr "Legg til database"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:87
+#: frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx:36
+#: frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx:36
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:468
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:183
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:91
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:402
+#: frontend/src/metabase/containers/EntitySearch.jsx:26
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:218
+#: frontend/src/metabase/entities/collections.js:86
+#: frontend/src/metabase/entities/dashboards.js:96
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:461
+#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:62
+msgid "Name"
+msgstr "Navn"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:88
+msgid "Engine"
+msgstr "Motor"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:117
+msgid "Deleting..."
+msgstr "Sletter..."
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:147
+msgid "Loading ..."
+msgstr "Laster..."
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:163
+msgid "Bring the sample dataset back"
+msgstr "Hent eksempel-datasettet tilbake"
+
+#: frontend/src/metabase/admin/databases/database.js:175
+msgid "Couldn't connect to the database. Please check the connection details."
+msgstr "Kunne ikke koble til databasen. Vennligst sjekk tilkoblingen."
+
+#: frontend/src/metabase/admin/databases/database.js:383
+msgid "Successfully created!"
+msgstr "Opprettelse vellykket!"
+
+#: frontend/src/metabase/admin/databases/database.js:393
+msgid "Successfully saved!"
+msgstr "Lagring vellykket!"
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectActionSelect.jsx:44
+#: frontend/src/metabase/dashboard/components/DashCard.jsx:276
+#: frontend/src/metabase/parameters/components/ParameterWidget.jsx:173
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:209
+#: frontend/src/metabase/reference/components/EditButton.jsx:18
+msgid "Edit"
+msgstr "Rediger"
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectActionSelect.jsx:59
+msgid "Revision History"
+msgstr "Revisjonshistorikk"
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:33
+msgid "Retire this {0}?"
+msgstr "Arkiver denne {0}?"
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:38
+msgid "Saved questions and other things that depend on this {0} will continue to work, but this {1} will no longer be selectable from the query builder."
+msgstr "Lagrede spørsmål og andre ting som avhenger av denne {0} vil fortsette å virke, men denne {1} vil ikke lenger være mulig å velge fra spørrings-byggeren."
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:39
+msgid "If you're sure you want to retire this {0}, please write a quick explanation of why it's being retired:"
+msgstr "Hvis du er usikker på om du vil arkivere denne {0}, vennligst skriv en kort forklaring på hvorfor den blir arkivert:"
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:43
+msgid "This will show up in the activity feed and in an email that will be sent to anyone on your team who created something that uses this {0}."
+msgstr "Dette vil vises i aktivitetsstrømmen og i en e-post som vil bli sendt til alle på ditt lag som lager noe som bruker denne {0}."
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:58
+msgid "Retire"
+msgstr "Arkiver"
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:59
+msgid "Retiring…"
+msgstr "Kaster..."
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:60
+msgid "Failed"
+msgstr "Feilet"
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:61
+msgid "Success"
+msgstr "Suksess"
+
+#: frontend/src/metabase/admin/datamodel/components/PartialQueryBuilder.jsx:118
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:110
+msgid "Preview"
+msgstr "Forhåndsvisning"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnItem.jsx:97
+msgid "No column description yet"
+msgstr "Ingen kolonnebeskrivelse enda"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnItem.jsx:133
+msgid "Select a field visibility"
+msgstr "Velg en feltsynlighet"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnItem.jsx:189
+msgid "No special type"
+msgstr "Ingen spesiell type"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnItem.jsx:190
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:34
+#: frontend/src/metabase/reference/components/Field.jsx:57
+#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:42
+msgid "Other"
+msgstr "Andre"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnItem.jsx:208
+msgid "Select a special type"
+msgstr "Velg en spesiell type"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnItem.jsx:222
+msgid "Select a target"
+msgstr "Velg ett mål"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnsList.jsx:17
+msgid "Columns"
+msgstr "Kolonner"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnsList.jsx:22
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataSchema.jsx:44
+msgid "Column"
+msgstr "Kolonne"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnsList.jsx:24
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:121
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:203
+msgid "Visibility"
+msgstr "Synlighet"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnsList.jsx:25
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:214
+msgid "Type"
+msgstr "Type"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataHeader.jsx:87
+msgid "Current database:"
+msgstr "Nåværende database:"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataHeader.jsx:92
+msgid "Show original schema"
+msgstr "Vis opprinnelige skjema"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataSchema.jsx:45
+msgid "Data Type"
+msgstr "Datatype"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataSchema.jsx:46
+msgid "Additional Info"
+msgstr "Tillegsinfo"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataSchemaList.jsx:44
+msgid "Find a schema"
+msgstr "Fins ett skjema"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataSchemaList.jsx:51
+msgid "{0} schema"
+msgid_plural "{0} schemas"
+msgstr[0] "{0} skjema"
+msgstr[1] "{0} skjemaer"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:82
+msgid "Why Hide?"
+msgstr "Hvorfor skjule?"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:83
+msgid "Technical Data"
+msgstr "Teknisk data"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:84
+msgid "Irrelevant/Cruft"
+msgstr "Irrelevant"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:90
+msgid "Queryable"
+msgstr "Spørbar"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:91
+msgid "Hidden"
+msgstr "Skjul"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:117
+msgid "No table description yet"
+msgstr "Ingen tabell beskrivelse enda"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:124
+msgid "Metadata Strength"
+msgstr "Metadata styrke"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTableList.jsx:87
+msgid "{0} Queryable Table"
+msgid_plural "{0} Queryable Tables"
+msgstr[0] "[0} Spørbar Tabell"
+msgstr[1] "[0} Spørbare Tabeller"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTableList.jsx:96
+msgid "{0} Hidden Table"
+msgid_plural "{0} Hidden Tables"
+msgstr[0] "{0} skjult tabell"
+msgstr[1] "{0} skjulte tabeller"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTableList.jsx:113
+msgid "Find a table"
+msgstr "Find en tabell"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTableList.jsx:126
+msgid "Schemas"
+msgstr "Skjemaer"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx:24
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:137
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:33
+#: frontend/src/metabase/reference/guide/BaseSidebar.jsx:27
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:189
+#: frontend/src/metabase/reference/metrics/MetricList.jsx:56
+#: frontend/src/metabase/reference/metrics/MetricSidebar.jsx:18
+#: frontend/src/metabase/routes.jsx:231
+msgid "Metrics"
+msgstr "Indikatorer"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx:30
+msgid "Add a Metric"
+msgstr "Legg til en indikator"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx:37
+#: frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx:37
+#: frontend/src/metabase/query_builder/components/QueryDefinitionTooltip.jsx:30
+msgid "Definition"
+msgstr "Definisjon"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx:54
+msgid "Create metrics to add them to the View dropdown in the query builder"
+msgstr "Lag indikatorer som vises i nedtrekksmenyen i spørsmålsbyggeren"
+
+#: frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx:24
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:922
+#: frontend/src/metabase/reference/guide/BaseSidebar.jsx:33
+#: frontend/src/metabase/reference/segments/SegmentFieldSidebar.jsx:19
+#: frontend/src/metabase/reference/segments/SegmentList.jsx:56
+#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:18
+msgid "Segments"
+msgstr "Segmenter"
+
+#: frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx:30
+msgid "Add a Segment"
+msgstr "Legg til segment"
+
+#: frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx:54
+msgid "Create segments to add them to the Filter dropdown in the query builder"
+msgstr "Lag segmenter som vises i nedfelsmenyen i spørsmål byggeren"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/Revision.jsx:24
+msgid "created"
+msgstr "laget"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/Revision.jsx:27
+msgid "reverted to a previous version"
+msgstr "vendte tilbake til en tidligere versjon"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/Revision.jsx:33
+msgid "edited the title"
+msgstr "endret tittelen"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/Revision.jsx:35
+msgid "edited the description"
+msgstr "endret beskrivelsen"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/Revision.jsx:37
+msgid "edited the "
+msgstr "edited the "
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/Revision.jsx:40
+msgid "made some changes"
+msgstr "gjorde noen endringer"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/Revision.jsx:46
+#: frontend/src/metabase/home/components/Activity.jsx:80
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:343
+msgid "You"
+msgstr "Du"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/RevisionHistory.jsx:37
+msgid "Datamodel"
+msgstr "Datamodell"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/RevisionHistory.jsx:43
+msgid " History"
+msgstr " Historie"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/RevisionHistory.jsx:48
+msgid "Revision History for"
+msgstr "Revisjons historie for"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:185
+msgid "{0} – Field Settings"
+msgstr "{0} - felt instillinger"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:204
+msgid "Where this field will appear throughout Metabase"
+msgstr "Hvor dette feltet vil dukke opp i Metabase"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:226
+msgid "Filtering on this field"
+msgstr "Filtrering på dette feltet"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:227
+msgid "When this field is used in a filter, what should people use to enter the value they want to filter on?"
+msgstr "Når dette feltet brukes i ett filter, hvilken metode skal brukes for å legge inn filtreringsverdi?"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:334
+msgid "No description for this field yet"
+msgstr "Ingen beskrivelse for dette feltet enda"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:413
+msgid "Original value"
+msgstr "Opprinnelig verdi"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:414
+msgid "Mapped value"
+msgstr "Kartlagt verdi"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:457
+msgid "Enter value"
+msgstr "Legg inn verdi"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:480
+msgid "Use original value"
+msgstr "Bruk opprinnelig verdi"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:481
+msgid "Use foreign key"
+msgstr "Bruk fremmed nøkkel"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:482
+msgid "Custom mapping"
+msgstr "Tilpasset kartlegging"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:510
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:610
+msgid "Unrecognized mapping type"
+msgstr "Ukjent kartleggingstype"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:544
+msgid "Current field isn't a foreign key or FK target table metadata is missing"
+msgstr "Det gjeldende feltet er ikke en fremmednøkkel eller måltabellen mangler FK-metadata."
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:641
+msgid "The selected field isn't a foreign key"
+msgstr "Valgt felt er ikke en fremmed nøkkel"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:703
+msgid "Display values"
+msgstr "Vis verdier"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:704
+msgid "Choose to show the original value from the database, or have this field display associated or custom information."
+msgstr "Velg å vise originalverdien fra databasen, eller å vise tilhørende eller egendefinert informasjon."
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:730
+msgid "Choose a field"
+msgstr "Velg ett felt"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:751
+msgid "Please select a column to use for display."
+msgstr "Vennligst velg en kolonne til visning"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:771
+msgid "Tip:"
+msgstr "Tips:"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:772
+msgid "You might want to update the field name to make sure it still makes sense based on your remapping choices."
+msgstr "Du vil kanskje oppdatere feltnavnet sånn at det fortsatt gir mening dersom du har valgt å bruke verdien fra et annet felt."
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:781
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:105
+msgid "Cached field values"
+msgstr "Mellomlagret felt verdier"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:782
+msgid "Metabase can scan the values for this field to enable checkbox filters in dashboards and questions."
+msgstr "Metabase kan skanne verdiene for dette feltet for å aktivere avhukingsfilter i paneler og spørsmål."
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:787
+msgid "Re-scan this field"
+msgstr "Skan feltet på nytt"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:795
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:119
+msgid "Discard cached field values"
+msgstr "Kast mellomlagrede feltverdier"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:797
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:121
+msgid "Failed to discard values"
+msgstr "Mislyktes å kaste verdier"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:798
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:122
+msgid "Discard triggered!"
+msgstr "Kasting utløst!"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetadataEditorApp.jsx:105
+msgid "Select any table to see its schema and add or edit metadata."
+msgstr "Velg en hvilken som helst tabell for å se tabellens skjema og for å legge til eller endre metadata."
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:37
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:34
+#: frontend/src/metabase/entities/collections.js:89
+msgid "Name is required"
+msgstr "Navn er påkrevd"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:40
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:37
+msgid "Description is required"
+msgstr "Beskrivelse er påkrevd"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:44
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:41
+msgid "Revision message is required"
+msgstr "Revisjonsmelding er påkrevd"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:50
+msgid "Aggregation is required"
+msgstr "Aggregering er påkrevd"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:110
+msgid "Edit Your Metric"
+msgstr "Rediger din indikator"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:111
+msgid "Create Your Metric"
+msgstr "Opprett din indikator"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:115
+msgid "Make changes to your metric and leave an explanatory note."
+msgstr "Gjør endringer i din indikator og legg igjen en forklaring."
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:116
+msgid "You can create saved metrics to add a named metric option to this table. Saved metrics include the aggregation type, the aggregated field, and optionally any filter you add. As an example, you might use this to create something like the official way of calculating \"Average Price\" for an Orders table."
+msgstr "Du kan lage lagrede indikatorer for å legge til en navngitt indikator i denne tabellen. Lagrede indikatorer inkluderer den aggregerte typen, det aggregerte feltet, og alternativt alle filtre du velger å legge til. Som et eksempel; du vil kanskje bruke dette for å lage noe sånt som den offisielle måten å beregne \"gjennomsnittlig pris\" for en ordre-tabell."
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:149
+msgid "Result: "
+msgstr "Resultat: "
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:157
+msgid "Name Your Metric"
+msgstr "Gi navn til din indikator"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:158
+msgid "Give your metric a name to help others find it."
+msgstr "Gi indikatoren din et navn for å hjelpe andre med å finne den."
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:162
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:166
+msgid "Something descriptive but not too long"
+msgstr "Noe beskrivende men ikke for langt"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:166
+msgid "Describe Your Metric"
+msgstr "Beskriv din indikator"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:167
+msgid "Give your metric a description to help others understand what it's about."
+msgstr "Gi din indikator en beskrivelse for å hjelpe andre med å forså hva den handler om."
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:171
+msgid "This is a good place to be more specific about less obvious metric rules"
+msgstr "Dette er et bra sted å være mer spesifikk om mindre åpenbare indikator-regler."
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:175
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:179
+msgid "Reason For Changes"
+msgstr "Grunn Til Endringer"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:177
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:181
+msgid "Leave a note to explain what changes you made and why they were required."
+msgstr "Legg igjen en beskjed for å forklare hva slags endringer du gjorde og hvorfor de måtte gjøres."
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:181
+msgid "This will show up in the revision history for this metric to help everyone remember why things changed"
+msgstr "Dette vil vises i revisjonshistorikken for denne indikatoren for å hjelpe alle med å huske hvorfor ting ble forandret."
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:49
+msgid "At least one filter is required"
+msgstr "Minst ett filter er påkrevd"
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:116
+msgid "Edit Your Segment"
+msgstr "Rediger ditt segment"
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:117
+msgid "Create Your Segment"
+msgstr "Opprett ditt segment"
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:121
+msgid "Make changes to your segment and leave an explanatory note."
+msgstr "Gjør endringer i ditt segment og legg igjen en forklaring."
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:122
+msgid "Select and add filters to create your new segment for the {0} table"
+msgstr "Velg og legg til filtere for å lage ditt nye segment for {0}-tabellen"
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:161
+msgid "Name Your Segment"
+msgstr "Navngi ditt segment"
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:162
+msgid "Give your segment a name to help others find it."
+msgstr "Gi segmentet ditt et navn for å gjøre det enklere for andre å finne det"
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:170
+msgid "Describe Your Segment"
+msgstr "Beskriv ditt segment"
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:171
+msgid "Give your segment a description to help others understand what it's about."
+msgstr "Gi ditt segment en beskrivelse for å hjelpe andre med å forstå hva det dreier seg om."
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:175
+msgid "This is a good place to be more specific about less obvious segment rules"
+msgstr "Dette er et bra sted å være mer spesifikk om mindre åpenbare segmenteringsregler"
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:185
+msgid "This will show up in the revision history for this segment to help everyone remember why things changed"
+msgstr "Dette vil vises i revisjonshistorikken for dette segmentet for å hjelpe alle å huske hvorfor ting ble endret"
+
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:91
+#: frontend/src/metabase/admin/routes.jsx:79
+#: frontend/src/metabase/admin/settings/containers/SettingsEditorApp.jsx:266
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:96
+#: frontend/src/metabase/nav/containers/Navbar.jsx:200
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorSidebar.jsx:99
+msgid "Settings"
+msgstr "Innstillinger"
+
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:106
+msgid "Metabase can scan the values in this table to enable checkbox filters in dashboards and questions."
+msgstr "Metabase kan skanne verdiene i denne tabellen for å lage avkrysningsboks-filtere på infotavlene og i spørsmålene."
+
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:111
+msgid "Re-scan this table"
+msgstr "Skan denne tabellen på nytt"
+
+#: frontend/src/metabase/admin/people/components/AddRow.jsx:34
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:194
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:253
+#: frontend/src/metabase/dashboard/components/DashCard.jsx:276
+msgid "Add"
+msgstr "Legg til"
+
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:80
+#: frontend/src/metabase/setup/components/UserStep.jsx:103
+#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:67
+msgid "Not a valid formatted email address"
+msgstr "Ikke ett gyldig format på e-post adresse"
+
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:135
+#: frontend/src/metabase/setup/components/UserStep.jsx:186
+#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:100
+msgid "First name"
+msgstr "Fornavn"
+
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:156
+#: frontend/src/metabase/setup/components/UserStep.jsx:203
+#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:117
+msgid "Last name"
+msgstr "Etternavn"
+
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:178
+#: frontend/src/metabase/auth/containers/ForgotPasswordApp.jsx:77
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:158
+#: frontend/src/metabase/components/NewsletterForm.jsx:94
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:470
+#: frontend/src/metabase/setup/components/UserStep.jsx:222
+#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:138
+msgid "Email address"
+msgstr "E-post adresse"
+
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:202
+msgid "Permission Groups"
+msgstr "Tillatelsesgrupper"
+
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:238
+msgid "Make this user an admin"
+msgstr "Gjør denne bruker til administrator"
+
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:32
+msgid "All users belong to the {0} group and can't be removed from it. Setting permissions for this group is a great way to\n"
+"make sure you know what new Metabase users will be able to see."
+msgstr "Alle brukere tilhører {0}-gruppen og kan ikke fjernes fra den. Innstilling av rettigheter for denne gruppen er en bra måte å være sikker på at du vet hva nye Metabase-brukere i denne gruppen har lov til å se."
+
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:41
+msgid "This is a special group whose members can see everything in the Metabase instance, and who can access and make changes to the\n"
+"settings in the Admin Panel, including changing permissions! So, add people to this group with care."
+msgstr "Dette er en spesiell gruppe, og dens medlemmer kan se alt i denne Metabase-instansen. Medlemmer av denne gruppen kan også gjøre endringer i administrasjons-panelet, inkludert endring av tillatelser! Så, vær forsiktig når du legger til brukere i denne gruppen."
+
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:45
+msgid "To make sure you don't get locked out of Metabase, there always has to be at least one user in this group."
+msgstr "For å sikre at du ikke blir låst ute av Metabase, må det alltid være igjen en bruker i denne gruppen."
+
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:177
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:219
+msgid "Members"
+msgstr "Medlemmer"
+
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:177
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:470
+#: frontend/src/metabase/admin/settings/selectors.js:107
+#: frontend/src/metabase/lib/core.js:50
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:293
+msgid "Email"
+msgstr "E-post"
+
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:203
+msgid "A group is only as good as its members."
+msgstr "En gruppe er kun like bra som sine medlemmer."
+
+#: frontend/src/metabase/admin/people/components/GroupSummary.jsx:15
+#: frontend/src/metabase/admin/routes.jsx:34
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:50
+msgid "Admin"
+msgstr "Administrator"
+
+#: frontend/src/metabase/admin/people/components/GroupSummary.jsx:16
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:237
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:290
+msgid "and"
+msgstr "og"
+
+#: frontend/src/metabase/admin/people/components/GroupSummary.jsx:19
+#: frontend/src/metabase/admin/people/components/GroupSummary.jsx:31
+msgid "{0} other group"
+msgid_plural "{0} other groups"
+msgstr[0] "{0} annen gruppe"
+msgstr[1] "{0} andre grupper"
+
+#: frontend/src/metabase/admin/people/components/GroupSummary.jsx:37
+msgid "Default"
+msgstr "Standard"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:40
+msgid "Something like \"Marketing\""
+msgstr "Noe sånt som \"markedsføring\""
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:59
+msgid "Remove this group?"
+msgstr "Fjern denne gruppen?"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:61
+msgid "Are you sure? All members of this group will lose any permissions settings they have based on this group.\n"
+"This can't be undone."
+msgstr "Er du sikker? Alle medlemmer av denne gruppen vil miste tilganger fra denne gruppen.\n"
+"Denne kan ikke bli gjennopprettet."
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:72
+#: frontend/src/metabase/components/ConfirmContent.jsx:17
+msgid "Yes"
+msgstr "Ja"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:75
+msgid "No"
+msgstr "Nei"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:93
+msgid "Edit Name"
+msgstr "Rediger navn"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:96
+msgid "Remove Group"
+msgstr "Fjern gruppe"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:139
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:225
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:263
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:367
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:385
+#: frontend/src/metabase/components/HeaderModal.jsx:43
+#: frontend/src/metabase/dashboard/components/AddSeriesModal.jsx:282
+#: frontend/src/metabase/parameters/components/widgets/CategoryWidget.jsx:106
+#: frontend/src/metabase/query_builder/components/AggregationPopover.jsx:298
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:194
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:96
+#: frontend/src/metabase/visualizations/components/ChartSettings.jsx:215
+msgid "Done"
+msgstr "Ferdig"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:219
+msgid "Group name"
+msgstr "Gruppenavn"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:399
+#: frontend/src/metabase/admin/people/containers/AdminPeopleApp.jsx:25
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:477
+#: frontend/src/metabase/admin/routes.jsx:72
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:154
+msgid "Groups"
+msgstr "Grupper"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:400
+msgid "Create a group"
+msgstr "Opprett en gruppe"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:406
+msgid "You can use groups to control your users' access to your data. Put users in groups and then go to the Permissions section to control each group's access. The Administrators and All Users groups are special default groups that can't be removed."
+msgstr "Du kan bruke grupper for og kontrollere din brukers tilgang til dataen. Putt brukere i grupper og endre hver gruppers tilgang under tilgangseksjonen. Gruppene \"Administratorer\" og \"Alle brukere\" er spesielle grupper som ikke kan slettes."
+
+#: frontend/src/metabase/admin/people/components/UserActionsSelect.jsx:79
+msgid "Edit Details"
+msgstr "Rediger detaljer"
+
+#: frontend/src/metabase/admin/people/components/UserActionsSelect.jsx:85
+msgid "Re-send Invite"
+msgstr "Send invitasjon på nytt"
+
+#: frontend/src/metabase/admin/people/components/UserActionsSelect.jsx:90
+msgid "Reset Password"
+msgstr "Tilbakestill passord"
+
+#: frontend/src/metabase/admin/people/components/UserActionsSelect.jsx:97
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:304
+msgid "Deactivate"
+msgstr "Deaktiver"
+
+#: frontend/src/metabase/admin/people/containers/AdminPeopleApp.jsx:24
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:435
+#: frontend/src/metabase/admin/routes.jsx:70
+#: frontend/src/metabase/nav/containers/Navbar.jsx:205
+msgid "People"
+msgstr "Folk"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:192
+msgid "Who do you want to add?"
+msgstr "Hvem vil du legge til?"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:207
+msgid "Edit {0}'s details"
+msgstr "Rediger {0}s detaljer"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:220
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:258
+msgid "{0} has been added"
+msgstr "{0} har blitt lagt til"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:224
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:262
+msgid "Add another person"
+msgstr "Legg til enda en person"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:231
+msgid "We couldn’t send them an email invitation,\n"
+"so make sure to tell them to log in using {0}\n"
+"and this password we’ve generated for them:"
+msgstr "Vi klarte ikke å sende dem en e-post invitasjon, vennligst sørg for at du forklarer at de logger inn med {0} og dette passordet vi har generert for dem:"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:242
+msgid "If you want to be able to send email invites, just go to the {0} page."
+msgstr "Hvis du vil ha muligheten til å sende e-post invitasjoner, gå til denne siden {0}."
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:268
+msgid "We’ve sent an invite to {0} with instructions to set their password."
+msgstr "Vi har sendt ut en invitasjon til {0} med instruksjoner om å sette passordet sitt."
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:283
+msgid "We've re-sent {0}'s invite"
+msgstr "Vi har sendt ut invitasjon på nytt til {0}"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:285
+#: frontend/src/metabase/query_builder/components/SavedQuestionIntroModal.jsx:22
+#: frontend/src/metabase/tutorial/Tutorial.jsx:253
+msgid "Okay"
+msgstr "Okei"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:289
+msgid "Any previous email invites they have will no longer work."
+msgstr "Tidligere sendte invitasjoner vil ikke fungere lenger."
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:300
+msgid "Deactivate {0}?"
+msgstr "Deaktiver {0}?"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:309
+msgid "{0} won't be able to log in anymore."
+msgstr "{0} vil ikke kunne logge inn lenger."
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:320
+msgid "Reactivate {0}'s account?"
+msgstr "Reaktiver {0} sin konto?"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:326
+msgid "Reactivate"
+msgstr "Reaktiver"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:330
+msgid "They'll be able to log in again, and they'll be placed back into the groups they were in before their account was deactivated."
+msgstr "De vil kunne logge inn igjen, de vil da bli plassert tilbake i gruppene de var i før de ble deaktiverte."
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:341
+msgid "Reset {0}'s password?"
+msgstr "Tilbakestill {0} sitt passord?"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:347
+#: frontend/src/metabase/components/form/StandardForm.jsx:71
+msgid "Reset"
+msgstr "Tilbakestill"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:351
+#: frontend/src/metabase/components/ConfirmContent.jsx:13
+#: frontend/src/metabase/dashboard/components/ArchiveDashboardModal.jsx:44
+msgid "Are you sure you want to do this?"
+msgstr "Er du sikker du vil gjøre dette?"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:362
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:384
+msgid "{0}'s password has been reset"
+msgstr "{0} sitt passord har blitt nullstilt"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:371
+msgid "Here’s a temporary password they can use to log in and then change their password."
+msgstr "Her er ett midlertidig passord de kan bruke for å logge inn og bytte passordet."
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:388
+msgid "We've sent them an email with instructions for creating a new password."
+msgstr "Vi har sendt dem en e-post med instruksjoner for å lage ett nytt passord."
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:443
+msgid "Active"
+msgstr "Aktiv"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:444
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:473
+msgid "Deactivated"
+msgstr "Deaktivert"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:459
+msgid "Add someone"
+msgstr "Legg til noen"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:478
+msgid "Last Login"
+msgstr "Siste innlogging"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:501
+msgid "Signed up via Google"
+msgstr "Meldt inn via Google"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:506
+msgid "Signed up via LDAP"
+msgstr "Meldt inn via LDAP"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:518
+msgid "Reactivate this account"
+msgstr "Reaktiver denne kontoen"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:545
+msgid "Never"
+msgstr "Aldri"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:27
+#: frontend/src/metabase/query_builder/components/dataref/MainPane.jsx:24
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} table"
+msgid_plural "{0} tables"
+msgstr[0] "{0} tabell"
+msgstr[1] "{0} tabeller"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:45
+msgid " will be "
+msgstr " vil bli "
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:48
+msgid "given access to"
+msgstr "gitt tilgang til"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:53
+msgid " and "
+msgstr " og "
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:56
+msgid "denied access to"
+msgstr "nektet tilgang til"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:70
+msgid " will no longer be able to "
+msgstr " vil ikke lenger kunne "
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:71
+msgid " will now be able to "
+msgstr "vil nå kunne "
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:79
+msgid " native queries for "
+msgstr " lokal spørring for "
+
+#: frontend/src/metabase/admin/permissions/routes.jsx:12
+#: frontend/src/metabase/nav/containers/Navbar.jsx:220
+msgid "Permissions"
+msgstr "Tillatelser"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:32
+msgid "Save permissions?"
+msgstr "Lagre tillatelser?"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:38
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:161
+msgid "Save Changes"
+msgstr "Lagre endringer"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:44
+msgid "Discard changes?"
+msgstr "Forkast endringer?"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:46
+msgid "No changes to permissions will be made."
+msgstr "Ingen endringer i tillatelser vil bli gjort."
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:65
+msgid "You've made changes to permissions."
+msgstr "Du har gjort endringer med tilganger"
+
+#: frontend/src/metabase/admin/permissions/containers/CollectionPermissionsModal.jsx:52
+msgid "Permissions for this collection"
+msgstr "Tilganger for denne kolleksjonen"
+
+#: frontend/src/metabase/admin/permissions/containers/PermissionsApp.jsx:53
+msgid "You have unsaved changes"
+msgstr "Du har ulagrede endringer"
+
+#: frontend/src/metabase/admin/permissions/containers/PermissionsApp.jsx:54
+msgid "Do you want to leave this page and discard your changes?"
+msgstr "Vil du gå bort fra siden uten å lagre endringene?"
+
+#: frontend/src/metabase/admin/permissions/permissions.js:137
+msgid "Sorry, an error occurred."
+msgstr "Beklager, en feil oppstod."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:59
+msgid "Administrators always have the highest level of access to everything in Metabase."
+msgstr "Administratorer har alltid høyeste tilgangsnivå til alt i Metabase."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:61
+msgid "Every Metabase user belongs to the All Users group. If you want to limit or restrict a group's access to something, make sure the All Users group has an equal or lower level of access."
+msgstr "Hver Metabase bruker tilhører i \"alle brukere\" gruppen. Hvis du vil begrense gruppe tilgang til noe, valider om \"alle brukere\" gruppen har samme eller mindre nivå med tilganger."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:63
+msgid "MetaBot is Metabase's Slack bot. You can choose what it has access to here."
+msgstr "MetaBot er Metabase sin Slack-bot. Du kan velge hva den skal ha tilgang til her."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:115
+msgid "The \"{0}\" group may have access to a different set of {1} than this group, which may give this group additional access to some {2}."
+msgstr "Gruppen {0} kan ha tilgang til et annet sett av {1} enn denne gruppen, som kan gi gruppen tilgang til noe av {2}"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:120
+msgid "The \"{0}\" group has a higher level of access than this, which will override this setting. You should limit or revoke the \"{1}\" group's access to this item."
+msgstr "\"{0}\"-gruppen har et høyere nivå av adgang enn dette, det vil overstyre denne innstillingen. Du bør begrense eller fjerne \"{1}\"-gruppens tilgang til denne ressursen."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:150
+msgid "Limit"
+msgstr "Grense"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:150
+msgid "Revoke"
+msgstr "Fjern"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:152
+msgid "access even though \"{0}\" has greater access?"
+msgstr "tilgang selv om \"{0}\" har høyere tilgangsnivå?"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:155
+#: frontend/src/metabase/admin/permissions/selectors.js:254
+msgid "Limit access"
+msgstr "Tilgangsgrense"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:155
+#: frontend/src/metabase/admin/permissions/selectors.js:219
+#: frontend/src/metabase/admin/permissions/selectors.js:262
+msgid "Revoke access"
+msgstr "Fjern tilgang"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:164
+msgid "Change access to this database to limited?"
+msgstr "Bytt tilgang til denne databasen til begrenset?"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:165
+msgid "Change"
+msgstr "Bytt"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:178
+msgid "Allow Raw Query Writing?"
+msgstr "Tillatt direkte spørringer?"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:179
+msgid "This will also change this group's data access to Unrestricted for this database."
+msgstr "Dette vil også endre denne gruppens data tilgang til ubegrenset for denne databasen."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:180
+msgid "Allow"
+msgstr "Tillatt"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:217
+msgid "Revoke access to all tables?"
+msgstr "Fjern tilgang til alle tabeller?"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:218
+msgid "This will also revoke this group's access to raw queries for this database."
+msgstr "Dette vil også fjerne denne gruppens tilgang til rå spørringer for denne databasen."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:247
+msgid "Grant unrestricted access"
+msgstr "Gi ubegrenset tilgang"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:248
+msgid "Unrestricted access"
+msgstr "Ubegrenset tilgang"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:255
+msgid "Limited access"
+msgstr "Begrenset tilgang"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:263
+msgid "No access"
+msgstr "Ingen tilgang"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:269
+msgid "Write raw queries"
+msgstr "Skriv rå spørringer"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:270
+msgid "Can write raw queries"
+msgstr "Kan skrive direkte spørringer"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:277
+msgid "Curate collection"
+msgstr "Organiser samlingen"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:284
+msgid "View collection"
+msgstr "Vis kolleksjon"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:327
+#: frontend/src/metabase/admin/permissions/selectors.js:423
+#: frontend/src/metabase/admin/permissions/selectors.js:520
+msgid "Data Access"
+msgstr "Data tilgang"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:488
+#: frontend/src/metabase/admin/permissions/selectors.js:645
+#: frontend/src/metabase/admin/permissions/selectors.js:650
+msgid "View tables"
+msgstr "Hvis tabeller"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:586
+msgid "SQL Queries"
+msgstr "SQL spørsmål"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:656
+msgid "View schemas"
+msgstr "Vis skjemaer"
+
+#: frontend/src/metabase/admin/routes.jsx:45
+#: frontend/src/metabase/nav/containers/Navbar.jsx:210
+msgid "Data Model"
+msgstr "Data modell"
+
+#: frontend/src/metabase/admin/settings/components/SettingsAuthenticationOptions.jsx:11
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:118
+msgid "Sign in with Google"
+msgstr "Loginn med Google"
+
+#: frontend/src/metabase/admin/settings/components/SettingsAuthenticationOptions.jsx:13
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:120
+msgid "Allows users with existing Metabase accounts to login with a Google account that matches their email address in addition to their Metabase username and password."
+msgstr "Tillat brukere med eksisterende Metabase konto å logge inn med en Google konto som tilsvarer deres e-postadresse i tillegg til Metabase brukernavn og passord."
+
+#: frontend/src/metabase/admin/settings/components/SettingsAuthenticationOptions.jsx:17
+#: frontend/src/metabase/admin/settings/components/SettingsAuthenticationOptions.jsx:29
+#: frontend/src/metabase/components/ChannelSetupMessage.jsx:32
+msgid "Configure"
+msgstr "Konfigurer"
+
+#: frontend/src/metabase/admin/settings/components/SettingsAuthenticationOptions.jsx:23
+#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:13
+#: frontend/src/metabase/admin/settings/selectors.js:201
+msgid "LDAP"
+msgstr "LDAP"
+
+#: frontend/src/metabase/admin/settings/components/SettingsAuthenticationOptions.jsx:25
+#, fuzzy
+msgid "Allows users within your LDAP directory to log in to Metabase with their LDAP credentials, and allows automatic mapping of LDAP groups to Metabase groups."
+msgstr "Tillat brukere innenfor din LDAP katalog til å logge inn på Metabase med deres LDAP konto, i tillegg til at Metabase kan tildele Metabase grupper som tilsvarer LDAP grupper."
+
+#: frontend/src/metabase/admin/settings/components/SettingsBatchForm.jsx:17
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:69
+#: frontend/src/metabase/admin/settings/selectors.js:154
+msgid "That's not a valid email address"
+msgstr "Det er ikke en gyldig e-postadresse"
+
+#: frontend/src/metabase/admin/settings/components/SettingsBatchForm.jsx:21
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:73
+msgid "That's not a valid integer"
+msgstr "Det er ikke ett heltall"
+
+#: frontend/src/metabase/admin/settings/components/SettingsBatchForm.jsx:28
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:161
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:223
+msgid "Changes saved!"
+msgstr "Endringer lagret!"
+
+#: frontend/src/metabase/admin/settings/components/SettingsBatchForm.jsx:157
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:132
+msgid "Looks like we ran into some problems"
+msgstr "Ser ut som det har oppstått noen problemer"
+
+#: frontend/src/metabase/admin/settings/components/SettingsEmailForm.jsx:12
+msgid "Send test email"
+msgstr "Send test e-post"
+
+#: frontend/src/metabase/admin/settings/components/SettingsEmailForm.jsx:13
+msgid "Sending..."
+msgstr "Sender..."
+
+#: frontend/src/metabase/admin/settings/components/SettingsEmailForm.jsx:14
+msgid "Sent!"
+msgstr "Sendt!"
+
+#: frontend/src/metabase/admin/settings/components/SettingsEmailForm.jsx:82
+msgid "Clear"
+msgstr "Rens"
+
+#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:12
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:113
+#: frontend/src/metabase/admin/settings/selectors.js:196
+msgid "Authentication"
+msgstr "Autentisering"
+
+#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:18
+msgid "Server Settings"
+msgstr "Tjenerinstillinger"
+
+#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:29
+msgid "User Schema"
+msgstr "Bruker skjema"
+
+#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:33
+msgid "Attributes"
+msgstr "Attributter"
+
+#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:42
+msgid "Group Schema"
+msgstr "Gruppe skjema"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSetting.jsx:28
+msgid "Using "
+msgstr "Bruker "
+
+#: frontend/src/metabase/admin/settings/components/SettingsSetupList.jsx:105
+msgid "Getting set up"
+msgstr "Blir satt opp"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSetupList.jsx:106
+msgid "A few things you can do to get the most out of Metabase."
+msgstr "Ett par ting du kan gjøre for å få mest mulig ut av Metabase."
+
+#: frontend/src/metabase/admin/settings/components/SettingsSetupList.jsx:115
+msgid "Recommended next step"
+msgstr "Anbefalt neste steg"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:114
+msgid "Google Sign-In"
+msgstr "Google innlogging"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:123
+msgid "To allow users to sign in with Google you'll need to give Metabase a Google Developers console application client ID. It only takes a few steps and instructions on how to create a key can be found {0}"
+msgstr "For å tillate at brukere logger på via Google må du gi Metabase en Google Developers console application client ID. Det tar kun noen få steg og instruksjoner for å lage en nøkkel kan finnes {0}"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:137
+msgid "Your Google client ID"
+msgstr "Din Google klient ID"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:142
+msgid "Allow users to sign up on their own if their Google account email address is from:"
+msgstr "Tillatt brukere å registrere seg hvis Google-kontoen kommer fra:"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:242
+msgid "Answers sent right to your Slack #channels"
+msgstr "Svar er sendt direkte til din Slack #kanaler"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:251
+msgid "Create a Slack Bot User for MetaBot"
+msgstr "Lag en Slack Bot bruker for MetaBot"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:261
+msgid "Once you're there, give it a name and click {0}. Then copy and paste the Bot API Token into the field below. Once you are done, create a \"metabase_files\" channel in Slack. Metabase needs this to upload graphs."
+msgstr "Når du er der, gi den ett navn og klikk på {0}.\n"
+"Kopier og lim inn Bot API nøkkel i understående felt. Når du er ferdig, lag en \"metabase_files\" kanal i Slack. Metabase trenger denne til å laste opp grafer."
+
+#: frontend/src/metabase/admin/settings/components/SettingsUpdatesForm.jsx:90
+msgid "You're running Metabase {0} which is the latest and greatest!"
+msgstr "Du bruker Metabase {0} som er den siste og beste!"
+
+#: frontend/src/metabase/admin/settings/components/SettingsUpdatesForm.jsx:99
+msgid "Metabase {0} is available.  You're running {1}"
+msgstr "Metabase {0} er tilgjengelig. Du bruker {1}"
+
+#: frontend/src/metabase/admin/settings/components/SettingsUpdatesForm.jsx:112
+#: frontend/src/metabase/components/form/StandardForm.jsx:63
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:96
+msgid "Update"
+msgstr "Oppdater"
+
+#: frontend/src/metabase/admin/settings/components/SettingsUpdatesForm.jsx:116
+msgid "What's Changed:"
+msgstr "Hva som blir endret:"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:131
+msgid "Add a map"
+msgstr "Legg til kart"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:184
+#: frontend/src/metabase/lib/core.js:100
+msgid "URL"
+msgstr "Nettlink"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:199
+msgid "Delete custom map"
+msgstr "Slett tilpasset kart"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:201
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:327
+#: frontend/src/metabase/dashboard/components/RemoveFromDashboardModal.jsx:48
+#: frontend/src/metabase/parameters/components/ParameterWidget.jsx:177
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:104
+msgid "Remove"
+msgstr "Slett"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:226
+#: frontend/src/metabase/dashboard/containers/DashCardCardParameterMapper.jsx:187
+#: frontend/src/metabase/parameters/components/ParameterValueWidget.jsx:241
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:142
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:184
+msgid "Select…"
+msgstr "Velg..."
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:241
+msgid "Sample values:"
+msgstr "Prøve verdier:"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:279
+msgid "Add a new map"
+msgstr "Legg til nytt kart"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:279
+msgid "Edit map"
+msgstr "Endre kart"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:280
+msgid "What do you want to call this map?"
+msgstr "Hva vil du kalle dette kartet?"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:285
+msgid "e.g. United Kingdom, Brazil, Mars"
+msgstr "Eksempel Norge, Europa, MÃ¥nen"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:292
+msgid "URL for the GeoJSON file you want to use"
+msgstr "Nettlink for GeoJSON fil du vil bruke"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:298
+msgid "Like https://my-mb-server.com/maps/my-map.json"
+msgstr "Eksempel https://min-mb-tjener.no/kart/mitt-kart.json"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:309
+#: frontend/src/metabase/query_builder/components/RunButton.jsx:33
+msgid "Refresh"
+msgstr "Oppdater"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:309
+msgid "Load"
+msgstr "Last inn"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:315
+msgid "Which property specifies the region’s identifier?"
+msgstr "Hvilken verdi spesifiserer regionale identifikatorer?"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:324
+msgid "Which property specifies the region’s display name?"
+msgstr "Hvilken verdi spesifiserer regionens visningsnavn?"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:345
+msgid "Load a GeoJSON file to see a preview"
+msgstr "Last inn en GeoJSON-fil for å se en forhåndsvisning"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:363
+msgid "Save map"
+msgstr "Lagre kart"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:363
+msgid "Add map"
+msgstr "Legg til kart"
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLegalese.jsx:7
+msgid "Using embedding"
+msgstr "Bruk innebygging"
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLegalese.jsx:9
+msgid "By enabling embedding you're agreeing to the embedding license located at"
+msgstr "Ved å aktivere innebygging er du enig med innebygging lisensen som fins her"
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLegalese.jsx:19
+msgid "In plain English, when you embed charts or dashboards from Metabase in your own application, that application isn't subject to the Affero General Public License that covers the rest of Metabase, provided you keep the Metabase logo and the \"Powered by Metabase\" visible on those embeds. You should, however, read the license text linked above as that is the actual license that you will be agreeing to by enabling this feature."
+msgstr "Kort oppsummert, når du bygger inn grafer eller paneler fra Metabase i din egen applikasjon, så blir ikke den applikasjonen underlagt \"Affero General Publicus Lovende\" som dekker resten av Metabase, så lenge Metabase-logoen og teksten \"Powered by Metabase\" er synlig på alle innbygginger. Du burde uansett lese selve lisensen lenket til ovenfor ettersom det er den faktiske lisensen som du godtar ved å aktivere denne funksjonen."
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLegalese.jsx:32
+msgid "Enable"
+msgstr "Aktiver"
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLevel.jsx:24
+msgid "Premium embedding enabled"
+msgstr "Premium innebygging aktivert"
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLevel.jsx:26
+msgid "Enter the token you bought from the Metabase Store"
+msgstr "Legg inn nøkkelen du kjøpte fra Metabase butikken"
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLevel.jsx:53
+msgid "Premium embedding lets you disable \"Powered by Metabase\" on your embedded dashboards and questions."
+msgstr "Premium innebygging lar deg inaktiveres \"Powered by Metabase\" på innebygde paneler og spørsmål."
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLevel.jsx:60
+msgid "Buy a token"
+msgstr "Kjøp en nøkkel"
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLevel.jsx:63
+msgid "Enter a token"
+msgstr "Legg inn en nøkkel"
+
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:134
+msgid "Edit Mappings"
+msgstr "Rediger tilordninger"
+
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:140
+msgid "Group Mappings"
+msgstr "Grupper tilordninger"
+
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:147
+msgid "Create a mapping"
+msgstr "Lag en tilordning"
+
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:149
+msgid "Mappings allow Metabase to automatically add and remove users from groups based on the membership information provided by the\n"
+"directory server. Membership to the Admin group can be granted through mappings, but will not be automatically removed as a\n"
+"failsafe measure."
+msgstr "Tilordninger tillater Metabase å automatisk legge til og fjerne brukere fra grupper basert på medlemsinformasjonen i LDAP-tjeneren. Medlemskap i Admin-gruppen kan gies gjennom tilordningene, men vil ikke automatisk fjernes som sikring mot å bli låst ute."
+
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:154
+msgid "Distinguished Name"
+msgstr "Unikt navn"
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:92
+msgid "Public Link"
+msgstr "Offentlig link"
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:93
+msgid "Revoke Link"
+msgstr "Tilbakekall linken"
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:121
+msgid "Disable this link?"
+msgstr "Deaktiver nettlinken?"
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:122
+msgid "They won't work anymore, and can't be restored, but you can create new links."
+msgstr "De vil ikke fungere lenger, og de kan ikke tilbakekalles, men du kan lage nye nettlinker."
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:149
+#, fuzzy
+msgid "Public Dashboard Listing"
+msgstr "Offentlig oppføring av infotavle"
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:152
+msgid "No dashboards have been publicly shared yet."
+msgstr "Ingen paneler har blitt offentlig publisert enda."
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:160
+msgid "Public Card Listing"
+msgstr "Offentlig kortvisning"
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:163
+msgid "No questions have been publicly shared yet."
+msgstr "Ingen spørsmål har blitt offentlig publisert enda "
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:172
+msgid "Embedded Dashboard Listing"
+msgstr "Innebygd panelvisning"
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:173
+msgid "No dashboards have been embedded yet."
+msgstr "Ingen paneler blir brukt som en del av en annen side enda."
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:183
+msgid "Embedded Card Listing"
+msgstr "Innebygd kortvisning"
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:184
+msgid "No questions have been embedded yet."
+msgstr "Ingen spørsmål har blitt innebygd enda."
+
+#: frontend/src/metabase/admin/settings/components/widgets/SecretKeyWidget.jsx:35
+msgid "Regenerate embedding key?"
+msgstr "Lag ny innebyggingsnøkkel?"
+
+#: frontend/src/metabase/admin/settings/components/widgets/SecretKeyWidget.jsx:36
+msgid "This will cause existing embeds to stop working until they are updated with the new key."
+msgstr "Dette vil få eksisterende innebygginger til å stoppe å virke til de er oppdatert med ny nøkkel."
+
+#: frontend/src/metabase/admin/settings/components/widgets/SecretKeyWidget.jsx:39
+msgid "Regenerate key"
+msgstr "Lag nøkkel på nytt"
+
+#: frontend/src/metabase/admin/settings/components/widgets/SecretKeyWidget.jsx:47
+msgid "Generate Key"
+msgstr "Lag nøkkel"
+
+#: frontend/src/metabase/admin/settings/components/widgets/SettingToggle.jsx:11
+#: frontend/src/metabase/admin/settings/selectors.js:77
+#: frontend/src/metabase/admin/settings/selectors.js:86
+msgid "Enabled"
+msgstr "Aktivert"
+
+#: frontend/src/metabase/admin/settings/components/widgets/SettingToggle.jsx:11
+#: frontend/src/metabase/admin/settings/selectors.js:82
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:103
+msgid "Disabled"
+msgstr "Deaktivert"
+
+#: frontend/src/metabase/admin/settings/containers/SettingsEditorApp.jsx:116
+msgid "Unknown setting {0}"
+msgstr "Ukjent innstilling {0}"
+
+#: frontend/src/metabase/admin/settings/selectors.js:22
+msgid "Setup"
+msgstr "Oppsett"
+
+#: frontend/src/metabase/admin/settings/selectors.js:27
+msgid "General"
+msgstr "Generelt"
+
+#: frontend/src/metabase/admin/settings/selectors.js:32
+msgid "Site Name"
+msgstr "Sidenavn"
+
+#: frontend/src/metabase/admin/settings/selectors.js:37
+msgid "Site URL"
+msgstr "Side nettlink"
+
+#: frontend/src/metabase/admin/settings/selectors.js:42
+msgid "Email Address for Help Requests"
+msgstr "E-postadresse for hjelp"
+
+#: frontend/src/metabase/admin/settings/selectors.js:47
+msgid "Report Timezone"
+msgstr "Rapportering tidssone"
+
+#: frontend/src/metabase/admin/settings/selectors.js:50
+msgid "Database Default"
+msgstr "Database standard"
+
+#: frontend/src/metabase/admin/settings/selectors.js:53
+msgid "Select a timezone"
+msgstr "Velg en tidssone"
+
+#: frontend/src/metabase/admin/settings/selectors.js:54
+msgid "Not all databases support timezones, in which case this setting won't take effect."
+msgstr "Ikke alle databaser støtter tidssoner, hvilket gjør at denne innstillingen ikke vil ta effekt."
+
+#: frontend/src/metabase/admin/settings/selectors.js:59
+msgid "Language"
+msgstr "Språk"
+
+#: frontend/src/metabase/admin/settings/selectors.js:64
+msgid "Select a language"
+msgstr "Velg ett språk"
+
+#: frontend/src/metabase/admin/settings/selectors.js:69
+msgid "Anonymous Tracking"
+msgstr "Anonym sporing"
+
+#: frontend/src/metabase/admin/settings/selectors.js:74
+msgid "Friendly Table and Field Names"
+msgstr "Vennlig tabell og feltnavn"
+
+#: frontend/src/metabase/admin/settings/selectors.js:80
+msgid "Only replace underscores and dashes with spaces"
+msgstr "Erstatt understrek og bindestreker med mellomrom"
+
+#: frontend/src/metabase/admin/settings/selectors.js:90
+msgid "Enable Nested Queries"
+msgstr "Aktiver nestede spørringer"
+
+#: frontend/src/metabase/admin/settings/selectors.js:96
+msgid "Updates"
+msgstr "Oppdateringer"
+
+#: frontend/src/metabase/admin/settings/selectors.js:101
+msgid "Check for updates"
+msgstr "Sjekk etter oppdateringer"
+
+#: frontend/src/metabase/admin/settings/selectors.js:112
+msgid "SMTP Host"
+msgstr "SMTP vert"
+
+#: frontend/src/metabase/admin/settings/selectors.js:120
+msgid "SMTP Port"
+msgstr "SMTP port"
+
+#: frontend/src/metabase/admin/settings/selectors.js:124
+#: frontend/src/metabase/admin/settings/selectors.js:224
+msgid "That's not a valid port number"
+msgstr "Det er ikke ett gyldig port nummer"
+
+#: frontend/src/metabase/admin/settings/selectors.js:128
+msgid "SMTP Security"
+msgstr "SMTP sikkerhet"
+
+#: frontend/src/metabase/admin/settings/selectors.js:136
+msgid "SMTP Username"
+msgstr "SMTP brukernavn"
+
+#: frontend/src/metabase/admin/settings/selectors.js:143
+msgid "SMTP Password"
+msgstr "SMTP passord"
+
+#: frontend/src/metabase/admin/settings/selectors.js:150
+msgid "From Address"
+msgstr "Fra e-postadresse"
+
+#: frontend/src/metabase/admin/settings/selectors.js:164
+msgid "Slack API Token"
+msgstr "Slack API nøkkel"
+
+#: frontend/src/metabase/admin/settings/selectors.js:166
+msgid "Enter the token you received from Slack"
+msgstr "Legg inn nøkkelen du fikk fra Slack"
+
+#: frontend/src/metabase/admin/settings/selectors.js:183
+msgid "Single Sign-On"
+msgstr "Singel innlogging"
+
+#: frontend/src/metabase/admin/settings/selectors.js:207
+msgid "LDAP Authentication"
+msgstr "LDAP autorisering"
+
+#: frontend/src/metabase/admin/settings/selectors.js:213
+msgid "LDAP Host"
+msgstr "LDAP vert"
+
+#: frontend/src/metabase/admin/settings/selectors.js:221
+msgid "LDAP Port"
+msgstr "LDAP port"
+
+#: frontend/src/metabase/admin/settings/selectors.js:228
+msgid "LDAP Security"
+msgstr "LDAP sikkerhet"
+
+#: frontend/src/metabase/admin/settings/selectors.js:236
+msgid "Username or DN"
+msgstr "Brukernavn eller DN"
+
+#: frontend/src/metabase/admin/settings/selectors.js:241
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:188
+#: frontend/src/metabase/user/components/UserSettings.jsx:72
+msgid "Password"
+msgstr "Passord"
+
+#: frontend/src/metabase/admin/settings/selectors.js:246
+#, fuzzy
+msgid "User search base"
+msgstr "Bruker søkebase"
+
+#: frontend/src/metabase/admin/settings/selectors.js:252
+msgid "User filter"
+msgstr "Brukerfilter"
+
+#: frontend/src/metabase/admin/settings/selectors.js:258
+msgid "Check your parentheses"
+msgstr "Sjekk dine parenteser"
+
+#: frontend/src/metabase/admin/settings/selectors.js:264
+msgid "Email attribute"
+msgstr "E-post attributter"
+
+#: frontend/src/metabase/admin/settings/selectors.js:269
+msgid "First name attribute"
+msgstr "Fornavn attributter"
+
+#: frontend/src/metabase/admin/settings/selectors.js:274
+msgid "Last name attribute"
+msgstr "Etternavn attributter"
+
+#: frontend/src/metabase/admin/settings/selectors.js:279
+msgid "Synchronize group memberships"
+msgstr "Synkroniser gruppe medlemskap"
+
+#: frontend/src/metabase/admin/settings/selectors.js:285
+msgid "Group search base"
+msgstr "Gruppesøkebase"
+
+#: frontend/src/metabase/admin/settings/selectors.js:294
+msgid "Maps"
+msgstr "Kart"
+
+#: frontend/src/metabase/admin/settings/selectors.js:299
+msgid "Map tile server URL"
+msgstr "URL til tjener for kartfliser"
+
+#: frontend/src/metabase/admin/settings/selectors.js:300
+msgid "Metabase uses OpenStreetMaps by default."
+msgstr "Metabase bruker OpenStreetMaps som standard"
+
+#: frontend/src/metabase/admin/settings/selectors.js:305
+msgid "Custom Maps"
+msgstr "Egendefinerte kart"
+
+#: frontend/src/metabase/admin/settings/selectors.js:306
+msgid "Add your own GeoJSON files to enable different region map visualizations"
+msgstr "Legg til din egen GeoJSON fil for å aktivere forskjellige regionale kartvisualiseringer"
+
+#: frontend/src/metabase/admin/settings/selectors.js:313
+msgid "Public Sharing"
+msgstr "Offentlig deling"
+
+#: frontend/src/metabase/admin/settings/selectors.js:318
+msgid "Enable Public Sharing"
+msgstr "Aktiver offentlig deling"
+
+#: frontend/src/metabase/admin/settings/selectors.js:323
+msgid "Shared Dashboards"
+msgstr "Delte paneler"
+
+#: frontend/src/metabase/admin/settings/selectors.js:329
+msgid "Shared Questions"
+msgstr "Delte spørsmål"
+
+#: frontend/src/metabase/admin/settings/selectors.js:336
+msgid "Embedding in other Applications"
+msgstr "Innebygd i andre applikasjoner"
+
+#: frontend/src/metabase/admin/settings/selectors.js:363
+msgid "Enable Embedding Metabase in other Applications"
+msgstr "Aktiver innebygging av Metabase i andre applikasjoner"
+
+#: frontend/src/metabase/admin/settings/selectors.js:373
+msgid "Embedding secret key"
+msgstr "Hemmelig innebyggingsnøkkel"
+
+#: frontend/src/metabase/admin/settings/selectors.js:379
+msgid "Embedded Dashboards"
+msgstr "Innebygde paneler"
+
+#: frontend/src/metabase/admin/settings/selectors.js:385
+msgid "Embedded Questions"
+msgstr "Innebygde spørsmål"
+
+#: frontend/src/metabase/admin/settings/selectors.js:392
+msgid "Caching"
+msgstr "Mellomlagring"
+
+#: frontend/src/metabase/admin/settings/selectors.js:397
+msgid "Enable Caching"
+msgstr "Aktiver mellomlagring"
+
+#: frontend/src/metabase/admin/settings/selectors.js:402
+msgid "Minimum Query Duration"
+msgstr "Minimum spørringstid"
+
+#: frontend/src/metabase/admin/settings/selectors.js:409
+#, fuzzy
+msgid "Cache Time-To-Live (TTL) multiplier"
+msgstr "Mellomlagre Time-To-Live (TTL) multiplikator"
+
+#: frontend/src/metabase/admin/settings/selectors.js:416
+msgid "Max Cache Entry Size"
+msgstr "Maks mellomlagrings oppføringsstørrelse"
+
+#: frontend/src/metabase/alert/alert.js:60
+msgid "Your alert is all set up."
+msgstr "Dine varsel er satt opp."
+
+#: frontend/src/metabase/alert/alert.js:101
+msgid "Your alert was updated."
+msgstr "Dine varsler er oppdatert."
+
+#: frontend/src/metabase/alert/alert.js:149
+msgid "The alert was successfully deleted."
+msgstr "Varselen ble vellykket slettet."
+
+#: frontend/src/metabase/auth/auth.js:33
+msgid "Please enter a valid formatted email address."
+msgstr "Vennligst skriv inn en gyldig e-postadresse."
+
+#: frontend/src/metabase/auth/auth.js:116
+#: frontend/src/metabase/setup/components/UserStep.jsx:110
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:69
+msgid "Passwords do not match"
+msgstr "Passordene er ikke like"
+
+#: frontend/src/metabase/auth/components/BackToLogin.jsx:6
+msgid "Back to login"
+msgstr "Tilbake til innlogging"
+
+#: frontend/src/metabase/auth/components/GoogleNoAccount.jsx:15
+msgid "No Metabase account exists for this Google account."
+msgstr "Ingen Metabase konto eksisterer for denne Google kontoen."
+
+#: frontend/src/metabase/auth/components/GoogleNoAccount.jsx:17
+msgid "You'll need an administrator to create a Metabase account before you can use Google to log in."
+msgstr "Du må ha en administrator for og lage en Metabase konto før du kan logge inn med Google kontoen."
+
+#: frontend/src/metabase/auth/components/SSOLoginButton.jsx:18
+msgid "Sign in with {0}"
+msgstr "Logg inn med {0}"
+
+#: frontend/src/metabase/auth/containers/ForgotPasswordApp.jsx:56
+msgid "Please contact an administrator to have them reset your password"
+msgstr "Vennligst kontakt en administrator som kan tilbakestille passordet ditt"
+
+#: frontend/src/metabase/auth/containers/ForgotPasswordApp.jsx:69
+msgid "Forgot password"
+msgstr "Glemt passord"
+
+#: frontend/src/metabase/auth/containers/ForgotPasswordApp.jsx:84
+msgid "The email you use for your Metabase account"
+msgstr "E-postadresse du bruker til din Metabase konto"
+
+#: frontend/src/metabase/auth/containers/ForgotPasswordApp.jsx:99
+msgid "Send password reset email"
+msgstr "Send e-post for tilbskestilling av passord"
+
+#: frontend/src/metabase/auth/containers/ForgotPasswordApp.jsx:110
+msgid "Check your email for instructions on how to reset your password."
+msgstr "Sjekk din e-post konto for instruksjoner på hvordan du skal tilbakestille passordet."
+
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:128
+msgid "Sign in to Metabase"
+msgstr "Logg inn på Metabase"
+
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:138
+#, fuzzy
+msgid "OR"
+msgstr "ELLER"
+
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:157
+msgid "Username or email address"
+msgstr "Brukernavn eller e-postadresse"
+
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:217
+msgid "Sign in"
+msgstr "Logg inn"
+
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:230
+msgid "I seem to have forgotten my password"
+msgstr "Jeg har dessverre glemt passordet mitt"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:102
+msgid "request a new reset email"
+msgstr "Be om en ny tilbakestillingsepost"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:120
+msgid "Whoops, that's an expired link"
+msgstr "Oops, nettlinken har utløpt"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:122
+msgid "For security reasons, password reset links expire after a little while. If you still need\n"
+"to reset your password, you can {0}."
+msgstr "På grunn av sikkerhetsmessige grunner, passord tilbakestillingslinker utgår etter en liten stund. Hvis du fortsatt trenger å tilbakestille passordet, kan du {0}."
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:147
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:126
+msgid "New password"
+msgstr "Nytt passord"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:149
+msgid "To keep your data secure, passwords {0}"
+msgstr "For å holde dataen trygg er passordet {0}"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:163
+msgid "Create a new password"
+msgstr "Lag ett nytt passord"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:170
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:141
+msgid "Make sure its secure like the instructions above"
+msgstr "Forsikre deg om at det er sikkert som instruksjonene over"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:184
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:150
+msgid "Confirm new password"
+msgstr "Bekreft nytt passord"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:191
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:159
+msgid "Make sure it matches the one you just entered"
+msgstr "Forsikre deg om at det er likt som det du akkurat skrev inn"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:216
+msgid "Your password has been reset."
+msgstr "Passordet ditt har blitt tilbakestilt."
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:222
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:227
+msgid "Sign in with your new password"
+msgstr "Logg inn med ditt nye passord"
+
+#: frontend/src/metabase/components/ActionButton.jsx:53
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:182
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:184
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:251
+msgid "Save failed"
+msgstr "Lagring feilet"
+
+#: frontend/src/metabase/components/ActionButton.jsx:54
+#: frontend/src/metabase/components/SaveStatus.jsx:60
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:183
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:124
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:185
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:225
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:252
+msgid "Saved"
+msgstr "Lagret"
+
+#: frontend/src/metabase/components/Alert.jsx:12
+msgid "Ok"
+msgstr "Ok"
+
+#: frontend/src/metabase/components/ArchiveCollectionModal.jsx:40
+msgid "Archive this collection?"
+msgstr "Arkiver denne kolleksjonen?"
+
+#: frontend/src/metabase/components/ArchiveCollectionModal.jsx:45
+msgid "The dashboards, collections, and pulses in this collection will also be archived."
+msgstr "Panelene, kolleksjonene og pulser i denne kolleksjonen vil også bli arkivert."
+
+#: frontend/src/metabase/components/ArchiveCollectionModal.jsx:49
+#: frontend/src/metabase/components/CollectionLanding.jsx:587
+#: frontend/src/metabase/components/EntityItem.jsx:54
+#: frontend/src/metabase/components/EntityMenu.info.js:31
+#: frontend/src/metabase/components/EntityMenu.info.js:87
+#: frontend/src/metabase/home/containers/ArchiveApp.jsx:48
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:195
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:200
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:201
+#: frontend/src/metabase/query_builder/containers/ArchiveQuestionModal.jsx:40
+#: frontend/src/metabase/query_builder/containers/ArchiveQuestionModal.jsx:53
+#: frontend/src/metabase/routes.jsx:199
+msgid "Archive"
+msgstr "Arkiv"
+
+#: frontend/src/metabase/containers/ErrorPages.jsx:63
+msgid "This {0} has been archived"
+msgstr "{0} har blitt arkiverr"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:679
+msgid "View the archive"
+msgstr "Se arkivet"
+
+#: frontend/src/metabase/components/ArchivedItem.jsx:39
+msgid "Unarchive this {0}"
+msgstr "Dearkiver {0}"
+
+#: frontend/src/metabase/components/BrowseApp.jsx:93
+#: frontend/src/metabase/components/BrowseApp.jsx:152
+#: frontend/src/metabase/components/BrowseApp.jsx:243
+#: frontend/src/metabase/containers/Overworld.jsx:222
+msgid "Our data"
+msgstr "VÃ¥r data"
+
+#: frontend/src/metabase/components/BrowseApp.jsx:188
+#: frontend/src/metabase/reference/databases/TableSidebar.jsx:51
+msgid "X-ray this table"
+msgstr "Kjør X-ray på denne tabellen"
+
+#: frontend/src/metabase/components/BrowseApp.jsx:201
+#: frontend/src/metabase/containers/Overworld.jsx:249
+msgid "Learn about this table"
+msgstr "Lær mer om denne tabellen"
+
+#: frontend/src/metabase/components/Button.info.js:11
+#: frontend/src/metabase/components/Button.info.js:12
+#: frontend/src/metabase/components/Button.info.js:13
+msgid "Clickity click"
+msgstr "Klikkende klikk"
+
+#: frontend/src/metabase/components/ButtonWithStatus.jsx:9
+msgid "Saved!"
+msgstr "Lagret!"
+
+#: frontend/src/metabase/components/ButtonWithStatus.jsx:10
+msgid "Saving failed."
+msgstr "Lagring feilet."
+
+#: frontend/src/metabase/components/Calendar.jsx:119
+msgid "Su"
+msgstr "Sø"
+
+#: frontend/src/metabase/components/Calendar.jsx:119
+msgid "Mo"
+msgstr "Ma"
+
+#: frontend/src/metabase/components/Calendar.jsx:119
+msgid "Tu"
+msgstr "Ti"
+
+#: frontend/src/metabase/components/Calendar.jsx:119
+msgid "We"
+msgstr "On"
+
+#: frontend/src/metabase/components/Calendar.jsx:119
+msgid "Th"
+msgstr "To"
+
+#: frontend/src/metabase/components/Calendar.jsx:119
+msgid "Fr"
+msgstr "Fr"
+
+#: frontend/src/metabase/components/Calendar.jsx:119
+msgid "Sa"
+msgstr "Lø"
+
+#: frontend/src/metabase/components/ChannelSetupMessage.jsx:41
+msgid "Your admin's email address"
+msgstr "Din administrator e-postadresse"
+
+#: frontend/src/metabase/components/ChannelSetupModal.jsx:37
+msgid "To send {0}, you'll need to set up {1} integration."
+msgstr "For og sende {0} må du sette opp {1} integrasjonen."
+
+#: frontend/src/metabase/components/ChannelSetupModal.jsx:38
+#: frontend/src/metabase/components/ChannelSetupModal.jsx:41
+msgid " or "
+msgstr " eller "
+
+#: frontend/src/metabase/components/ChannelSetupModal.jsx:40
+msgid "To send {0}, an admin needs to set up {1} integration."
+msgstr "For og sende {0} må en administrator sette opp {1} integrasjonen."
+
+#: frontend/src/metabase/components/CollectionEmptyState.jsx:15
+msgid "This collection is empty, like a blank canvas"
+msgstr "Denne kolleksjonen er tom, som ett tomt lerret"
+
+#: frontend/src/metabase/components/CollectionEmptyState.jsx:16
+msgid "You can use collections to organize and group dashboards, questions and pulses for your team or yourself"
+msgstr "Du kan bruke samlinger for å organisere og gruppere infotavler, spørsmål og pulser for ditt lag eller deg selv."
+
+#: frontend/src/metabase/components/CollectionEmptyState.jsx:28
+msgid "Create another collection"
+msgstr "Lag en ny kolleksjon"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:61
+msgid "Dashboards let you collect and share data in one place."
+msgstr "Paneler lar deg samle og dele data fra ett sted."
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:70
+msgid "Pulses let you send out the latest data to your team on a schedule via email or slack."
+msgstr "Pulser lar deg sende ut de siste dataene til ditt lag regelmessig via e-post eller Slack."
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:79
+#, fuzzy
+msgid "Questions are a saved look at your data."
+msgstr "Spørsmål er en lagret visning av dataene dine."
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:275
+#, fuzzy
+msgid "Pins"
+msgstr "Festet"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:329
+msgid "Drag something here to pin it to the top"
+msgstr "Dra noe hit for og feste det til toppen"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:733
+#: frontend/src/metabase/components/CollectionLanding.jsx:341
+#: frontend/src/metabase/home/containers/SearchApp.jsx:35
+#: frontend/src/metabase/home/containers/SearchApp.jsx:96
+msgid "Collections"
+msgstr "Kolleksjoner"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:411
+#: frontend/src/metabase/components/CollectionLanding.jsx:434
+msgid "Drag here to un-pin"
+msgstr "Dra hit for og fjerne festet"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:469
+msgid "{0} item selected"
+msgid_plural "{0} items selected"
+msgstr[0] "{0} element valgt"
+msgstr[1] "{0} elementer valgt"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:487
+msgid "Move {0} items?"
+msgstr "Flytt {0} elementer"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:488
+msgid "Move \"{0}\"?"
+msgstr "Flytt \"{0}\"?"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:594
+#: frontend/src/metabase/components/EntityMenu.info.js:29
+#: frontend/src/metabase/components/EntityMenu.info.js:85
+#: frontend/src/metabase/containers/CollectionMoveModal.jsx:78
+msgid "Move"
+msgstr "Flytt"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:656
+msgid "Edit this collection"
+msgstr "Rediger denne kolleksjonen"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:664
+msgid "Archive this collection"
+msgstr "Arkiver denne kolleksjonen"
+
+#: frontend/src/metabase/components/CollectionList.jsx:64
+#: frontend/src/metabase/entities/collections.js:148
+msgid "My personal collection"
+msgstr "Min personlige kolleksjon"
+
+#: frontend/src/metabase/components/CollectionList.jsx:106
+#: frontend/src/metabase/containers/CollectionForm.jsx:9
+msgid "New collection"
+msgstr "Ny kolleksjon"
+
+#: frontend/src/metabase/components/CopyButton.jsx:35
+msgid "Copied!"
+msgstr "Kopiert!"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:216
+msgid "Use an SSH-tunnel for database connections"
+msgstr "Bruk en SSH-tunnel for database tilkoblinger"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:218
+msgid "Some database installations can only be accessed by connecting through an SSH bastion host.\n"
+"This option also provides an extra layer of security when a VPN is not available.\n"
+"Enabling this is usually slower than a direct connection."
+msgstr "Noen databaseinstallasjoner kan kun kobles til gjennom en SSH bastion-vert.\n"
+"Dette alternativet gir også et ekstra lag med sikkerhet når VPN ikke er tilgjengelig.\n"
+"Det er vanligvis treigere enn en direkte tilkobling."
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:274
+msgid "This is a large database, so let me choose when Metabase syncs and scans"
+msgstr "Dette er en stor database, så la meg velge når Metabase synkroniserer og skanner."
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:276
+msgid "By default, Metabase does a lightweight hourly sync and an intensive daily scan of field values.\n"
+"If you have a large database, we recommend turning this on and reviewing when and how often the field value scans happen."
+msgstr "Som standard kjører Metabase en lettvekts-synkronisering hver time og en intensiv daglig skann av feltverdier.\n"
+"Hvis du har en stor database anbefaler vi at du slår på dette og følger med på når og hvor ofte feltverdi-skanningen skjer."
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:292
+msgid "{0} to generate a Client ID and Client Secret for your project."
+msgstr "{0} for å generere en Klient ID og en Klient Hemmelighet for ditt prosjekt."
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:294
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:321
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:356
+msgid "Click here"
+msgstr "Klikk her"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:297
+msgid "Choose \"Other\" as the application type. Name it whatever you'd like."
+msgstr "Velg \"Annet\" som applikasjonstype. Gi den det navnet du vil."
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:319
+msgid "{0} to get an auth code"
+msgstr "{0} for å få en autentiseringskode"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:331
+msgid "with Google Drive permissions"
+msgstr "Med Google Drive tillatelse"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:351
+msgid "To use Metabase with this data you must enable API access in the Google Developers Console."
+msgstr "For å bruke Metabase med denne dataen må du aktivere API tilgang i Google Developers Console. "
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:354
+msgid "{0} to go to the console if you haven't already done so."
+msgstr "{0} for å gå til konsollen hvis du ikke allerede har gjort det."
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:403
+msgid "How would you like to refer to this database?"
+msgstr "Hvordan vil du referere til denne databasen?"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:430
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:97
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:240
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:188
+#: frontend/src/metabase/setup/components/DatabaseSchedulingStep.jsx:74
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:116
+#: frontend/src/metabase/setup/components/UserStep.jsx:308
+msgid "Next"
+msgstr "Neste"
+
+#: frontend/src/metabase/components/DeleteModalWithConfirm.jsx:80
+msgid "Delete this {0}"
+msgstr "Slett denne {0}"
+
+#: frontend/src/metabase/components/EntityItem.jsx:42
+msgid "Pin this item"
+msgstr "Fest dette elementet"
+
+#: frontend/src/metabase/components/EntityItem.jsx:48
+msgid "Move this item"
+msgstr "Flytt dette elementet"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:24
+#: frontend/src/metabase/components/EntityMenu.info.js:80
+msgid "Edit this question"
+msgstr "Rediger dette spørsmålet"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:26
+#: frontend/src/metabase/components/EntityMenu.info.js:47
+#: frontend/src/metabase/components/EntityMenu.info.js:82
+#: frontend/src/metabase/components/EntityMenu.info.js:99
+msgid "Action type"
+msgstr "Handlingstype"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:28
+#: frontend/src/metabase/components/EntityMenu.info.js:84
+msgid "View revision history"
+msgstr "Vis revisjonshistorikk"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:29
+#: frontend/src/metabase/components/EntityMenu.info.js:85
+msgid "Move action"
+msgstr "Flytt handling"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:33
+#: frontend/src/metabase/components/EntityMenu.info.js:89
+msgid "Archive action"
+msgstr "Arkiver handling"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:45
+#: frontend/src/metabase/components/EntityMenu.info.js:97
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:329
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:342
+msgid "Add to dashboard"
+msgstr "Legg til panel"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:49
+#: frontend/src/metabase/components/EntityMenu.info.js:101
+msgid "Download results"
+msgstr "Last ned resultater"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:51
+#: frontend/src/metabase/components/EntityMenu.info.js:103
+#: frontend/src/metabase/public/components/widgets/EmbedWidget.jsx:52
+msgid "Sharing and embedding"
+msgstr "Deling og innebygging"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:53
+#: frontend/src/metabase/components/EntityMenu.info.js:105
+msgid "Another action type"
+msgstr "Andre handlingstyper"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:65
+#: frontend/src/metabase/components/EntityMenu.info.js:67
+#: frontend/src/metabase/components/EntityMenu.info.js:113
+#: frontend/src/metabase/components/EntityMenu.info.js:115
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:449
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:454
+msgid "Get alerts about this"
+msgstr "FÃ¥ varsler om dette"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:69
+#: frontend/src/metabase/components/EntityMenu.info.js:117
+msgid "View the SQL"
+msgstr "Vis SQL'en"
+
+#: frontend/src/metabase/components/EntitySegments.jsx:18
+msgid "Segments for this"
+msgstr "Segmenter for dette"
+
+#: frontend/src/metabase/components/ErrorDetails.jsx:20
+msgid "Show error details"
+msgstr "Vis feil detaljer"
+
+#: frontend/src/metabase/components/ErrorDetails.jsx:26
+msgid "Here's the full error message"
+msgstr "Her er hele feilmeldingen"
+
+#: frontend/src/metabase/components/ExplorePane.jsx:19
+msgid "Hi, Metabot here."
+msgstr "Hei, MetaBot her."
+
+#: frontend/src/metabase/components/ExplorePane.jsx:95
+msgid "Based on the schema"
+msgstr "Basert på skjemaet"
+
+#: frontend/src/metabase/components/ExplorePane.jsx:174
+#, fuzzy
+msgid "A look at your"
+msgstr "En kikk på din"
+
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:234
+msgid "Search the list"
+msgstr "Søk i listen"
+
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:238
+msgid "Search by {0}"
+msgstr "Søk med {0}"
+
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:240
+msgid " or enter an ID"
+msgstr " eller skriv inn en ID"
+
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:244
+msgid "Enter an ID"
+msgstr "Skriv inn en ID"
+
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:246
+msgid "Enter a number"
+msgstr "Skriv inn et tall"
+
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:248
+msgid "Enter some text"
+msgstr "Skriv inn tekst"
+
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:355
+msgid "No matching {0} found."
+msgstr "Ingen funn på {0}."
+
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:363
+msgid "Including every option in your filter probably won’t do much…"
+msgstr "Inkluderer hver mulighet i filteret ditt vil mest sannsynlig ikke gjøre mye."
+
+#: frontend/src/metabase/containers/ErrorPages.jsx:24
+msgid "Something's gone wrong"
+msgstr "Noe gikk galt"
+
+#: frontend/src/metabase/containers/ErrorPages.jsx:25
+msgid "We've run into an error. You can try refreshing the page, or just go back."
+msgstr "Vi har støtt på en feil. Du kan prøve å oppdatere siden, eller bare gå tilbake."
+
+#: frontend/src/metabase/components/Header.jsx:97
+#: frontend/src/metabase/components/HeaderBar.jsx:45
+#: frontend/src/metabase/components/ListItem.jsx:37
+#: frontend/src/metabase/reference/components/Detail.jsx:47
+#: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:158
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:213
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:191
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:205
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:209
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:209
+msgid "No description yet"
+msgstr "Ingen beskrivelse enda"
+
+#: frontend/src/metabase/components/Header.jsx:112
+msgid "New {0}"
+msgstr "Ny {0}"
+
+#: frontend/src/metabase/components/Header.jsx:123
+msgid "Asked by {0}"
+msgstr "Spurt av {0}"
+
+#: frontend/src/metabase/components/HistoryModal.jsx:13
+msgid "Today, "
+msgstr "Idag, "
+
+#: frontend/src/metabase/components/HistoryModal.jsx:15
+msgid "Yesterday, "
+msgstr "Igår, "
+
+#: frontend/src/metabase/components/HistoryModal.jsx:68
+msgid "First revision."
+msgstr "Første revisjon"
+
+#: frontend/src/metabase/components/HistoryModal.jsx:70
+msgid "Reverted to an earlier revision and {0}"
+msgstr "Reversert til en tidligere revisjon og {0}"
+
+#: frontend/src/metabase/components/HistoryModal.jsx:82
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:289
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:379
+#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:54
+msgid "Revision history"
+msgstr "Revisjonshistorikk"
+
+#: frontend/src/metabase/components/HistoryModal.jsx:90
+msgid "When"
+msgstr "NÃ¥r"
+
+#: frontend/src/metabase/components/HistoryModal.jsx:91
+msgid "Who"
+msgstr "Hvem"
+
+#: frontend/src/metabase/components/HistoryModal.jsx:92
+msgid "What"
+msgstr "Hva"
+
+#: frontend/src/metabase/components/HistoryModal.jsx:113
+msgid "Revert"
+msgstr "Tilbakestilt"
+
+#: frontend/src/metabase/components/HistoryModal.jsx:114
+msgid "Reverting…"
+msgstr "Reverserer..."
+
+#: frontend/src/metabase/components/HistoryModal.jsx:115
+msgid "Revert failed"
+msgstr "Reversering feilet"
+
+#: frontend/src/metabase/components/HistoryModal.jsx:116
+msgid "Reverted"
+msgstr "Reversert"
+
+#: frontend/src/metabase/components/ItemTypeFilterBar.jsx:13
+msgid "Everything"
+msgstr "Alt"
+
+#: frontend/src/metabase/components/ItemTypeFilterBar.jsx:18
+#: frontend/src/metabase/home/containers/SearchApp.jsx:73
+msgid "Dashboards"
+msgstr "Paneler"
+
+#: frontend/src/metabase/components/ItemTypeFilterBar.jsx:23
+#: frontend/src/metabase/home/containers/SearchApp.jsx:119
+msgid "Questions"
+msgstr "Spørsmål"
+
+#: frontend/src/metabase/components/ItemTypeFilterBar.jsx:28
+#: frontend/src/metabase/routes.jsx:320
+msgid "Pulses"
+msgstr "Pulser"
+
+#: frontend/src/metabase/components/LeftNavPane.jsx:36
+#: frontend/src/metabase/query_builder/components/dataref/DataReference.jsx:86
+msgid "Back"
+msgstr "Tilbake"
+
+#: frontend/src/metabase/components/ListSearchField.jsx:18
+msgid "Find..."
+msgstr "Finn..."
+
+#: frontend/src/metabase/components/LoadingAndErrorWrapper.jsx:48
+msgid "An error occured"
+msgstr "En feil oppstod"
+
+#: frontend/src/metabase/components/LoadingAndErrorWrapper.jsx:35
+msgid "Loading..."
+msgstr "Laster inn..."
+
+#: frontend/src/metabase/components/NewsletterForm.jsx:71
+msgid "Metabase Newsletter"
+msgstr "Metabase nyhetsbrev"
+
+#: frontend/src/metabase/components/NewsletterForm.jsx:81
+msgid "Get infrequent emails about new releases and feature updates."
+msgstr "Motta epost om nye utgaver og oppdaterte funksjoner."
+
+#: frontend/src/metabase/components/NewsletterForm.jsx:99
+msgid "Subscribe"
+msgstr "Abonnere"
+
+#: frontend/src/metabase/components/NewsletterForm.jsx:106
+msgid "You're subscribed. Thanks for using Metabase!"
+msgstr "Du har abonnert. Takk for at du bruker Metabase!"
+
+#: frontend/src/metabase/containers/ErrorPages.jsx:44
+msgid "We're a little lost..."
+msgstr "Vi er litt fortapt..."
+
+#: frontend/src/metabase/components/PasswordReveal.jsx:27
+msgid "Temporary Password"
+msgstr "Midlertidig passord"
+
+#: frontend/src/metabase/components/PasswordReveal.jsx:68
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:334
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:349
+msgid "Hide"
+msgstr "Skjul"
+
+#: frontend/src/metabase/components/PasswordReveal.jsx:68
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:335
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:350
+msgid "Show"
+msgstr "Vis"
+
+#: frontend/src/metabase/components/QuestionSavedModal.jsx:17
+msgid "Saved! Add this to a dashboard?"
+msgstr "Lagret! Vil du legge den til ett panel?"
+
+#: frontend/src/metabase/components/QuestionSavedModal.jsx:25
+msgid "Yes please!"
+msgstr "Ja takk!"
+
+#: frontend/src/metabase/components/QuestionSavedModal.jsx:29
+msgid "Not now"
+msgstr "Ikke nå"
+
+#: frontend/src/metabase/components/SaveStatus.jsx:53
+msgid "Error:"
+msgstr "Feil:"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:23
+msgid "Sunday"
+msgstr "Søndag"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:24
+msgid "Monday"
+msgstr "Mandag"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:25
+msgid "Tuesday"
+msgstr "Tirsdag"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:26
+msgid "Wednesday"
+msgstr "Onsdag"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:27
+msgid "Thursday"
+msgstr "Torsdag"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:28
+msgid "Friday"
+msgstr "Fredag"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:29
+msgid "Saturday"
+msgstr "Lørdag"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:33
+msgid "First"
+msgstr "Først"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:34
+msgid "Last"
+msgstr "Sist"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:35
+msgid "15th (Midpoint)"
+msgstr "Femtende (midten)"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:125
+msgid "Calendar Day"
+msgstr "Kalenderdag"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:210
+msgid "your Metabase timezone"
+msgstr "din Metabase tidssone"
+
+#: frontend/src/metabase/components/SearchHeader.jsx:21
+msgid "Filter this list..."
+msgstr "Filtrer denne listen..."
+
+#: frontend/src/metabase/components/Select.info.js:8
+msgid "Blue"
+msgstr "Blå"
+
+#: frontend/src/metabase/components/Select.info.js:9
+msgid "Green"
+msgstr "Grønn"
+
+#: frontend/src/metabase/components/Select.info.js:10
+msgid "Red"
+msgstr "Rød"
+
+#: frontend/src/metabase/components/Select.info.js:11
+msgid "Yellow"
+msgstr "Gul"
+
+#: frontend/src/metabase/components/Select.info.js:14
+msgid "A component used to make a selection"
+msgstr "En komponent bruker dette valget"
+
+#: frontend/src/metabase/components/Select.info.js:20
+#: frontend/src/metabase/components/Select.info.js:28
+msgid "Selected"
+msgstr "Valgt"
+
+#: frontend/src/metabase/components/Select.jsx:281
+msgid "Nothing to select"
+msgstr "Ingenting å velge"
+
+#: frontend/src/metabase/containers/ErrorPages.jsx:54
+msgid "Sorry, you don’t have permission to see that."
+msgstr "Beklager, du har ikke tilgang til å se dette."
+
+#: frontend/src/metabase/components/form/FormMessage.jsx:5
+msgid "Unknown error encountered"
+msgstr "Ukjent feil oppstod"
+
+#: frontend/src/metabase/components/form/StandardForm.jsx:63
+#: frontend/src/metabase/nav/containers/Navbar.jsx:300
+msgid "Create"
+msgstr "Lag"
+
+#: frontend/src/metabase/containers/DashboardForm.jsx:9
+msgid "Create dashboard"
+msgstr "Lag panel"
+
+#: frontend/src/metabase/containers/EntitySearch.jsx:35
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:324
+#: frontend/src/metabase/visualizations/visualizations/Table.jsx:44
+msgid "Table"
+msgstr "Tabell"
+
+#: frontend/src/metabase/containers/EntitySearch.jsx:42
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:299
+msgid "Database"
+msgstr "Database"
+
+#: frontend/src/metabase/containers/EntitySearch.jsx:49
+msgid "Creator"
+msgstr "Skaper"
+
+#: frontend/src/metabase/containers/EntitySearch.jsx:238
+msgid "No results found"
+msgstr "Ingen resultater funnet"
+
+#: frontend/src/metabase/containers/EntitySearch.jsx:239
+msgid "Try adjusting your filter to find what you’re looking for."
+msgstr "Prøv å justere dine filter for å finne hva du leter etter."
+
+#: frontend/src/metabase/containers/EntitySearch.jsx:258
+msgid "View by"
+msgstr "Vis med"
+
+#: frontend/src/metabase/containers/EntitySearch.jsx:494
+#: frontend/src/metabase/query_builder/components/AggregationWidget.jsx:69
+#: frontend/src/metabase/tutorial/TutorialModal.jsx:34
+msgid "of"
+msgstr "av"
+
+#: frontend/src/metabase/containers/Overworld.jsx:78
+msgid "Don't tell anyone, but you're my favorite."
+msgstr "Ikke si det til noen, men du er min favoritt."
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:85
+msgid "Once you connect your own data, I can show you some automatic explorations called x-rays. Here are some examples with sample data."
+msgstr "Når du kobler til dine data, kan jeg gjøre en automatisk utforsking kalt X-ray. Her er noen eksempler med test-data."
+
+#: frontend/src/metabase/containers/Overworld.jsx:131
+#: frontend/src/metabase/containers/Overworld.jsx:302
+#: frontend/src/metabase/reference/components/GuideHeader.jsx:12
+msgid "Start here"
+msgstr "Start her"
+
+#: frontend/src/metabase/containers/Overworld.jsx:297
+#: frontend/src/metabase/entities/collections.js:140
+#: src/metabase/models/collection.clj
+msgid "Our analytics"
+msgstr "VÃ¥r analyse"
+
+#: frontend/src/metabase/containers/Overworld.jsx:206
+msgid "Browse all items"
+msgstr "Bla gjennom alle elementer"
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:165
+msgid "Replace or save as new?"
+msgstr "Erstatt eller lagre som ny?"
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:173
+msgid "Replace original question, \"{0}\""
+msgstr "Erstatt det opprinnelige spørsmålet, \"{0}\""
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:178
+msgid "Save as new question"
+msgstr "Lagre som nytt spørsmål"
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:187
+msgid "First, save your question"
+msgstr "Først, lagre spørsmålet ditt"
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:188
+msgid "Save question"
+msgstr "Lagre spørsmålet"
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:224
+msgid "What is the name of your card?"
+msgstr "Hva er navnet på kortet ditt?"
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:232
+#: frontend/src/metabase/entities/collections.js:94
+#: frontend/src/metabase/entities/dashboards.js:102
+#: frontend/src/metabase/lib/core.js:45 frontend/src/metabase/lib/core.js:200
+#: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:156
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:211
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:189
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:203
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:207
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:207
+#: frontend/src/metabase/visualizations/lib/settings.js:163
+msgid "Description"
+msgstr "Beskrivelse"
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:238
+#: frontend/src/metabase/entities/dashboards.js:104
+msgid "It's optional but oh, so helpful"
+msgstr "Det er valgfritt, men veldig hjelpsomt"
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:245
+#: frontend/src/metabase/entities/dashboards.js:108
+msgid "Which collection should this go in?"
+msgstr "Hvilken kolleksjon skal denne være i?"
+
+#: frontend/src/metabase/containers/UndoListing.jsx:34
+msgid "modified"
+msgstr "modifiert"
+
+#: frontend/src/metabase/containers/UndoListing.jsx:34
+msgid "item"
+msgstr "element"
+
+#: frontend/src/metabase/containers/UndoListing.jsx:81
+msgid "Undo"
+msgstr "Angre"
+
+#: frontend/src/metabase/dashboard/components/AddSeriesModal.jsx:270
+msgid "Applying Question"
+msgstr "Bruk spørsmål"
+
+#: frontend/src/metabase/dashboard/components/AddSeriesModal.jsx:274
+msgid "That question isn't compatible"
+msgstr "Det spørsmålet er ikke kompatibelt"
+
+#: frontend/src/metabase/dashboard/components/AddSeriesModal.jsx:310
+msgid "Search for a question"
+msgstr "Søk etter et spørsmål"
+
+#: frontend/src/metabase/dashboard/components/AddSeriesModal.jsx:339
+msgid "We're not sure if this question is compatible"
+msgstr "Vi er ikke sikker på om dette spørsmålet kompatibelt"
+
+#: frontend/src/metabase/dashboard/components/ArchiveDashboardModal.jsx:43
+msgid "Archive Dashboard"
+msgstr "Arkiver panel"
+
+#: frontend/src/metabase/dashboard/components/DashCardParameterMapper.jsx:20
+msgid "Make sure to make a selection for each series, or the filter won't work on this card."
+msgstr "Forsikre deg om at noe i hver serie er valgt, ellers vil ikke filteret virke på dette kortet."
+
+#: frontend/src/metabase/dashboard/components/Dashboard.jsx:284
+msgid "This dashboard is looking empty."
+msgstr "Dette panelet ser ut som det er tomt."
+
+#: frontend/src/metabase/dashboard/components/Dashboard.jsx:287
+msgid "Add a question to start making it useful!"
+msgstr "Legg til ett spørsmål for og gjøre det nyttig!"
+
+#: frontend/src/metabase/dashboard/components/DashboardActions.jsx:36
+msgid "Daytime mode"
+msgstr "Dagmodus"
+
+#: frontend/src/metabase/dashboard/components/DashboardActions.jsx:36
+msgid "Nighttime mode"
+msgstr "Nattmodus"
+
+#: frontend/src/metabase/dashboard/components/DashboardActions.jsx:53
+msgid "Exit fullscreen"
+msgstr "Lukk fullskjerm"
+
+#: frontend/src/metabase/dashboard/components/DashboardActions.jsx:53
+msgid "Enter fullscreen"
+msgstr "Ã…pne fullskjerm"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:181
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:183
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:250
+msgid "Saving…"
+msgstr "Lagrer..."
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:216
+msgid "Add a question"
+msgstr "Legg til spørsmål"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:219
+msgid "Add a question to this dashboard"
+msgstr "Legg til spørsmål til dette panelet"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:248
+msgid "Add a filter"
+msgstr "Legg til filter"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:254
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:78
+msgid "Parameters"
+msgstr "Parametere"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:275
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:279
+msgid "Add a text box"
+msgstr "Legg til en tekst boks"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:301
+msgid "Move dashboard"
+msgstr "Flytt panel"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:313
+msgid "Edit dashboard"
+msgstr "Rediger panel"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:317
+msgid "Edit Dashboard Layout"
+msgstr "Rediger panelvisning"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:352
+msgid "You are editing a dashboard"
+msgstr "Du redigerer ett panel"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:357
+msgid "Select the field that should be filtered for each card"
+msgstr "Velg feltet som skal være filtrert for hvert kort"
+
+#: frontend/src/metabase/dashboard/components/DashboardMoveModal.jsx:28
+msgid "Move dashboard to..."
+msgstr "Flytt panel til..."
+
+#: frontend/src/metabase/dashboard/components/DashboardMoveModal.jsx:52
+msgid "Dashboard moved to {0}"
+msgstr "Panelet ble flyttet til {0}"
+
+#: frontend/src/metabase/dashboard/components/ParametersPopover.jsx:82
+msgid "What do you want to filter?"
+msgstr "Hva vil du filtrere?"
+
+#: frontend/src/metabase/dashboard/components/ParametersPopover.jsx:115
+msgid "What kind of filter?"
+msgstr "Hva slags filter?"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:13
+msgid "Off"
+msgstr "Av"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:14
+msgid "1 minute"
+msgstr "1 minutt"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:15
+msgid "5 minutes"
+msgstr "5 minutter"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:16
+msgid "10 minutes"
+msgstr "10 minutter"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:17
+msgid "15 minutes"
+msgstr "15 minutter"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:18
+msgid "30 minutes"
+msgstr "30 minutter"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:19
+msgid "60 minutes"
+msgstr "60 minutter"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:31
+msgid "Auto-refresh"
+msgstr "Auto-forny"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:37
+msgid "Refreshing in"
+msgstr "Fornyes om"
+
+#: frontend/src/metabase/dashboard/components/RemoveFromDashboardModal.jsx:37
+msgid "Remove this question?"
+msgstr "Fjern dette spørsmålet?"
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:70
+msgid "Your dashboard was saved"
+msgstr "Panelet ble lagret"
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:75
+msgid "See it"
+msgstr "Se det"
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:132
+msgid "Save this"
+msgstr "Lagre dette"
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:165
+msgid "Show more about this"
+msgstr "Vis mer om dette"
+
+#: frontend/src/metabase/dashboard/containers/DashCardCardParameterMapper.jsx:140
+msgid "This card doesn't have any fields or parameters that can be mapped to this parameter type."
+msgstr "Dette kortet har ingen felter eller parametere som kan tilordnes denne parametertypen."
+
+#: frontend/src/metabase/dashboard/containers/DashCardCardParameterMapper.jsx:142
+msgid "The values in this field don't overlap with the values of any other fields you've chosen."
+msgstr "Verdiene i dette feltet overlapper ikke med verdiene i noen andre felt du har valgt."
+
+#: frontend/src/metabase/dashboard/containers/DashCardCardParameterMapper.jsx:186
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingFieldPicker.jsx:15
+msgid "No valid fields"
+msgstr "Ingen gyldige felt"
+
+#: frontend/src/metabase/entities/collections.js:90
+msgid "Name must be 100 characters or less"
+msgstr "Navn må være 100 tegn eller mindre"
+
+#: frontend/src/metabase/entities/collections.js:104
+msgid "Color is required"
+msgstr "Farge er påkrevd"
+
+#: frontend/src/metabase/entities/dashboards.js:97
+msgid "What is the name of your dashboard?"
+msgstr "Hva er navnet på infotavlen din?"
+
+#: frontend/src/metabase/home/components/Activity.jsx:92
+msgid "did some super awesome stuff that's hard to describe"
+msgstr "gjorde noen superstilige ting som er vanskelig å beskrive"
+
+#: frontend/src/metabase/home/components/Activity.jsx:101
+#: frontend/src/metabase/home/components/Activity.jsx:116
+msgid "created an alert about - "
+msgstr "laget en varsel om - "
+
+#: frontend/src/metabase/home/components/Activity.jsx:126
+#: frontend/src/metabase/home/components/Activity.jsx:141
+msgid "deleted an alert about - "
+msgstr "slettet en varsel om - "
+
+#: frontend/src/metabase/home/components/Activity.jsx:152
+msgid "saved a question about "
+msgstr "lagret et spørsmål om "
+
+#: frontend/src/metabase/home/components/Activity.jsx:165
+msgid "saved a question"
+msgstr "lagret et spørsmål"
+
+#: frontend/src/metabase/home/components/Activity.jsx:169
+msgid "deleted a question"
+msgstr "slettet et spørsmål"
+
+#: frontend/src/metabase/home/components/Activity.jsx:172
+msgid "created a dashboard"
+msgstr "laget en infotavle"
+
+#: frontend/src/metabase/home/components/Activity.jsx:175
+msgid "deleted a dashboard"
+msgstr "slettet en infotavle"
+
+#: frontend/src/metabase/home/components/Activity.jsx:181
+#: frontend/src/metabase/home/components/Activity.jsx:196
+msgid "added a question to the dashboard - "
+msgstr "la til et spørsmål til infotavlen - "
+
+#: frontend/src/metabase/home/components/Activity.jsx:206
+#: frontend/src/metabase/home/components/Activity.jsx:221
+msgid "removed a question from the dashboard - "
+msgstr "fjernet et spørsmål fra infotavlen - "
+
+#: frontend/src/metabase/home/components/Activity.jsx:231
+#: frontend/src/metabase/home/components/Activity.jsx:238
+msgid "received the latest data from"
+msgstr "mottok de siste dataene fra"
+
+#: frontend/src/metabase/home/components/Activity.jsx:244
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:273
+msgid "Unknown"
+msgstr "Ukjent"
+
+#: frontend/src/metabase/home/components/Activity.jsx:251
+msgid "Hello World!"
+msgstr "Hallo verden!"
+
+#: frontend/src/metabase/home/components/Activity.jsx:252
+msgid "Metabase is up and running."
+msgstr "Metabase er oppe og kjører."
+
+#: frontend/src/metabase/home/components/Activity.jsx:258
+#: frontend/src/metabase/home/components/Activity.jsx:288
+msgid "added the metric "
+msgstr "la til indikatoren"
+
+#: frontend/src/metabase/home/components/Activity.jsx:272
+#: frontend/src/metabase/home/components/Activity.jsx:362
+msgid " to the "
+msgstr " til "
+
+#: frontend/src/metabase/home/components/Activity.jsx:282
+#: frontend/src/metabase/home/components/Activity.jsx:322
+#: frontend/src/metabase/home/components/Activity.jsx:372
+#: frontend/src/metabase/home/components/Activity.jsx:413
+msgid " table"
+msgstr " tabell"
+
+#: frontend/src/metabase/home/components/Activity.jsx:298
+#: frontend/src/metabase/home/components/Activity.jsx:328
+msgid "made changes to the metric "
+msgstr "gjorde endringer i indikatoren"
+
+#: frontend/src/metabase/home/components/Activity.jsx:312
+#: frontend/src/metabase/home/components/Activity.jsx:403
+msgid " in the "
+msgstr " i "
+
+#: frontend/src/metabase/home/components/Activity.jsx:335
+msgid "removed the metric "
+msgstr "fjernet indikatoren"
+
+#: frontend/src/metabase/home/components/Activity.jsx:338
+msgid "created a pulse"
+msgstr "laget en puls"
+
+#: frontend/src/metabase/home/components/Activity.jsx:341
+msgid "deleted a pulse"
+msgstr "slettet en puls"
+
+#: frontend/src/metabase/home/components/Activity.jsx:347
+#: frontend/src/metabase/home/components/Activity.jsx:378
+msgid "added the filter"
+msgstr "la til filteret"
+
+#: frontend/src/metabase/home/components/Activity.jsx:388
+#: frontend/src/metabase/home/components/Activity.jsx:419
+msgid "made changes to the filter"
+msgstr "gjorde endringer til filteret"
+
+#: frontend/src/metabase/home/components/Activity.jsx:426
+msgid "removed the filter {0}"
+msgstr "fjernet filteret {0}"
+
+#: frontend/src/metabase/home/components/Activity.jsx:429
+msgid "joined!"
+msgstr "ble med!"
+
+#: frontend/src/metabase/home/components/Activity.jsx:529
+msgid "Hmmm, looks like nothing has happened yet."
+msgstr "Hmm, det ser ikke ut til at noe har skjedd enda."
+
+#: frontend/src/metabase/home/components/Activity.jsx:532
+#, fuzzy
+msgid "Save a question and get this baby going!"
+msgstr "Lagre ett spørsmål så du får fart på denne skuta!"
+
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:19
+msgid "Ask questions and explore"
+msgstr "Spør ett spørsmål og utforsk"
+
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:20
+msgid "Click on charts or tables to explore, or ask a new question using the easy interface or the powerful SQL editor."
+msgstr "Trykk på diagrammer eller tabeller for å utforske, eller still et spørsmål med det enkle grensesnittet eller det kraftige SQL-redigeringsverktøyet."
+
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:30
+msgid "Make your own charts"
+msgstr "Lag dine egne grafer"
+
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:31
+msgid "Create line charts, scatter plots, maps, and more."
+msgstr "Lag linjediagrammer, spredningsplott, kart og mer."
+
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:41
+msgid "Share what you find"
+msgstr "Del hva du finner"
+
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:42
+msgid "Create powerful and flexible dashboards, and send regular updates via email or Slack."
+msgstr "Lag kraftige og fleksible infotavler, og send regelmessige oppdateringer via e-post eller Slack."
+
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:97
+msgid "Let's go"
+msgstr "La oss gå videre"
+
+#: frontend/src/metabase/home/components/NextStep.jsx:34
+msgid "Setup Tip"
+msgstr "Tips til oppsett"
+
+#: frontend/src/metabase/home/components/NextStep.jsx:40
+msgid "View all"
+msgstr "Vis alle"
+
+#: frontend/src/metabase/home/components/RecentViews.jsx:40
+msgid "Recently Viewed"
+msgstr "Nylig vist"
+
+#: frontend/src/metabase/home/components/RecentViews.jsx:75
+msgid "You haven't looked at any dashboards or questions recently"
+msgstr "Du har ikke sett på noen infotavler eller spørsmål nylig"
+
+#: frontend/src/metabase/home/containers/ArchiveApp.jsx:82
+msgid "{0} items selected"
+msgstr "{0} elementer valgt"
+
+#: frontend/src/metabase/home/containers/ArchiveApp.jsx:102
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:172
+msgid "Unarchive"
+msgstr "Dearkiver"
+
+#: frontend/src/metabase/home/containers/HomepageApp.jsx:74
+#: frontend/src/metabase/nav/containers/Navbar.jsx:327
+msgid "Activity"
+msgstr "Aktivitet"
+
+#: frontend/src/metabase/home/containers/SearchApp.jsx:28
+msgid "Results for \"{0}\""
+msgstr "Resultater for \"{0}\""
+
+#: frontend/src/metabase/home/containers/SearchApp.jsx:142
+msgid "Pulse"
+msgstr "Puls"
+
+#: frontend/src/metabase/lib/core.js:7
+msgid "Entity Key"
+msgstr "Entitetsnøkkel"
+
+#: frontend/src/metabase/lib/core.js:8 frontend/src/metabase/lib/core.js:14
+#: frontend/src/metabase/lib/core.js:20
+msgid "Overall Row"
+msgstr "Totalrad"
+
+#: frontend/src/metabase/lib/core.js:9
+msgid "The primary key for this table."
+msgstr "Primærnøkkelen for denne tabellen."
+
+#: frontend/src/metabase/lib/core.js:13
+msgid "Entity Name"
+msgstr "Entitetsnavn"
+
+#: frontend/src/metabase/lib/core.js:15
+msgid "The \"name\" of each record. Usually a column called \"name\", \"title\", etc."
+msgstr "\"Navnet\" for hver oppføring. Vanligvis en kolonne kalt \"navn\", \"tittel\", osv."
+
+#: frontend/src/metabase/lib/core.js:19
+msgid "Foreign Key"
+msgstr "Fremmednøkkel"
+
+#: frontend/src/metabase/lib/core.js:21
+msgid "Points to another table to make a connection."
+msgstr "Peker på en annen tabell for å lage en kobling."
+
+#: frontend/src/metabase/lib/core.js:25
+msgid "Avatar Image URL"
+msgstr "Avatar bilde-URL"
+
+#: frontend/src/metabase/lib/core.js:26 frontend/src/metabase/lib/core.js:31
+#: frontend/src/metabase/lib/core.js:36 frontend/src/metabase/lib/core.js:41
+#: frontend/src/metabase/lib/core.js:46 frontend/src/metabase/lib/core.js:51
+#: frontend/src/metabase/lib/core.js:56 frontend/src/metabase/lib/core.js:61
+#: frontend/src/metabase/lib/core.js:66 frontend/src/metabase/lib/core.js:71
+#: frontend/src/metabase/lib/core.js:76 frontend/src/metabase/lib/core.js:81
+#: frontend/src/metabase/lib/core.js:86 frontend/src/metabase/lib/core.js:91
+#: frontend/src/metabase/lib/core.js:96 frontend/src/metabase/lib/core.js:101
+#: frontend/src/metabase/lib/core.js:106 frontend/src/metabase/lib/core.js:111
+#: frontend/src/metabase/lib/core.js:116 frontend/src/metabase/lib/core.js:121
+#: frontend/src/metabase/lib/core.js:126 frontend/src/metabase/lib/core.js:131
+#: frontend/src/metabase/lib/core.js:136 frontend/src/metabase/lib/core.js:141
+#: frontend/src/metabase/lib/core.js:146 frontend/src/metabase/lib/core.js:151
+#: frontend/src/metabase/lib/core.js:156 frontend/src/metabase/lib/core.js:161
+#: frontend/src/metabase/lib/core.js:166 frontend/src/metabase/lib/core.js:171
+#: frontend/src/metabase/lib/core.js:176 frontend/src/metabase/lib/core.js:181
+#: frontend/src/metabase/lib/core.js:186 frontend/src/metabase/lib/core.js:191
+#: frontend/src/metabase/lib/core.js:196 frontend/src/metabase/lib/core.js:201
+#: frontend/src/metabase/lib/core.js:206 frontend/src/metabase/lib/core.js:211
+#: frontend/src/metabase/lib/core.js:216 frontend/src/metabase/lib/core.js:221
+#: frontend/src/metabase/lib/core.js:226
+msgid "Common"
+msgstr "Vanlig"
+
+#: frontend/src/metabase/lib/core.js:30
+#: frontend/src/metabase/meta/Dashboard.js:82
+#: frontend/src/metabase/qb/components/actions/PivotByCategoryAction.jsx:9
+msgid "Category"
+msgstr "Kategori"
+
+#: frontend/src/metabase/lib/core.js:35
+#: frontend/src/metabase/meta/Dashboard.js:62
+msgid "City"
+msgstr "By"
+
+#: frontend/src/metabase/lib/core.js:40
+#: frontend/src/metabase/meta/Dashboard.js:74
+msgid "Country"
+msgstr "Land"
+
+#: frontend/src/metabase/lib/core.js:55
+msgid "Enum"
+msgstr "Enum"
+
+#: frontend/src/metabase/lib/core.js:60
+msgid "Image URL"
+msgstr "Bilde nettlink"
+
+#: frontend/src/metabase/lib/core.js:65
+msgid "Field containing JSON"
+msgstr "Felt inneholder JSON"
+
+#: frontend/src/metabase/lib/core.js:70
+msgid "Latitude"
+msgstr "Breddegrad"
+
+#: frontend/src/metabase/lib/core.js:75
+msgid "Longitude"
+msgstr "Lengdegrad"
+
+#: frontend/src/metabase/lib/core.js:80
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:146
+#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:22
+msgid "Number"
+msgstr "Nummer"
+
+#: frontend/src/metabase/lib/core.js:85
+#: frontend/src/metabase/meta/Dashboard.js:66
+msgid "State"
+msgstr "Stat"
+
+#: frontend/src/metabase/lib/core.js:90
+msgid "UNIX Timestamp (Seconds)"
+msgstr "UNIX tidsstempel (sekunder)"
+
+#: frontend/src/metabase/lib/core.js:95
+msgid "UNIX Timestamp (Milliseconds)"
+msgstr "UNIX tidsstempel (millisekunder)"
+
+#: frontend/src/metabase/lib/core.js:105
+msgid "Zip Code"
+msgstr "Postnummer"
+
+#: frontend/src/metabase/lib/core.js:110
+msgid "Quantity"
+msgstr "Kvantum"
+
+#: frontend/src/metabase/lib/core.js:115
+msgid "Income"
+msgstr "Inntekt"
+
+#: frontend/src/metabase/lib/core.js:120
+msgid "Discount"
+msgstr "Avslag"
+
+#: frontend/src/metabase/lib/core.js:125
+msgid "Creation timestamp"
+msgstr "Tidsstempel for opprettelse"
+
+#: frontend/src/metabase/lib/core.js:130
+msgid "Creation time"
+msgstr "Tid for opprettelse"
+
+#: frontend/src/metabase/lib/core.js:135
+msgid "Creation date"
+msgstr "Dato for opprettelse"
+
+#: frontend/src/metabase/lib/core.js:140
+msgid "Product"
+msgstr "Produkt"
+
+#: frontend/src/metabase/lib/core.js:145
+msgid "User"
+msgstr "Bruker"
+
+#: frontend/src/metabase/lib/core.js:150
+msgid "Source"
+msgstr "Kilde"
+
+#: frontend/src/metabase/lib/core.js:155
+msgid "Price"
+msgstr "Pris"
+
+#: frontend/src/metabase/lib/core.js:160
+msgid "Join timestamp"
+msgstr "Tidsstempel når ble med"
+
+#: frontend/src/metabase/lib/core.js:165
+msgid "Join time"
+msgstr "Registreringstid"
+
+#: frontend/src/metabase/lib/core.js:170
+msgid "Join date"
+msgstr "Registreringsdato"
+
+#: frontend/src/metabase/lib/core.js:175
+msgid "Share"
+msgstr "Del"
+
+#: frontend/src/metabase/lib/core.js:180
+msgid "Owner"
+msgstr "Eier"
+
+#: frontend/src/metabase/lib/core.js:185
+msgid "Company"
+msgstr "Firma"
+
+#: frontend/src/metabase/lib/core.js:190
+msgid "Subscription"
+msgstr "Abonnement"
+
+#: frontend/src/metabase/lib/core.js:195
+msgid "Score"
+msgstr "Poengsum"
+
+#: frontend/src/metabase/lib/core.js:205
+#: frontend/src/metabase/public/components/widgets/DisplayOptionsPane.jsx:49
+#: frontend/src/metabase/visualizations/lib/settings.js:156
+msgid "Title"
+msgstr "Tittel"
+
+#: frontend/src/metabase/lib/core.js:210
+msgid "Comment"
+msgstr "Kommentar"
+
+#: frontend/src/metabase/lib/core.js:215
+msgid "Cost"
+msgstr "Kostnad"
+
+#: frontend/src/metabase/lib/core.js:220
+msgid "Gross margin"
+msgstr "Bruttomargin"
+
+#: frontend/src/metabase/lib/core.js:225
+msgid "Birthday"
+msgstr "Bursdag"
+
+#: frontend/src/metabase/lib/core.js:236
+msgid "Search box"
+msgstr "Søkeboks"
+
+#: frontend/src/metabase/lib/core.js:237
+msgid "A list of all values"
+msgstr "En liste med alle verdier"
+
+#: frontend/src/metabase/lib/core.js:238
+msgid "Plain input box"
+msgstr "Enkel informasjonsboks"
+
+#: frontend/src/metabase/lib/core.js:244
+msgid "Everywhere"
+msgstr "Overalt"
+
+#: frontend/src/metabase/lib/core.js:245
+msgid "The default setting. This field will be displayed normally in tables and charts."
+msgstr "Standardinnstillingen. Dette feltet vil normalt vises. i tabeller og diagrammer"
+
+#: frontend/src/metabase/lib/core.js:249
+msgid "Only in Detail Views"
+msgstr "Bare i standard visning"
+
+#: frontend/src/metabase/lib/core.js:250
+msgid "This field will only be displayed when viewing the details of a single record. Use this for information that's lengthy or that isn't useful in a table or chart."
+msgstr "Dette feltet vil bare vises når du ser på detaljene for en enkeltoppføring. Bruk denne for informasjon som er lang eller som ikke er nyttig i en tabell eller et diagram."
+
+#: frontend/src/metabase/lib/core.js:254
+msgid "Do Not Include"
+msgstr "Ikke inkluder"
+
+#: frontend/src/metabase/lib/core.js:255
+msgid "Metabase will never retrieve this field. Use this for sensitive or irrelevant information."
+msgstr "Metabase vil aldri hente dette feltet. Bruk dette for sensitiv eller irrelevant informasjon."
+
+#: frontend/src/metabase/lib/expressions/config.js:7
+#: frontend/src/metabase/lib/query.js:615
+#: frontend/src/metabase/visualizations/lib/utils.js:113
+msgid "Count"
+msgstr "Telle"
+
+#: frontend/src/metabase/lib/expressions/config.js:8
+msgid "CumulativeCount"
+msgstr "KumulativTelling"
+
+#: frontend/src/metabase/lib/expressions/config.js:9
+#: frontend/src/metabase/qb/components/drill/SummarizeColumnDrill.js:17
+#: frontend/src/metabase/visualizations/lib/utils.js:114
+msgid "Sum"
+msgstr "Sum"
+
+#: frontend/src/metabase/lib/expressions/config.js:10
+msgid "CumulativeSum"
+msgstr "KumulativSum"
+
+#: frontend/src/metabase/lib/expressions/config.js:11
+#: frontend/src/metabase/visualizations/lib/utils.js:115
+msgid "Distinct"
+msgstr "Distrikt"
+
+#: frontend/src/metabase/lib/expressions/config.js:12
+msgid "StandardDeviation"
+msgstr "StandardAvvik"
+
+#: frontend/src/metabase/lib/expressions/config.js:13
+#: frontend/src/metabase/visualizations/lib/utils.js:112
+msgid "Average"
+msgstr "Gjennomsnitt"
+
+#: frontend/src/metabase/lib/expressions/config.js:14
+#: frontend/src/metabase/qb/components/drill/SummarizeColumnDrill.js:25
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:363
+msgid "Min"
+msgstr "Min"
+
+#: frontend/src/metabase/lib/expressions/config.js:15
+#: frontend/src/metabase/qb/components/drill/SummarizeColumnDrill.js:29
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:371
+msgid "Max"
+msgstr "Maks"
+
+#: frontend/src/metabase/lib/expressions/parser.js:384
+msgid "sad sad panda, lexing errors detected"
+msgstr "trist trist panda, leksikalske feil ble funnet"
+
+#: frontend/src/metabase/lib/formatting.js:447
+msgid "{0} second"
+msgid_plural "{0} seconds"
+msgstr[0] "{0} sekund"
+msgstr[1] "{0} sekunder"
+
+#: frontend/src/metabase/lib/formatting.js:450
+msgid "{0} minute"
+msgid_plural "{0} minutes"
+msgstr[0] "{0} minutt"
+msgstr[1] "{0} minutter"
+
+#: frontend/src/metabase/lib/greeting.js:4
+msgid "Hey there"
+msgstr "Hei du"
+
+#: frontend/src/metabase/lib/greeting.js:5
+#: frontend/src/metabase/lib/greeting.js:29
+msgid "How's it going"
+msgstr "Hvordan går det"
+
+#: frontend/src/metabase/lib/greeting.js:6
+msgid "Howdy"
+msgstr "Halla"
+
+#: frontend/src/metabase/lib/greeting.js:7
+msgid "Greetings"
+msgstr "Velkommen"
+
+#: frontend/src/metabase/lib/greeting.js:8
+msgid "Good to see you"
+msgstr "Godt å se deg"
+
+#: frontend/src/metabase/lib/greeting.js:12
+msgid "What do you want to know?"
+msgstr "Hva vil du vite?"
+
+#: frontend/src/metabase/lib/greeting.js:13
+msgid "What's on your mind?"
+msgstr "Hva tenker du på?"
+
+#: frontend/src/metabase/lib/greeting.js:14
+msgid "What do you want to find out?"
+msgstr "Hva lurer du på?"
+
+#: frontend/src/metabase/lib/query.js:613
+#: frontend/src/metabase/lib/schema_metadata.js:441
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:246
+msgid "Raw data"
+msgstr "RÃ¥data"
+
+#: frontend/src/metabase/lib/query.js:617
+msgid "Cumulative count"
+msgstr "Kumulativ telling"
+
+#: frontend/src/metabase/lib/query.js:620
+msgid "Average of "
+msgstr "Gjennomsnittet av "
+
+#: frontend/src/metabase/lib/query.js:625
+msgid "Distinct values of "
+msgstr "Unike verdier av "
+
+#: frontend/src/metabase/lib/query.js:630
+msgid "Standard deviation of "
+msgstr "Standardavviket av "
+
+#: frontend/src/metabase/lib/query.js:635
+msgid "Sum of "
+msgstr "Summen av "
+
+#: frontend/src/metabase/lib/query.js:640
+msgid "Cumulative sum of "
+msgstr "Kumulativ sum av "
+
+#: frontend/src/metabase/lib/query.js:645
+msgid "Maximum of "
+msgstr "Største av "
+
+#: frontend/src/metabase/lib/query.js:650
+msgid "Minimum of "
+msgstr "Minste av "
+
+#: frontend/src/metabase/lib/query.js:664
+msgid "Grouped by "
+msgstr "Gruppert etter "
+
+#: frontend/src/metabase/lib/query.js:678
+msgid "Filtered by "
+msgstr "Filtrert på "
+
+#: frontend/src/metabase/lib/query.js:706
+msgid "Sorted by "
+msgstr "Sortert etter "
+
+#: frontend/src/metabase/lib/schema_metadata.js:211
+msgid "True"
+msgstr "Sant"
+
+#: frontend/src/metabase/lib/schema_metadata.js:211
+msgid "False"
+msgstr "Usant"
+
+#: frontend/src/metabase/lib/schema_metadata.js:295
+msgid "Select longitude field"
+msgstr "Velg felt for lengdegrad"
+
+#: frontend/src/metabase/lib/schema_metadata.js:296
+msgid "Enter upper latitude"
+msgstr "Skriv inn øvre breddegrad"
+
+#: frontend/src/metabase/lib/schema_metadata.js:297
+msgid "Enter left longitude"
+msgstr "Skriv inn venstre lengdegrad"
+
+#: frontend/src/metabase/lib/schema_metadata.js:298
+msgid "Enter lower latitude"
+msgstr "Skriv inn nedre breddegrad"
+
+#: frontend/src/metabase/lib/schema_metadata.js:299
+msgid "Enter right longitude"
+msgstr "Skriv inn høyre lengdegrad"
+
+#: frontend/src/metabase/lib/schema_metadata.js:335
+#: frontend/src/metabase/lib/schema_metadata.js:355
+#: frontend/src/metabase/lib/schema_metadata.js:365
+#: frontend/src/metabase/lib/schema_metadata.js:371
+#: frontend/src/metabase/lib/schema_metadata.js:379
+#: frontend/src/metabase/lib/schema_metadata.js:385
+#: frontend/src/metabase/lib/schema_metadata.js:390
+msgid "Is"
+msgstr "Er"
+
+#: frontend/src/metabase/lib/schema_metadata.js:336
+#: frontend/src/metabase/lib/schema_metadata.js:356
+#: frontend/src/metabase/lib/schema_metadata.js:366
+#: frontend/src/metabase/lib/schema_metadata.js:380
+#: frontend/src/metabase/lib/schema_metadata.js:386
+msgid "Is not"
+msgstr "Er ikke"
+
+#: frontend/src/metabase/lib/schema_metadata.js:337
+#: frontend/src/metabase/lib/schema_metadata.js:351
+#: frontend/src/metabase/lib/schema_metadata.js:359
+#: frontend/src/metabase/lib/schema_metadata.js:367
+#: frontend/src/metabase/lib/schema_metadata.js:375
+#: frontend/src/metabase/lib/schema_metadata.js:381
+#: frontend/src/metabase/lib/schema_metadata.js:391
+msgid "Is empty"
+msgstr "Er tom"
+
+#: frontend/src/metabase/lib/schema_metadata.js:338
+#: frontend/src/metabase/lib/schema_metadata.js:352
+#: frontend/src/metabase/lib/schema_metadata.js:360
+#: frontend/src/metabase/lib/schema_metadata.js:368
+#: frontend/src/metabase/lib/schema_metadata.js:376
+#: frontend/src/metabase/lib/schema_metadata.js:382
+#: frontend/src/metabase/lib/schema_metadata.js:392
+msgid "Not empty"
+msgstr "Ikke tom"
+
+#: frontend/src/metabase/lib/schema_metadata.js:344
+msgid "Equal to"
+msgstr "Er lik"
+
+#: frontend/src/metabase/lib/schema_metadata.js:345
+msgid "Not equal to"
+msgstr "Ikke lik"
+
+#: frontend/src/metabase/lib/schema_metadata.js:346
+msgid "Greater than"
+msgstr "Større enn"
+
+#: frontend/src/metabase/lib/schema_metadata.js:347
+msgid "Less than"
+msgstr "Mindre enn"
+
+#: frontend/src/metabase/lib/schema_metadata.js:348
+#: frontend/src/metabase/lib/schema_metadata.js:374
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:289
+#: frontend/src/metabase/query_builder/components/filters/pickers/TimePicker.jsx:96
+msgid "Between"
+msgstr "Mellom"
+
+#: frontend/src/metabase/lib/schema_metadata.js:349
+msgid "Greater than or equal to"
+msgstr "Større eller lik"
+
+#: frontend/src/metabase/lib/schema_metadata.js:350
+msgid "Less than or equal to"
+msgstr "Mindre eller lik"
+
+#: frontend/src/metabase/lib/schema_metadata.js:357
+msgid "Contains"
+msgstr "Inneholder"
+
+#: frontend/src/metabase/lib/schema_metadata.js:358
+msgid "Does not contain"
+msgstr "Inneholder ikke"
+
+#: frontend/src/metabase/lib/schema_metadata.js:361
+msgid "Starts with"
+msgstr "Starter med"
+
+#: frontend/src/metabase/lib/schema_metadata.js:362
+msgid "Ends with"
+msgstr "Slutter med"
+
+#: frontend/src/metabase/lib/schema_metadata.js:372
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:268
+#: frontend/src/metabase/query_builder/components/filters/pickers/TimePicker.jsx:74
+msgid "Before"
+msgstr "Før"
+
+#: frontend/src/metabase/lib/schema_metadata.js:373
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:275
+#: frontend/src/metabase/query_builder/components/filters/pickers/TimePicker.jsx:85
+msgid "After"
+msgstr "Etter"
+
+#: frontend/src/metabase/lib/schema_metadata.js:387
+msgid "Inside"
+msgstr "Inni"
+
+#: frontend/src/metabase/lib/schema_metadata.js:443
+msgid "Just a table with the rows in the answer, no additional operations."
+msgstr "Bare en tabell med rader i svaret, ingen flere operasjoner."
+
+#: frontend/src/metabase/lib/schema_metadata.js:449
+msgid "Count of rows"
+msgstr "Antall rader"
+
+#: frontend/src/metabase/lib/schema_metadata.js:451
+msgid "Total number of rows in the answer."
+msgstr "Totalt antall rader i svaret"
+
+#: frontend/src/metabase/lib/schema_metadata.js:457
+msgid "Sum of ..."
+msgstr "Summen av ..."
+
+#: frontend/src/metabase/lib/schema_metadata.js:459
+msgid "Sum of all the values of a column."
+msgstr "Summen av alle verdiene i en kolonne."
+
+#: frontend/src/metabase/lib/schema_metadata.js:465
+msgid "Average of ..."
+msgstr "Gjennomsnittet av ..."
+
+#: frontend/src/metabase/lib/schema_metadata.js:467
+msgid "Average of all the values of a column"
+msgstr "Gjennomsnittet av alle verdiene i en kolonne"
+
+#: frontend/src/metabase/lib/schema_metadata.js:473
+msgid "Number of distinct values of ..."
+msgstr "Antall unike verdier av ..."
+
+#: frontend/src/metabase/lib/schema_metadata.js:475
+msgid "Number of unique values of a column among all the rows in the answer."
+msgstr "Antall unike verdier i en kolonne blant alle radene i svaret."
+
+#: frontend/src/metabase/lib/schema_metadata.js:481
+msgid "Cumulative sum of ..."
+msgstr "Kumulativ sum av ..."
+
+#: frontend/src/metabase/lib/schema_metadata.js:483
+msgid "Additive sum of all the values of a column.\\ne.x. total revenue over time."
+msgstr "Additiv sum av alle verdiene i en kolonne.\\nf.eks. total omsetning over tid."
+
+#: frontend/src/metabase/lib/schema_metadata.js:489
+msgid "Cumulative count of rows"
+msgstr "Kumulativ telling av rader"
+
+#: frontend/src/metabase/lib/schema_metadata.js:491
+msgid "Additive count of the number of rows.\\ne.x. total number of sales over time."
+msgstr "Additiv telling av antall rader.\\nf.eks. antall salg over tid."
+
+#: frontend/src/metabase/lib/schema_metadata.js:497
+msgid "Standard deviation of ..."
+msgstr "Standardavvik for ..."
+
+#: frontend/src/metabase/lib/schema_metadata.js:499
+msgid "Number which expresses how much the values of a column vary among all rows in the answer."
+msgstr "Tall som uttrykker hvor mye verdiene i en kolonne varierer blant alle radene i svaret."
+
+#: frontend/src/metabase/lib/schema_metadata.js:505
+msgid "Minimum of ..."
+msgstr "Minste av ..."
+
+#: frontend/src/metabase/lib/schema_metadata.js:507
+msgid "Minimum value of a column"
+msgstr "Minste verdi i kolonnen"
+
+#: frontend/src/metabase/lib/schema_metadata.js:513
+msgid "Maximum of ..."
+msgstr "Største av ..."
+
+#: frontend/src/metabase/lib/schema_metadata.js:515
+msgid "Maximum value of a column"
+msgstr "Største verdi i kolonnen"
+
+#: frontend/src/metabase/lib/schema_metadata.js:523
+msgid "Break out by dimension"
+msgstr "Bryt ut etter dimensjon"
+
+#: frontend/src/metabase/lib/settings.js:93
+msgid "lower case letter"
+msgstr "liten bokstav"
+
+#: frontend/src/metabase/lib/settings.js:95
+msgid "upper case letter"
+msgstr "stor bokstav"
+
+#: frontend/src/metabase/lib/settings.js:97
+#: src/metabase/automagic_dashboards/core.clj
+msgid "number"
+msgstr "tall"
+
+#: frontend/src/metabase/lib/settings.js:99
+msgid "special character"
+msgstr "spesialtegn"
+
+#: frontend/src/metabase/lib/settings.js:105
+msgid "must be"
+msgstr "må være"
+
+#: frontend/src/metabase/lib/settings.js:105
+#: frontend/src/metabase/lib/settings.js:106
+msgid "characters long"
+msgstr "tegn lang"
+
+#: frontend/src/metabase/lib/settings.js:106
+msgid "Must be"
+msgstr "Må være"
+
+#: frontend/src/metabase/lib/settings.js:122
+msgid "and include"
+msgstr "og inkludere"
+
+#: frontend/src/metabase/lib/utils.js:92
+msgid "zero"
+msgstr "null"
+
+#: frontend/src/metabase/lib/utils.js:93
+msgid "one"
+msgstr "en"
+
+#: frontend/src/metabase/lib/utils.js:94
+msgid "two"
+msgstr "to"
+
+#: frontend/src/metabase/lib/utils.js:95
+msgid "three"
+msgstr "tre"
+
+#: frontend/src/metabase/lib/utils.js:96
+msgid "four"
+msgstr "fire"
+
+#: frontend/src/metabase/lib/utils.js:97
+msgid "five"
+msgstr "fem"
+
+#: frontend/src/metabase/lib/utils.js:98
+msgid "six"
+msgstr "seks"
+
+#: frontend/src/metabase/lib/utils.js:99
+msgid "seven"
+msgstr "syv"
+
+#: frontend/src/metabase/lib/utils.js:100
+msgid "eight"
+msgstr "Ã¥tte"
+
+#: frontend/src/metabase/lib/utils.js:101
+msgid "nine"
+msgstr "ni"
+
+#: frontend/src/metabase/meta/Dashboard.js:31
+msgid "Month and Year"
+msgstr "MÃ¥ned og Dag"
+
+#: frontend/src/metabase/meta/Dashboard.js:32
+msgid "Like January, 2016"
+msgstr "Som Januar, 2016"
+
+#: frontend/src/metabase/meta/Dashboard.js:36
+msgid "Quarter and Year"
+msgstr "Kvartal og Ã…r"
+
+#: frontend/src/metabase/meta/Dashboard.js:37
+msgid "Like Q1, 2016"
+msgstr "Som Q1, 2016"
+
+#: frontend/src/metabase/meta/Dashboard.js:41
+msgid "Single Date"
+msgstr "Enkelt Dato"
+
+#: frontend/src/metabase/meta/Dashboard.js:42
+msgid "Like January 31, 2016"
+msgstr "Som 31 Januar, 2016"
+
+#: frontend/src/metabase/meta/Dashboard.js:46
+msgid "Date Range"
+msgstr "Datointervall"
+
+#: frontend/src/metabase/meta/Dashboard.js:47
+msgid "Like December 25, 2015 - February 14, 2016"
+msgstr "Som 25 Desember, 2015 - 14 Februar, 2016"
+
+#: frontend/src/metabase/meta/Dashboard.js:51
+msgid "Relative Date"
+msgstr "Relativ Dato"
+
+#: frontend/src/metabase/meta/Dashboard.js:52
+msgid "Like \"the last 7 days\" or \"this month\""
+msgstr "Som \"de siste 7 dagene\" eller \"denne måneden\""
+
+#: frontend/src/metabase/meta/Dashboard.js:56
+msgid "Date Filter"
+msgstr "Datofilter"
+
+#: frontend/src/metabase/meta/Dashboard.js:57
+msgid "All Options"
+msgstr "Alle alternativer"
+
+#: frontend/src/metabase/meta/Dashboard.js:58
+msgid "Contains all of the above"
+msgstr "Inneholder alt ovenfor"
+
+#: frontend/src/metabase/meta/Dashboard.js:70
+msgid "ZIP or Postal Code"
+msgstr "Postnummer"
+
+#: frontend/src/metabase/meta/Dashboard.js:78
+#: frontend/src/metabase/meta/Dashboard.js:108
+msgid "ID"
+msgstr "ID"
+
+#: frontend/src/metabase/meta/Dashboard.js:96
+#: frontend/src/metabase/qb/components/actions/PivotByTimeAction.jsx:8
+msgid "Time"
+msgstr "Tid"
+
+#: frontend/src/metabase/meta/Dashboard.js:97
+msgid "Date range, relative date, time of day, etc."
+msgstr "Datointervall, relativ dato, tid på dagen, osv."
+
+#: frontend/src/metabase/meta/Dashboard.js:102
+#: frontend/src/metabase/qb/components/actions/PivotByLocationAction.jsx:8
+msgid "Location"
+msgstr "Sted"
+
+#: frontend/src/metabase/meta/Dashboard.js:103
+msgid "City, State, Country, ZIP code."
+msgstr "By, Stat, Land, Postnummer."
+
+#: frontend/src/metabase/meta/Dashboard.js:109
+msgid "User ID, product ID, event ID, etc."
+msgstr "Bruker ID, produkt ID, hendelses ID, osv."
+
+#: frontend/src/metabase/meta/Dashboard.js:114
+msgid "Other Categories"
+msgstr "Andre kategorier"
+
+#: frontend/src/metabase/meta/Dashboard.js:115
+msgid "Category, Type, Model, Rating, etc."
+msgstr "Kategori, Type, Modell, Vurdering, osv."
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:43
+#: frontend/src/metabase/user/components/UserSettings.jsx:54
+msgid "Account settings"
+msgstr "Kontoinnstillinger"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:50
+msgid "Exit admin"
+msgstr "Lukk admin"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:60
+msgid "Logs"
+msgstr "Logger"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:67
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorSidebar.jsx:105
+msgid "Help"
+msgstr "Hjelp"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:76
+msgid "About Metabase"
+msgstr "Om Metabase"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:82
+msgid "Sign out"
+msgstr "Logg ut"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:107
+msgid "Thanks for using"
+msgstr "Takk for at du bruker"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:111
+msgid "You're on version"
+msgstr "Du bruker versjon"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:114
+msgid "Built on"
+msgstr "Bygget den"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:133
+msgid "is a Trademark of"
+msgstr "er et varemerke for"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:135
+msgid "and is built with care in San Francisco, CA"
+msgstr "og er bygget med omhu i San Francisco, California"
+
+#: frontend/src/metabase/nav/containers/Navbar.jsx:195
+msgid "Metabase Admin"
+msgstr "Metabase Admin"
+
+#: frontend/src/metabase/nav/containers/Navbar.jsx:296
+#: frontend/src/metabase/reference/databases/TableQuestions.jsx:36
+#: frontend/src/metabase/reference/metrics/MetricQuestions.jsx:37
+#: frontend/src/metabase/reference/segments/SegmentQuestions.jsx:37
+msgid "Ask a question"
+msgstr "Still et spørsmål"
+
+#: frontend/src/metabase/nav/containers/Navbar.jsx:305
+msgid "New dashboard"
+msgstr "Ny infotavle"
+
+#: frontend/src/metabase/nav/containers/Navbar.jsx:311
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:127
+msgid "New pulse"
+msgstr "Ny puls"
+
+#: frontend/src/metabase/nav/containers/Navbar.jsx:319
+msgid "Reference"
+msgstr "Referanse"
+
+#: frontend/src/metabase/new_query/containers/MetricSearch.jsx:83
+msgid "Which metric?"
+msgstr "Hvilken indikator?"
+
+#: frontend/src/metabase/new_query/containers/MetricSearch.jsx:110
+#: frontend/src/metabase/reference/metrics/MetricList.jsx:24
+msgid "Defining common metrics for your team makes it even easier to ask questions"
+msgstr "Å definere vanlige indikatorer for ditt lag gjør det enklere å spørre spørsmål"
+
+#: frontend/src/metabase/new_query/containers/MetricSearch.jsx:113
+msgid "How to create metrics"
+msgstr "Hvordan lage indikatorer"
+
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:138
+msgid "See data over time, as a map, or pivoted to help you understand trends or changes."
+msgstr "Se data over tid, som et kart, eller pivotert for å gjøre det enklere å forstå trender eller endringer."
+
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:149
+msgid "Custom"
+msgstr "Egendefinert"
+
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:150
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:517
+msgid "New question"
+msgstr "Nytt spørsmål"
+
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:152
+msgid "Use the simple question builder to see trends, lists of things, or to create your own metrics."
+msgstr "Bruke den enkle spørsmåls-byggeren for å se trender, liser over ting, eller for å lage dine egne indikatorer."
+
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:161
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Native query"
+msgstr "Lokal spørring"
+
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:162
+msgid "For more complicated questions, you can write your own SQL or native query."
+msgstr "For mer kompliserte spørsmål, kan du skrive din egen SQL eller lokal spørring."
+
+#: frontend/src/metabase/parameters/components/ParameterValueWidget.jsx:240
+msgid "Select a default value…"
+msgstr "Velg en standardverdi..."
+
+#: frontend/src/metabase/parameters/components/widgets/DateAllOptionsWidget.jsx:149
+#: frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx:390
+msgid "Update filter"
+msgstr "Oppdater filter"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:9
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:144
+msgid "Today"
+msgstr "I dag"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:14
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:148
+msgid "Yesterday"
+msgstr "I går"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:18
+msgid "Past 7 days"
+msgstr "Siste 7 dager"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:19
+msgid "Past 30 days"
+msgstr "Siste 30 dager"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:24
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:29
+#: src/metabase/api/table.clj
+msgid "Week"
+msgstr "Uke"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:25
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:30
+#: src/metabase/api/table.clj
+msgid "Month"
+msgstr "MÃ¥ned"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:26
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:31
+#: src/metabase/api/table.clj
+msgid "Year"
+msgstr "Ã…r"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:152
+msgid "Past 7 Days"
+msgstr "Siste 7 Dager"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:156
+msgid "Past 30 Days"
+msgstr "Siste 30 Dager"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:160
+msgid "Last Week"
+msgstr "Siste Uke"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:164
+msgid "Last Month"
+msgstr "Siste MÃ¥ned"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:168
+msgid "Last Year"
+msgstr "Siste Ã…r"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:172
+msgid "This Week"
+msgstr "Denne Uken"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:176
+msgid "This Month"
+msgstr "Denne MÃ¥neden"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:180
+msgid "This Year"
+msgstr "Dette Ã…ret"
+
+#: frontend/src/metabase/parameters/components/widgets/ParameterFieldWidget.jsx:88
+#: frontend/src/metabase/parameters/components/widgets/TextWidget.jsx:54
+msgid "Enter a value..."
+msgstr "Skriv en verdi..."
+
+#: frontend/src/metabase/parameters/components/widgets/TextWidget.jsx:90
+msgid "Enter a default value..."
+msgstr "Skriv en standardverdi..."
+
+#: frontend/src/metabase/public/components/PublicError.jsx:18
+msgid "An error occurred"
+msgstr "En feil oppstod"
+
+#: frontend/src/metabase/public/components/PublicNotFound.jsx:11
+msgid "Not found"
+msgstr "Ikke funnet"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:82
+msgid "You’ve made changes that need to be published before they will be reflected in your application embed."
+msgstr "Du har gjort endringer som må publiseres før de vil bli reflektert i din applikasjonsinnbygging."
+
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:83
+msgid "You will need to publish this {0} before you can embed it in another application."
+msgstr "Du må publisere denne {0} før du kan bygge den inn i en annen applikasjon."
+
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:92
+msgid "Discard Changes"
+msgstr "Forkast Endringer."
+
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:99
+msgid "Updating..."
+msgstr "Oppdaterer..."
+
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:100
+msgid "Updated"
+msgstr "Oppdatert"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:101
+msgid "Failed!"
+msgstr "Feilet!"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:102
+msgid "Publish"
+msgstr "Publiser"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:111
+msgid "Code"
+msgstr "Kode"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:70
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:157
+msgid "Style"
+msgstr "Stil"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:80
+msgid "Which parameters can users of this embed use?"
+msgstr "Hvilke parametere kan brukere av denne innbyggingen bruke?"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:83
+msgid "This {0} doesn't have any parameters to configure yet."
+msgstr "Denne {0} har ikke noen parametere å konfigurere enda."
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:104
+msgid "Editable"
+msgstr "Redigerbar"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:105
+msgid "Locked"
+msgstr "LÃ¥st"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:113
+msgid "Preview Locked Parameters"
+msgstr "Forhåndsvis Låste Parametere"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:115
+msgid "Try passing some values to your locked parameters here. Your server will have to provide the actual values in the signed token when using this for real."
+msgstr "Prøv å sende noen verdier til dine låste parametere her. Tjeneren din må tilby de faktiske verdiene i den signerte tokenen når du bruker dette på ordentlig."
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:126
+msgid "Danger zone"
+msgstr "Faresone"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:127
+msgid "This will disable embedding for this {0}."
+msgstr "Dette vil deaktivere innbygging av denne {0}."
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:128
+msgid "Unpublish"
+msgstr "Avpubliser"
+
+#: frontend/src/metabase/public/components/widgets/DisplayOptionsPane.jsx:17
+msgid "Light"
+msgstr "Lys"
+
+#: frontend/src/metabase/public/components/widgets/DisplayOptionsPane.jsx:18
+msgid "Dark"
+msgstr "Mørk"
+
+#: frontend/src/metabase/public/components/widgets/DisplayOptionsPane.jsx:37
+msgid "Border"
+msgstr "Ramme"
+
+#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:62
+msgid "To embed this {0} in your application:"
+msgstr "For å bygge inn denne {0} i din applikasjon:"
+
+#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:64
+msgid "Insert this code snippet in your server code to generate the signed embedding URL "
+msgstr "Sett inn denne kodesnutten i koden på tjeneren din for å generere den signerte innbyggings-URLen "
+
+#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:87
+msgid "Then insert this code snippet in your HTML template or single page app."
+msgstr "Deretter, sett inn denne kodesnutten i din HTML-mal eller en enkeltsidig applikasjon."
+
+#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:94
+msgid "Embed code snippet for your HTML or Frontend Application"
+msgstr "Kodesnutt for innbygging for din HTML eller frontend-applikasjon"
+
+#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:101
+msgid "More {0}"
+msgstr "Mer {0}"
+
+#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:103
+msgid "examples on GitHub"
+msgstr "eksempler på GitHub"
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:72
+msgid "Enable sharing"
+msgstr "Aktiver deling"
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:76
+msgid "Disable this public link?"
+msgstr "Deaktiver denne offentlige lenken?"
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:77
+msgid "This will cause the existing link to stop working. You can re-enable it, but when you do it will be a different link."
+msgstr "Dette vil gjøre sånn at den gjeldende lenken slutter å fungere. Du kan re-aktivere den, men du vil da få en annen lenke."
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:117
+msgid "Public link"
+msgstr "Offentlig lenke"
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:118
+msgid "Share this {0} with people who don't have a Metabase account using the URL below:"
+msgstr "Del denne {0} med folk som ikke har en Metabase-konto ved å bruke nettadressen under:"
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:158
+msgid "Public embed"
+msgstr "Offentlig innbygging"
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:159
+msgid "Embed this {0} in blog posts or web pages by copying and pasting this snippet:"
+msgstr "Bygg inn denne {0} i blogginnlegg eller nettside ved å kopiere og lime inn denne snutten:"
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:176
+msgid "Embed this {0} in an application"
+msgstr "Bygg inn denne {0} i en applikasjon"
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:177
+msgid "By integrating with your application server code, you can provide a secure stats {0} limited to a specific user, customer, organization, etc."
+msgstr "Ved å integrere med koden til din applikasjonstjener, så kan du tilby sikker statistikk {0} begrenset til en spesifikk bruker, kunde, organisasjon, osv."
+
+#: frontend/src/metabase/pulse/components/PulseCardPreview.jsx:94
+msgid "Remove attachment"
+msgstr "Fjern vedlegg"
+
+#: frontend/src/metabase/pulse/components/PulseCardPreview.jsx:95
+msgid "Attach file with results"
+msgstr "Legg ved fil med resultatene"
+
+#: frontend/src/metabase/pulse/components/PulseCardPreview.jsx:127
+msgid "This question will be added as a file attachment"
+msgstr "Dette spørsmålet vil bli lagt til som vedlegg"
+
+#: frontend/src/metabase/pulse/components/PulseCardPreview.jsx:128
+msgid "This question won't be included in your Pulse"
+msgstr "Dette spørsmålet vil ikke bli inkludert i din Puls"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:92
+msgid "This pulse will no longer be emailed to {0} {1}"
+msgstr "Denne pulsen vil ikke lenger bli sendt til {0} {1}"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:94
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:374
+msgid "{0} address"
+msgid_plural "{0} addresses"
+msgstr[0] "{0} adresse"
+msgstr[1] "{0} adresser"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:102
+msgid "Slack channel {0} will no longer get this pulse {1}"
+msgstr "Slack-kanalen {0} vil ikke lenger motta denne pulsen {1}"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:110
+msgid "Channel {0} will no longer receive this pulse {1}"
+msgstr "Kanalen {0} vil ikke lenger motta denne pulsen {1}"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:127
+msgid "Edit pulse"
+msgstr "Rediger puls"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:131
+msgid "What's a Pulse?"
+msgstr "Hva er en Puls?"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:141
+msgid "Got it"
+msgstr "Skjønner"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:157
+msgid "Where should this data go?"
+msgstr "Hvor bør disse dataene gå?"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:173
+msgid "Unarchiving…"
+msgstr "Pakker ut..."
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:174
+msgid "Unarchive failed"
+msgstr "Utpakking feilet"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:175
+msgid "Unarchived"
+msgstr "Pakket ut"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:182
+msgid "Create pulse"
+msgstr "Laget puls"
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:90
+msgid "Attachment"
+msgstr "Vedlegg"
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:104
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:111
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:671
+msgid "Heads up"
+msgstr "Varsko"
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:105
+msgid "We'll show the first 10 columns and 20 rows of this table in your Pulse. If you email this, we'll add a file attachment with all columns and up to 2,000 rows."
+msgstr "Vi vil vise deg de første 10 kolonnene og 20 radene fra denne tabellen i din Puls. Hvis du sender dette som e-post, så legger vi ved et vedlegg med alle kolonner og opptil 2.000 rader."
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:112
+msgid "Raw data questions can only be included as email attachments"
+msgstr "Rådata-spørsmål kan bare inkluderes som e-postvedlegg"
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:119
+msgid "Looks like this pulse is getting big"
+msgstr "Det ser ut til at denne pulsen begynner å bli stor."
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:120
+msgid "We recommend keeping pulses small and focused to help keep them digestible and useful to the whole team."
+msgstr "Vi anbefaler å holde pulsene små og fokuserte for å gjøre de enklere å fordøye og mer anvendelige for hele laget."
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:160
+msgid "Pick your data"
+msgstr "Velg dataene dine"
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:162
+msgid "Choose questions you'd like to send in this pulse"
+msgstr "Velg spørsmål du vil sende med denne pulsen"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:29
+msgid "Emails"
+msgstr "E-poster"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:30
+msgid "Slack messages"
+msgstr "Slack-meldinger"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:218
+msgid "Sent"
+msgstr "Sendt"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:219
+msgid "{0} will be sent at"
+msgstr "{0} vil bli sendt"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:221
+msgid "Messages"
+msgstr "Meldinger"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:232
+msgid "Send email now"
+msgstr "Send e-post nå"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:233
+msgid "Send to {0} now"
+msgstr "Send til {0} nå"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:235
+msgid "Sending…"
+msgstr "Sender..."
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:236
+msgid "Sending failed"
+msgstr "Sending feilet"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:239
+msgid "Didn’t send because the pulse has no results."
+msgstr "Sendte ikke, fordi pulsen ikke har noen resultater."
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:240
+msgid "Pulse sent"
+msgstr "Puls sendt"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:279
+msgid "{0} needs to be set up by an administrator."
+msgstr "{0} må settes opp av en administrator"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:294
+msgid "Slack"
+msgstr "Slack"
+
+#: frontend/src/metabase/pulse/components/PulseEditCollection.jsx:12
+msgid "Which collection should this pulse live in?"
+msgstr "Hvilken samling skal denne pulsen bo i?"
+
+#: frontend/src/metabase/pulse/components/PulseEditName.jsx:35
+msgid "Name your pulse"
+msgstr "Gi et navn til pulsen din"
+
+#: frontend/src/metabase/pulse/components/PulseEditName.jsx:37
+msgid "Give your pulse a name to help others understand what it's about"
+msgstr "Gi et navn til pulsen for å gjøre det enklere for andre å forstå hva den handler om"
+
+#: frontend/src/metabase/pulse/components/PulseEditName.jsx:49
+msgid "Important metrics"
+msgstr "Viktige indikatorer"
+
+#: frontend/src/metabase/pulse/components/PulseEditSkip.jsx:22
+msgid "Skip if no results"
+msgstr "Hopp over hvis det ikke er noen resultater"
+
+#: frontend/src/metabase/pulse/components/PulseEditSkip.jsx:24
+msgid "Skip a scheduled Pulse if none of its questions have any results"
+msgstr "Hopp over en planlagt Puls hvis ingen av spørsmålene har noen resultater"
+
+#: frontend/src/metabase/pulse/components/RecipientPicker.jsx:65
+msgid "Enter email addresses you'd like this data to go to"
+msgstr "Skriv e-postadresser du vil sende disse dataene til"
+
+#: frontend/src/metabase/pulse/components/WhatsAPulse.jsx:16
+msgid "Help everyone on your team stay in sync with your data."
+msgstr "Hjelp alle på ditt lag med å holde seg i synk med dine data."
+
+#: frontend/src/metabase/pulse/components/WhatsAPulse.jsx:30
+msgid "Pulses let you send data from Metabase to email or Slack on the schedule of your choice."
+msgstr "Pulser lar deg sende data fra Metabase som e-post eller Slack-meldinger enten regelmessig eller etter behov."
+
+#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:100
+msgid "After {0}"
+msgstr "Etter {0}"
+
+#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:102
+msgid "Before {0}"
+msgstr "Før {0}"
+
+#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:104
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:299
+msgid "Is Empty"
+msgstr "Er tom"
+
+#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:106
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:305
+msgid "Not Empty"
+msgstr "Ikke tom"
+
+#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:109
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:216
+msgid "All Time"
+msgstr "Hele tiden"
+
+#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:154
+msgid "Apply"
+msgstr "Bruk"
+
+#: frontend/src/metabase/qb/components/actions/CommonMetricsAction.jsx:21
+msgid "View {0}"
+msgstr "Vis {0}"
+
+#: frontend/src/metabase/qb/components/actions/CompareWithTable.jsx:29
+msgid "Compare this with all rows in the table"
+msgstr "Sammenlign dette med alle rader i tabellen"
+
+#: frontend/src/metabase/qb/components/actions/CompoundQueryAction.jsx:14
+msgid "Analyze the results of this Query"
+msgstr "Analyser resultatene av denne Spørringen"
+
+#: frontend/src/metabase/qb/components/actions/CountByTimeAction.jsx:29
+msgid "Count of rows by time"
+msgstr "Antall rader over tid"
+
+#: frontend/src/metabase/qb/components/actions/PivotByAction.jsx:55
+msgid "Break out by {0}"
+msgstr "Bryt ut ved {0}"
+
+#: frontend/src/metabase/qb/components/actions/SummarizeBySegmentMetricAction.jsx:34
+msgid "Summarize this segment"
+msgstr "Oppsummer dette segmentet"
+
+#: frontend/src/metabase/qb/components/actions/UnderlyingDataAction.jsx:14
+msgid "View this as a table"
+msgstr "Vis dette som en tabell"
+
+#: frontend/src/metabase/qb/components/actions/UnderlyingRecordsAction.jsx:22
+msgid "View the underlying {0} records"
+msgstr "Vis de underliggende {0} oppføringene"
+
+#: frontend/src/metabase/qb/components/actions/XRayCard.jsx:15
+msgid "X-Ray this question"
+msgstr "Utfør X-ray på dette spørsmålet"
+
+#: frontend/src/metabase/qb/components/drill/AutomaticDashboardDrill.jsx:32
+msgid "X-ray {0} {1}"
+msgstr "X-ray {0} {1}"
+
+#: frontend/src/metabase/qb/components/drill/AutomaticDashboardDrill.jsx:32
+#: frontend/src/metabase/qb/components/drill/CompareToRestDrill.js:32
+msgid "these"
+msgstr "disse"
+
+#: frontend/src/metabase/qb/components/drill/AutomaticDashboardDrill.jsx:32
+#: frontend/src/metabase/qb/components/drill/CompareToRestDrill.js:32
+msgid "this"
+msgstr "denne"
+
+#: frontend/src/metabase/qb/components/drill/CompareToRestDrill.js:32
+msgid "Compare {0} {1} to the rest"
+msgstr "Sammenlign {0} {1} med resten"
+
+#: frontend/src/metabase/qb/components/drill/DistributionDrill.jsx:35
+msgid "Distribution"
+msgstr "Distribusjon"
+
+#: frontend/src/metabase/qb/components/drill/ObjectDetailDrill.jsx:38
+msgid "View details"
+msgstr "Vis detaljer"
+
+#: frontend/src/metabase/qb/components/drill/QuickFilterDrill.jsx:54
+msgid "View this {0}'s {1}"
+msgstr "Vis denne {0}s {1}"
+
+#: frontend/src/metabase/qb/components/drill/SortAction.jsx:45
+msgid "Ascending"
+msgstr "Stigende"
+
+#: frontend/src/metabase/qb/components/drill/SortAction.jsx:57
+msgid "Descending"
+msgstr "Synkende"
+
+#: frontend/src/metabase/qb/components/drill/SummarizeColumnByTimeDrill.js:47
+msgid "over time"
+msgstr "over tid"
+
+#: frontend/src/metabase/qb/components/drill/SummarizeColumnDrill.js:21
+msgid "Avg"
+msgstr "Gjennomsnitt"
+
+#: frontend/src/metabase/qb/components/drill/SummarizeColumnDrill.js:33
+msgid "Distincts"
+msgstr "Unike"
+
+#: frontend/src/metabase/qb/components/drill/UnderlyingRecordsDrill.jsx:32
+msgid "View this {0}"
+msgid_plural "View these {0}"
+msgstr[0] "Vis denne {0}"
+msgstr[1] "Vis disse {0}"
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:220
+#: frontend/src/metabase/qb/components/drill/ZoomDrill.jsx:26
+msgid "Zoom in"
+msgstr "Forstørr"
+
+#: frontend/src/metabase/query_builder/components/AggregationPopover.jsx:19
+msgid "Custom Expression"
+msgstr "Egendefinert uttrykk"
+
+#: frontend/src/metabase/query_builder/components/AggregationPopover.jsx:20
+msgid "Common Metrics"
+msgstr "Vanlige indikatorer"
+
+#: frontend/src/metabase/query_builder/components/AggregationPopover.jsx:182
+msgid "Metabasics"
+msgstr "Metabasics"
+
+#: frontend/src/metabase/query_builder/components/AggregationPopover.jsx:290
+msgid "Name (optional)"
+msgstr "Navn (valgfritt)"
+
+#: frontend/src/metabase/query_builder/components/AggregationWidget.jsx:153
+msgid "Choose an aggregation"
+msgstr "Velg en aggregering"
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:115
+msgid "Set up your own alert"
+msgstr "Sett opp ditt eget varsel"
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:155
+msgid "Unsubscribing..."
+msgstr "Stopper abonnement..."
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:160
+msgid "Failed to unsubscribe"
+msgstr "Klarte ikke å stoppe abonnement"
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:216
+msgid "Unsubscribe"
+msgstr "Stopp abonnement"
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:247
+msgid "No channel"
+msgstr "Ingen kanal"
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:274
+msgid "Okay, you're unsubscribed"
+msgstr "Okei, du har meldt deg av"
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:346
+msgid "You're receiving {0}'s alerts"
+msgstr "Du mottar {0}s varsler"
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:347
+msgid "{0} set up an alert"
+msgstr "{0} satte opp en varsel"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:161
+msgid "alerts"
+msgstr "varsler"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:184
+msgid "Let's set up your alert"
+msgstr "La oss sette opp varselet ditt"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:215
+msgid "The wide world of alerts"
+msgstr "En verden av varsler"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:216
+msgid "There are a few different kinds of alerts you can get"
+msgstr "Det er noen forskjellige typer varsler du kan få"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:229
+msgid "When a raw data question {0}"
+msgstr "Når et rådata-spørsmål {0}"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:230
+msgid "returns any results"
+msgstr "returnerer alle resultater"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:240
+msgid "When a line or bar {0}"
+msgstr "NÃ¥r en linje eller stolpe {0}"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:241
+msgid "crosses a goal line"
+msgstr "krysser en mållinje"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:251
+msgid "When a progress bar {0}"
+msgstr "NÃ¥r en fremdriftsindikator {0}"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:252
+msgid "reaches its goal"
+msgstr "når sitt mål"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:260
+msgid "Set up an alert"
+msgstr "Sett opp et varsel"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:329
+msgid "Edit your alert"
+msgstr "Rediger varselet ditt"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:329
+msgid "Edit alert"
+msgstr "Rediger varsel"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:372
+msgid "This alert will no longer be emailed to {0}."
+msgstr "Dette varselet vil ikke lenger bli sendt til {0}"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:380
+msgid "Slack channel {0} will no longer get this alert."
+msgstr "Slack-kanalen {0} vil ikke lenger motta dette varselet."
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:384
+msgid "Channel {0} will no longer receive this alert."
+msgstr "Kanalen {0} vil ikke lenger motta dette varselet."
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:401
+msgid "Delete this alert"
+msgstr "Slett dette varselet."
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:403
+msgid "Stop delivery and delete this alert. There's no undo, so be careful."
+msgstr "Stopp levering og slett dette varselet. Det er ingen måte å angre dette, så vær forsiktig."
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:411
+msgid "Delete this alert?"
+msgstr "Slett dette varselet?"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:495
+msgid "Alert me when the line…"
+msgstr "Varsle meg når linjen..."
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:496
+msgid "Alert me when the progress bar…"
+msgstr "Varsle meg når fremdriftsindikatoren..."
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:499
+msgid "Goes above the goal line"
+msgstr "Går over mållinjen"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:499
+msgid "Reaches the goal"
+msgstr "Når målet"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:502
+msgid "Goes below the goal line"
+msgstr "Går under mållinjen"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:502
+msgid "Goes below the goal"
+msgstr "Går under målet"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:510
+msgid "The first time it crosses, or every time?"
+msgstr "Første gangen den krysser, eller hver gang?"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:511
+msgid "The first time it reaches the goal, or every time?"
+msgstr "Første gangen den når målet, eller hver gang?"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:513
+msgid "The first time"
+msgstr "Første gangen"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:514
+msgid "Every time"
+msgstr "Hver gang"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:617
+msgid "Where do you want to send these alerts?"
+msgstr "Hvor vil du ha disse varslene sendt?"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:628
+msgid "Email alerts to:"
+msgstr "Send e-postvarsel til:"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:670
+msgid "{0} Goal-based alerts aren't yet supported for charts with more than one line, so this alert will be sent whenever the chart has {1}."
+msgstr "{0} Måldrevne varseler støttes ikke enda for diagrammer med mer enn én linje, så dette varselet vil bli sendt når enn diagrammet har {1}."
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:673
+msgid "results"
+msgstr "resultater"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:677
+msgid "{0} This kind of alert is most useful when your saved question doesn’t {1} return any results, but you want to know when it does."
+msgstr "{0} Denne typen varsel er mest nyttig når dine lagrede spørsmål ikke {1} returnerer noen resultater, men du vil vite når de gjør det."
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:678
+msgid "Tip"
+msgstr "Tips"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:680
+msgid "usually"
+msgstr "vanligvis"
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:57
+msgid "Pick a segment or table"
+msgstr "Velg et segment eller en tabell"
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:73
+msgid "Select a database"
+msgstr "Velg en database"
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:88
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:87
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:187
+#: frontend/src/metabase/reference/components/MetricImportantFieldsDetail.jsx:35
+msgid "Select..."
+msgstr "Velg..."
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:128
+msgid "Select a table"
+msgstr "Velg en tabell"
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:785
+msgid "No tables found in this database."
+msgstr "Ingen tabeller funnet for denne databasen"
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:822
+msgid "Is a question missing?"
+msgstr "Mangler det et spørsmål?"
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:826
+msgid "Learn more about nested queries"
+msgstr "Lær mer om nøstede spørringer"
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:860
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:30
+msgid "Fields"
+msgstr "Felter"
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:938
+msgid "No segments were found."
+msgstr "Ingen segmenter ble funnet."
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:961
+msgid "Find a segment"
+msgstr "Finn et segment"
+
+#: frontend/src/metabase/query_builder/components/ExpandableString.jsx:46
+msgid "View less"
+msgstr "Vis mindre"
+
+#: frontend/src/metabase/query_builder/components/ExpandableString.jsx:56
+msgid "View more"
+msgstr "Vis mer"
+
+#: frontend/src/metabase/query_builder/components/ExtendedOptions.jsx:111
+msgid "Pick a field to sort by"
+msgstr "Velg et felt det skal sorteres etter"
+
+#: frontend/src/metabase/query_builder/components/ExtendedOptions.jsx:124
+msgid "Sort"
+msgstr "Sorter"
+
+#: frontend/src/metabase/query_builder/components/ExtendedOptions.jsx:193
+msgid "Row limit"
+msgstr "Radgrense"
+
+#: frontend/src/metabase/query_builder/components/FieldName.jsx:76
+msgid "Unknown Field"
+msgstr "Ukjent felt"
+
+#: frontend/src/metabase/query_builder/components/FieldName.jsx:79
+msgid "field"
+msgstr "felt"
+
+#: frontend/src/metabase/query_builder/components/Filter.jsx:113
+msgid "Matches"
+msgstr "Matcher"
+
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:152
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:160
+msgid "Add filters to narrow your answer"
+msgstr "Legg til filter for å snevre inn svaret ditt"
+
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:284
+msgid "Add a grouping"
+msgstr "Legg til en gruppering"
+
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:322
+#: frontend/src/metabase/visualizations/components/LineAreaBarChart.jsx:102
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:55
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:92
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:131
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:58
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:66
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:73
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:54
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:61
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:66
+#: frontend/src/metabase/visualizations/visualizations/Table.jsx:60
+#: frontend/src/metabase/visualizations/visualizations/Table.jsx:72
+msgid "Data"
+msgstr "Data"
+
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:352
+msgid "Filtered by"
+msgstr "Filtrert etter"
+
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:369
+msgid "View"
+msgstr "Vis"
+
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:386
+msgid "Grouped By"
+msgstr "Gruppert Etter"
+
+#: frontend/src/metabase/query_builder/components/LimitWidget.jsx:27
+msgid "None"
+msgstr "Ingen"
+
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:338
+msgid "This question is written in {0}."
+msgstr "Dette spørsmålet er skrevet i {0}."
+
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:346
+msgid "Hide Editor"
+msgstr "Skjul Redigeringsverktøy"
+
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:347
+msgid "Hide Query"
+msgstr "Skjul Spørring"
+
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:352
+msgid "Open Editor"
+msgstr "Åpne Redigeringsverktøy"
+
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:353
+msgid "Show Query"
+msgstr "Vis Spørring"
+
+#: frontend/src/metabase/query_builder/components/QueryDefinitionTooltip.jsx:25
+msgid "This metric has been retired.  It's no longer available for use."
+msgstr "Denne indikatoren har blitt forkastet. Den er ikke lenger tilgjengelig for bruk."
+
+#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:34
+#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:46
+msgid "Download full results"
+msgstr "Last ned fullt resultat"
+
+#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:35
+msgid "Download this data"
+msgstr "Last ned denne dataen"
+
+#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:46
+msgid "Warning"
+msgstr "Advarsel"
+
+#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:52
+msgid "Your answer has a large number of rows so it could take a while to download."
+msgstr "Ditt svar har veldig mange rader, så det kan ta en stund å laste ned."
+
+#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:53
+msgid "The maximum download size is 1 million rows."
+msgstr "Maks antall rader ved nedlasting er 1 million."
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:232
+msgid "Edit question"
+msgstr "Rediger spørsmål"
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:249
+msgid "SAVE CHANGES"
+msgstr "LAGRE ENDRINGER"
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:263
+msgid "CANCEL"
+msgstr "KANSELER"
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:276
+msgid "Move question"
+msgstr "Flytt spørsmål"
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:283
+msgid "Which collection should this be in?"
+msgstr "Hvilken kolleksjon skal denne være i?"
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:313
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:110
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorSidebar.jsx:83
+msgid "Variables"
+msgstr "Variabler"
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:432
+msgid "Learn about your data"
+msgstr "Lær mer om dataen din"
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:460
+msgid "Alerts are on"
+msgstr "Advarsler er på"
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:522
+msgid "started from"
+msgstr "startet fra"
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:48
+msgid "SQL"
+msgstr "SQL"
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:48
+msgid "native query"
+msgstr "lokal spørring"
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:52
+msgid "Not Supported"
+msgstr "Ikke støttet"
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:58
+msgid "View the {0}"
+msgstr "Se på {0}"
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:59
+msgid "Switch to {0}"
+msgstr "Bytt til {0}"
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:62
+msgid "Switch to Builder"
+msgstr "Bytt til bygger"
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:87
+msgid "{0} for this question"
+msgstr "{0} for dette spørsmålet"
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:111
+msgid "Convert this question to {0}"
+msgstr "Konverter dette spørsmålet til {0}"
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:122
+msgid "This question will take approximately {0} to refresh"
+msgstr "Dette spørsmålet vil ta cirka {0} å fornye"
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:131
+msgid "Updated {0}"
+msgstr "Oppdatert {0}"
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:141
+msgid "row"
+msgid_plural "rows"
+msgstr[0] "rad"
+msgstr[1] "rader"
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:148
+msgid "Showing first {0} {1}"
+msgstr "Viser først {0} {1}"
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:151
+msgid "Showing {0} {1}"
+msgstr "Viser {0} {1}"
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:281
+msgid "Doing science"
+msgstr "Vitenskaplig tenkinging pågår"
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:294
+msgid "If you give me some data I can show you something cool. Run a Query!"
+msgstr "Hvis du gir meg noe data, så kan jeg vise deg noe kult. Kjør en spørring!"
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:299
+msgid "How do I use this thing?"
+msgstr "Hvordan bruker jeg denne?"
+
+#: frontend/src/metabase/query_builder/components/RunButton.jsx:28
+msgid "Get Answer"
+msgstr "FÃ¥ svar"
+
+#: frontend/src/metabase/query_builder/components/SavedQuestionIntroModal.jsx:12
+msgid "It's okay to play around with saved questions"
+msgstr "Det er ok å leke med lagrede spørsmål"
+
+#: frontend/src/metabase/query_builder/components/SavedQuestionIntroModal.jsx:14
+msgid "You won't make any permanent changes to a saved question unless you click the edit icon in the top-right."
+msgstr "Endringer blir ikke lagret til lagrede spørsmål, hvis du ikke klikker endre ikonet oppe i høyre hjørnet."
+
+#: frontend/src/metabase/query_builder/components/SearchBar.jsx:28
+msgid "Search for"
+msgstr "Søk etter"
+
+#: frontend/src/metabase/query_builder/components/SelectionModule.jsx:158
+msgid "Advanced..."
+msgstr "Avansert..."
+
+#: frontend/src/metabase/query_builder/components/SelectionModule.jsx:167
+msgid "Sorry. Something went wrong."
+msgstr "Beklager. Noe gikk galt."
+
+#: frontend/src/metabase/query_builder/components/TimeGroupingPopover.jsx:40
+msgid "Group time by"
+msgstr "Grupper tid ved"
+
+#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:46
+msgid "Your question took too long"
+msgstr "Spørsmålet ditt tok for lang tid å svare på"
+
+#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:47
+msgid "We didn't get an answer back from your database in time, so we had to stop. You can try again in a minute, or if the problem persists, you can email an admin to let them know."
+msgstr "Vi fikk ikke ett svar fra din database i tid, så vi måtte stoppe. Du kan prøve igjen om ett minutt, hvis problemet fortsetter, kan du sende en e-post til administrator for å rapportere."
+
+#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:55
+msgid "We're experiencing server issues"
+msgstr "Vi har for øyeblikket tjenerproblemer"
+
+#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:56
+msgid "Try refreshing the page after waiting a minute or two. If the problem persists we'd recommend you contact an admin."
+msgstr "Prøv oppdatere siden etter å ha ventet ett minutt eller to. Hvis problemet fortsetter, ta kontakt med administrator."
+
+#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:88
+msgid "There was a problem with your question"
+msgstr "Det var ett problem med spørsmålet ditt"
+
+#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:89
+msgid "Most of the time this is caused by an invalid selection or bad input value. Double check your inputs and retry your query."
+msgstr "For det meste skyldes dette et ugyldig valg eller feil inntastet verdi. Sjekk input en gang til og prøv spørringen din igjen."
+
+#: frontend/src/metabase/query_builder/components/VisualizationResult.jsx:60
+msgid "This may be the answer you’re looking for. If not, try removing or changing your filters to make them less specific."
+msgstr "Dette kan være svaret du leter etter. Hvis ikke, prøv å fjern eller endre filterne for å gjøre det mindre spesifisert."
+
+#: frontend/src/metabase/query_builder/components/VisualizationResult.jsx:66
+msgid "You can also {0} when there are some results."
+msgstr "Du kan også {0} når det er noen resultater."
+
+#: frontend/src/metabase/query_builder/components/VisualizationResult.jsx:68
+msgid "get an alert"
+msgstr "få en varsel"
+
+#: frontend/src/metabase/query_builder/components/VisualizationResult.jsx:77
+msgid "Back to last run"
+msgstr "Tilbake til siste kjøring"
+
+#: frontend/src/metabase/query_builder/components/VisualizationSettings.jsx:53
+msgid "Visualization"
+msgstr "Visualisering"
+
+#: frontend/src/metabase/query_builder/components/dataref/DetailPane.jsx:17
+#: frontend/src/metabase/query_builder/components/dataref/TablePane.jsx:151
+msgid "No description set."
+msgstr "Ingen beskrivelse satt."
+
+#: frontend/src/metabase/query_builder/components/dataref/DetailPane.jsx:21
+msgid "Use for current question"
+msgstr "Bruk til dette spørsmålet"
+
+#: frontend/src/metabase/query_builder/components/dataref/DetailPane.jsx:33
+#: frontend/src/metabase/reference/components/UsefulQuestions.jsx:16
+msgid "Potentially useful questions"
+msgstr "Potensielt nyttige spørsmål"
+
+#: frontend/src/metabase/query_builder/components/dataref/FieldPane.jsx:156
+msgid "Group by {0}"
+msgstr "Grupper med {0}"
+
+#: frontend/src/metabase/query_builder/components/dataref/FieldPane.jsx:165
+msgid "Sum of all values of {0}"
+msgstr "Sum av alle verdier med {0}"
+
+#: frontend/src/metabase/query_builder/components/dataref/FieldPane.jsx:173
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:63
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:51
+msgid "All distinct values of {0}"
+msgstr "Alle distinkte verdier av {0}"
+
+#: frontend/src/metabase/query_builder/components/dataref/FieldPane.jsx:177
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:39
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:51
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:39
+msgid "Number of {0} grouped by {1}"
+msgstr "Nummer av {0} gruppert med {0}"
+
+#: frontend/src/metabase/query_builder/components/dataref/MainPane.jsx:11
+#: frontend/src/metabase/reference/databases/DatabaseSidebar.jsx:20
+#: frontend/src/metabase/reference/databases/FieldSidebar.jsx:27
+#: frontend/src/metabase/reference/databases/TableSidebar.jsx:23
+#: frontend/src/metabase/reference/guide/BaseSidebar.jsx:17
+#: frontend/src/metabase/reference/guide/BaseSidebar.jsx:19
+#: frontend/src/metabase/reference/metrics/MetricSidebar.jsx:20
+#: frontend/src/metabase/reference/segments/SegmentFieldSidebar.jsx:24
+#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:20
+msgid "Data Reference"
+msgstr "Datareferanse"
+
+#: frontend/src/metabase/query_builder/components/dataref/MainPane.jsx:13
+msgid "Learn more about your data structure to ask more useful questions"
+msgstr "Lær mer om din datastruktur for å spørre nyttigere spørsmål"
+
+#: frontend/src/metabase/query_builder/components/dataref/MetricPane.jsx:58
+#: frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx:84
+msgid "Could not find the table metadata prior to creating a new question"
+msgstr "Kunne ikke å finne tabellmetadata i før opprettelsen av nytt spørsmål"
+
+#: frontend/src/metabase/query_builder/components/dataref/MetricPane.jsx:80
+msgid "See {0}"
+msgstr "Se {0}"
+
+#: frontend/src/metabase/query_builder/components/dataref/MetricPane.jsx:94
+msgid "Metric Definition"
+msgstr "Indikatordefinisjon"
+
+#: frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx:118
+msgid "Filter by {0}"
+msgstr "Filtrer med {0}"
+
+#: frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx:127
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:36
+msgid "Number of {0}"
+msgstr "Tall av {0}"
+
+#: frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx:134
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:46
+msgid "See all {0}"
+msgstr "Se alle {0}"
+
+#: frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx:148
+msgid "Segment Definition"
+msgstr "Segmentdefinisjoner"
+
+#: frontend/src/metabase/query_builder/components/dataref/TablePane.jsx:50
+msgid "An error occurred loading the table"
+msgstr "En feil oppstod ved innlasting av tabell"
+
+#: frontend/src/metabase/query_builder/components/dataref/TablePane.jsx:74
+msgid "See the raw data for {0}"
+msgstr "Se rådata for {0}"
+
+#: frontend/src/metabase/query_builder/components/dataref/TablePane.jsx:205
+msgid "More"
+msgstr "Mer"
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:200
+msgid "Invalid expression"
+msgstr "Ugyldig uttrykk"
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:275
+msgid "unknown error"
+msgstr "ukjent feil"
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:46
+msgid "Field formula"
+msgstr "Felt formel"
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:57
+msgid "Think of this as being kind of like writing a formula in a spreadsheet program: you can use numbers, fields in this table, mathematical symbols like +, and some functions. So you could type something like Subtotal - Cost."
+msgstr "Tenk på dette litt som å skrive en formel i et regneark: Du kan bruke tall, felter i denne tabellen, matematiske symboler som +, og noen funksjoner. Så du kan skrive noe sånt som Subtotal - Kostnad."
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:62
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:126
+msgid "Learn more"
+msgstr "Lær mer"
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:66
+msgid "Give it a name"
+msgstr "Gi den ett navn"
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:72
+msgid "Something nice and descriptive"
+msgstr "Noe fint og informativt"
+
+#: frontend/src/metabase/query_builder/components/expressions/Expressions.jsx:60
+msgid "Add a custom field"
+msgstr "Legg til tilpasset felt"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:17
+msgid "Include {0}"
+msgstr "Inkluder {0}"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:19
+msgid "Case sensitive"
+msgstr "Shiftsensitiv"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:23
+msgid "today"
+msgstr "idag"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:24
+msgid "this week"
+msgstr "denne uken"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:25
+msgid "this month"
+msgstr "denne måneden"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:26
+msgid "this year"
+msgstr "dette året"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:27
+msgid "this minute"
+msgstr "dette minuttet"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:28
+msgid "this hour"
+msgstr "denne timen"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx:285
+msgid "not implemented {0}"
+msgstr "ikke implementert {0}"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx:286
+msgid "true"
+msgstr "riktig"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx:286
+msgid "false"
+msgstr "feil"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx:390
+msgid "Add filter"
+msgstr "Legg til filter"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterWidgetList.jsx:64
+msgid "Item"
+msgstr "Element"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:224
+msgid "Previous"
+msgstr "Tidligere"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:255
+msgid "Current"
+msgstr "Nåværende"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:282
+msgid "On"
+msgstr "PÃ¥"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/NumberPicker.jsx:47
+msgid "Enter desired number"
+msgstr "Skriv inn ønsket tall"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/SelectPicker.jsx:83
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:100
+msgid "Empty"
+msgstr "Tom"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/SelectPicker.jsx:116
+msgid "Find a value"
+msgstr "Finn en verdi"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/SpecificDatePicker.jsx:113
+msgid "Hide calendar"
+msgstr "Skjul kalender"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/SpecificDatePicker.jsx:113
+msgid "Show calendar"
+msgstr "Vis kalender"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/TextPicker.jsx:97
+msgid "You can enter multiple values separated by commas"
+msgstr "Du kan skrive inn flere verdier separert med komma"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/TextPicker.jsx:38
+msgid "Enter desired text"
+msgstr "Skriv inn ønsket tekst"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:83
+msgid "Try it"
+msgstr "Prøv den"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:105
+msgid "What's this for?"
+msgstr "Hva er dette til?"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:107
+msgid "Variables in native queries let you dynamically replace values in your queries using filter widgets or through the URL."
+msgstr "Variabler i lokale spørringer lar deg dynamisk erstatte verdier i spørringen ved bruk av filter eller gjennom nettlinken."
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:112
+msgid "{0} creates a variable in this SQL template called \"variable_name\". Variables can be given types in the side panel, which changes their behavior. All variable types other than \"Field Filter\" will automatically cause a filter widget to be placed on this question; with Field Filters, this is optional. When this filter widget is filled in, that value replaces the variable in the SQL template."
+msgstr "{0} oppretter en variabel i denne SQL-malen kalt \"variable_name\". "
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:121
+msgid "Field Filters"
+msgstr "Felt filtere"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:123
+msgid "Giving a variable the \"Field Filter\" type allows you to link SQL cards to dashboard filter widgets or use more types of filter widgets on your SQL question. A Field Filter variable inserts SQL similar to that generated by the GUI query builder when adding filters on existing columns."
+msgstr "Ved å gi en variabel \"Felt Filter\"-typen så kan du lenke SQL-spørsmålskort til filter-widgetsene på infotavler, eller bruke flere typer filter-widgets på ditt SQL-spørsmål. En \"Felt Filter\"-variabel setter inn SQL tilsvarende det som den grafiske spørsmålsbyggeren gjør når det legges til filtere på eksisterende kolonner."
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:126
+msgid "When adding a Field Filter variable, you'll need to map it to a specific field. You can then choose to display a filter widget on your question, but even if you don't, you can now map your Field Filter variable to a dashboard filter when adding this question to a dashboard. Field Filters should be used inside of a \"WHERE\" clause."
+msgstr "Når du legger til en \"Felt Filter\"-variabel, så må du tilordne den til et spesifikt felt. Du kan deretter velge å vise en filter-widget på spørsmålet ditt, men selv om du ikke gjør det, så kan du nå tilordne \"Felt Filter\"-variabelen din til et infotavle-filter når du legger dette spørsmålet til en infotavle. Felt Filtere bør brukes inni en \"WHERE\"-klausul."
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:130
+msgid "Optional Clauses"
+msgstr "Valgfrie klausuler"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:132
+msgid "brackets around a {0} create an optional clause in the template. If \"variable\" is set, then the entire clause is placed into the template. If not, then the entire clause is ignored."
+msgstr "firkant-parenteser rundt en {0} lager en valgfri klausul i malen. Hvis \"variabel\" er satt, da blir hele klausulen satt inn i malen. Ellers så blir hele klausulen ignorert."
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:142
+msgid "To use multiple optional clauses you can include at least one non-optional WHERE clause followed by optional clauses starting with \"AND\"."
+msgstr "For å bruke flere valgfrie klausuler kan du inkludere minst én ikke-valgfri WHERE-klausul etterfulgt av valgfrie klausuler som starter med \"AND\"."
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:154
+msgid "Read the full documentation"
+msgstr "Les hele dokumentasjonen"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:124
+msgid "Filter label"
+msgstr "Filter etikett"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:136
+msgid "Variable type"
+msgstr "Variabel type"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:145
+msgid "Text"
+msgstr "Tekst"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:147
+msgid "Date"
+msgstr "Dato"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:148
+msgid "Field Filter"
+msgstr "Felt filter"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:154
+msgid "Field to map to"
+msgstr "Felt å koble til"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:176
+msgid "Filter widget type"
+msgstr "Filter type"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:199
+msgid "Required?"
+msgstr "PÃ¥krevd?"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:210
+msgid "Default filter widget value"
+msgstr "Standard filter verdi"
+
+#: frontend/src/metabase/query_builder/containers/ArchiveQuestionModal.jsx:46
+msgid "Archive this question?"
+msgstr "Arkiver spørsmålet?"
+
+#: frontend/src/metabase/query_builder/containers/ArchiveQuestionModal.jsx:57
+msgid "This question will be removed from any dashboards or pulses using it."
+msgstr "Dette spørsmål vil bli slettet fra alle panel eller pulser den er i."
+
+#: frontend/src/metabase/query_builder/containers/QueryBuilder.jsx:136
+msgid "Question"
+msgstr "Spørsmål"
+
+#: frontend/src/metabase/questions/containers/AddToDashboard.jsx:11
+msgid "Pick a question to add"
+msgstr "Plukk ett spørsmål å legge til"
+
+#: frontend/src/metabase/reference/components/EditHeader.jsx:19
+msgid "You are editing this page"
+msgstr "Du kan redigere denne siden"
+
+#: frontend/src/metabase/reference/components/EditableReferenceHeader.jsx:101
+#: frontend/src/metabase/reference/components/ReferenceHeader.jsx:63
+msgid "See this {0}"
+msgstr "Se dette {0}"
+
+#: frontend/src/metabase/reference/components/EditableReferenceHeader.jsx:120
+msgid "A subset of"
+msgstr "En underdel av"
+
+#: frontend/src/metabase/reference/components/Field.jsx:47
+#: frontend/src/metabase/reference/components/Field.jsx:86
+#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:32
+#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:68
+msgid "Select a field type"
+msgstr "Velg en felt type"
+
+#: frontend/src/metabase/reference/components/Field.jsx:56
+#: frontend/src/metabase/reference/components/Field.jsx:71
+#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:41
+#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:57
+msgid "No field type"
+msgstr "Ingen felt type"
+
+#: frontend/src/metabase/reference/components/FieldToGroupBy.jsx:22
+msgid "by"
+msgstr "av"
+
+#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:25
+#: frontend/src/metabase/reference/databases/FieldList.jsx:152
+#: frontend/src/metabase/reference/segments/SegmentFieldList.jsx:153
+msgid "Field type"
+msgstr "Felt type"
+
+#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:72
+msgid "Select a Foreign Key"
+msgstr "Velg en ukjent nøkkel"
+
+#: frontend/src/metabase/reference/components/Formula.jsx:53
+msgid "View the {0} formula"
+msgstr "Vis {0} formelen"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:80
+msgid "Why this {0} is important"
+msgstr "Hvorfor {0} er viktig"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:81
+msgid "Why this {0} is interesting"
+msgstr "Hvorfor {0} er interessant"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:87
+msgid "Nothing important yet"
+msgstr "Ingenting viktig enda"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:88
+#: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:168
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:233
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:211
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:215
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:219
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:229
+msgid "Nothing interesting yet"
+msgstr "Ingenting interessant enda"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:93
+msgid "Things to be aware of about this {0}"
+msgstr "Ting å være klar over om denne {0}"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:97
+#: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:178
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:243
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:221
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:225
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:229
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:239
+msgid "Nothing to be aware of yet"
+msgstr "Ingenting å være klar over enda"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:103
+msgid "Explore this metric"
+msgstr "Utforsk denne indikatoren"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:105
+msgid "View this metric"
+msgstr "Vis denne indikatoren"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:112
+msgid "By {0}"
+msgstr "Med {0}"
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:146
+msgid "Remove item"
+msgstr "Slett element"
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:155
+msgid "Why is this dashboard the most important?"
+msgstr "Hvorfor er dette panelet det viktigste?"
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:156
+msgid "What is useful or interesting about this {0}?"
+msgstr "Hva er nyttig eller interessant for denne {0}"
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:160
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:174
+msgid "Write something helpful here"
+msgstr "Skriv noe hjelpsomt her"
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:169
+msgid "Is there anything users of this dashboard should be aware of?"
+msgstr "Er det noe brukere av dette panelet bør vite om?"
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:170
+msgid "Anything users should be aware of about this {0}?"
+msgstr "Er det noe brukerne skal være klar over angående disse {0}"
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:182
+#: frontend/src/metabase/reference/components/MetricImportantFieldsDetail.jsx:26
+msgid "Which 2-3 fields do you usually group this metric by?"
+msgstr "Hvilke 2-3 felter grupperer du vanligvis denne indikatoren etter?"
+
+#: frontend/src/metabase/reference/components/GuideHeader.jsx:23
+msgid "This is the perfect place to start if you’re new to your company’s data, or if you just want to check in on what’s going on."
+msgstr "Dette er et perfekt sted å starte hvis bedriftens data er nye for deg, eller du ønsker å se hva som foregår"
+
+#: frontend/src/metabase/reference/components/MetricImportantFieldsDetail.jsx:65
+msgid "Most useful fields to group this metric by"
+msgstr "Nyttigste feltene å gruppere denne indikatoren etter"
+
+#: frontend/src/metabase/reference/components/RevisionMessageModal.jsx:32
+msgid "Reason for changes"
+msgstr "Grunn til endring"
+
+#: frontend/src/metabase/reference/components/RevisionMessageModal.jsx:36
+msgid "Leave a note to explain what changes you made and why they were required"
+msgstr "Legg igjen et notat for å forklare endringene du gjorde og hvorfor de var nødvendige"
+
+#: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:166
+msgid "Why this database is interesting"
+msgstr "Hvorfor denne databasen er interessant"
+
+#: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:176
+msgid "Things to be aware of about this database"
+msgstr "Ting å være klar over om denne databasen"
+
+#: frontend/src/metabase/reference/databases/DatabaseList.jsx:46
+#: frontend/src/metabase/reference/guide/BaseSidebar.jsx:39
+msgid "Databases and tables"
+msgstr "Databaser og tabeller"
+
+#: frontend/src/metabase/reference/databases/DatabaseSidebar.jsx:27
+#: frontend/src/metabase/reference/databases/FieldSidebar.jsx:38
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:170
+#: frontend/src/metabase/reference/databases/TableSidebar.jsx:31
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:184
+#: frontend/src/metabase/reference/metrics/MetricSidebar.jsx:27
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:188
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:187
+#: frontend/src/metabase/reference/segments/SegmentFieldSidebar.jsx:31
+#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:27
+msgid "Details"
+msgstr "Detaljer"
+
+#: frontend/src/metabase/reference/databases/DatabaseSidebar.jsx:33
+#: frontend/src/metabase/reference/databases/TableList.jsx:111
+msgid "Tables in {0}"
+msgstr "Tabeller i {0}"
+
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:222
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:200
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:218
+msgid "Actual name in database"
+msgstr "Reelt navn i database"
+
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:231
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:227
+msgid "Why this field is interesting"
+msgstr "Hvorfor er dette feltet interessant"
+
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:241
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:237
+msgid "Things to be aware of about this field"
+msgstr "Ting å være klar over om dette feltet"
+
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:253
+#: frontend/src/metabase/reference/databases/FieldList.jsx:155
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:249
+#: frontend/src/metabase/reference/segments/SegmentFieldList.jsx:156
+msgid "Data type"
+msgstr "Datatype"
+
+#: frontend/src/metabase/reference/databases/FieldList.jsx:39
+#: frontend/src/metabase/reference/segments/SegmentFieldList.jsx:39
+msgid "Fields in this table will appear here as they're added"
+msgstr "Felter i denne tabellen vil vises her når de blir lagt til"
+
+#: frontend/src/metabase/reference/databases/FieldList.jsx:134
+#: frontend/src/metabase/reference/segments/SegmentFieldList.jsx:135
+msgid "Fields in {0}"
+msgstr "Felter i {0}"
+
+#: frontend/src/metabase/reference/databases/FieldList.jsx:149
+#: frontend/src/metabase/reference/segments/SegmentFieldList.jsx:150
+msgid "Field name"
+msgstr "Feltnavn"
+
+#: frontend/src/metabase/reference/databases/FieldSidebar.jsx:46
+msgid "X-ray this field"
+msgstr "X-ray dette feltet"
+
+#: frontend/src/metabase/reference/databases/NoDatabasesEmptyState.jsx:8
+msgid "Metabase is no fun without any data"
+msgstr "Metabase er ikke morsomt uten data"
+
+#: frontend/src/metabase/reference/databases/NoDatabasesEmptyState.jsx:9
+msgid "Your databases will appear here once you connect one"
+msgstr "Dine databaser vil vises her når du har koblet til"
+
+#: frontend/src/metabase/reference/databases/NoDatabasesEmptyState.jsx:10
+msgid "Databases will appear here once your admins have added some"
+msgstr "Databasene vil vises her når en av administratorene har lagt til"
+
+#: frontend/src/metabase/reference/databases/NoDatabasesEmptyState.jsx:12
+msgid "Connect a database"
+msgstr "Koble til en database"
+
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:38
+msgid "Count of {0}"
+msgstr "Telling av {0}"
+
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:47
+msgid "See raw data for {0}"
+msgstr "Se rådata for {0}"
+
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:209
+msgid "Why this table is interesting"
+msgstr "Hvorfor er denne tabellen interessant"
+
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:219
+msgid "Things to be aware of about this table"
+msgstr "Ting å være klar over om denne tabellen"
+
+#: frontend/src/metabase/reference/databases/TableList.jsx:30
+msgid "Tables in this database will appear here as they're added"
+msgstr "Tabellen i denne databasen vises her etter hvert som de blir lagt til"
+
+#: frontend/src/metabase/reference/databases/TableQuestions.jsx:34
+msgid "Questions about this table will appear here as they're added"
+msgstr "Spørsmål om denne tabellen vises her etter hvert som de blir lagt til"
+
+#: frontend/src/metabase/reference/databases/TableQuestions.jsx:71
+#: frontend/src/metabase/reference/metrics/MetricQuestions.jsx:75
+#: frontend/src/metabase/reference/metrics/MetricSidebar.jsx:33
+#: frontend/src/metabase/reference/segments/SegmentQuestions.jsx:74
+msgid "Questions about {0}"
+msgstr "Spørsmål om {0}"
+
+#: frontend/src/metabase/reference/databases/TableQuestions.jsx:95
+#: frontend/src/metabase/reference/metrics/MetricQuestions.jsx:99
+#: frontend/src/metabase/reference/segments/SegmentQuestions.jsx:98
+msgid "Created {0} by {1}"
+msgstr "Laget {0} av {1}"
+
+#: frontend/src/metabase/reference/databases/TableSidebar.jsx:37
+msgid "Fields in this table"
+msgstr "Felter i denne tabellen"
+
+#: frontend/src/metabase/reference/databases/TableSidebar.jsx:45
+msgid "Questions about this table"
+msgstr "Spørsmål om denne tabellen"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:157
+msgid "Help your team get started with your data."
+msgstr "Hjelp ditt lag i gang med dataene dine."
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:159
+msgid "Show your team what’s most important by choosing your top dashboard, metrics, and segments."
+msgstr "Vis ditt lag hva som er viktigst ved å velge din viktigste infotavle, indikatorer og segmenter."
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:165
+msgid "Get started"
+msgstr "GÃ¥ igang"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:173
+msgid "Our most important dashboard"
+msgstr "VÃ¥rt viktigste panel"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:188
+msgid "Numbers that we pay attention to"
+msgstr "Tall vi følger med på"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:213
+msgid "Metrics are important numbers your company cares about. They often represent a core indicator of how the business is performing."
+msgstr "Indikatorer er viktige tall som din bedrift bryr seg om. De er ofte et mål på hvordan det går med bedriften."
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:221
+msgid "See all metrics"
+msgstr "Se alle indikatorer"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:235
+msgid "Segments and tables"
+msgstr "Segmenter og tabeller"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:236
+msgid "Tables"
+msgstr "Tabeller"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:262
+msgid "Segments and tables are the building blocks of your company's data. Tables are collections of the raw information while segments are specific slices with specific meanings, like {0}"
+msgstr "Segmenter og tabeller er byggeklossene i din organisasjons data. Tabeller er samlinger av rå informasjon mens segmenter er spesifikke stykker med spesiell betydning, som for eksempel {0}"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:267
+msgid "Tables are the building blocks of your company's data."
+msgstr "Tabeller er byggeblokkene i bedriftens data."
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:277
+msgid "See all segments"
+msgstr "Se alle segmenter"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:293
+msgid "See all tables"
+msgstr "Se alle tabeller"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:301
+msgid "Other things to know about our data"
+msgstr "Andre ting å vite om dataen din"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:302
+msgid "Find out more"
+msgstr "Finn ut mer"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:307
+msgid "A good way to get to know your data is by spending a bit of time exploring the different tables and other info available to you. It may take a while, but you'll start to recognize names and meanings over time."
+msgstr "En fin måte å bli kjent med dataene fine er å bruke litt tid på å utforske de forskjellige tabellene og annen informasjon som er tilgjengelig. Det kan ta litt tid, men etter hvert vil du begynne å kjenne igjen navn og hva ting betyr."
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:313
+msgid "Explore our data"
+msgstr "Utforsk dataene våre"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:321
+msgid "Have questions?"
+msgstr "Har du spørsmål?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:326
+msgid "Contact {0}"
+msgstr "Kontakt {0}"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:248
+msgid "Help new Metabase users find their way around."
+msgstr "Hjelp nye Metabase brukere finne veien igjennom."
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:251
+msgid "The Getting Started guide highlights the dashboard, metrics, segments, and tables that matter most, and informs your users of important things they should know before digging into the data."
+msgstr "\"Kom igang\"-veilederen fremhever infotavlen, beregninger, segmenter, og de mest viktige tabellene, og informerer brukerne dine om ting de bør vite før de begynner å grave i dataene."
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:258
+msgid "Is there an important dashboard for your team?"
+msgstr "Er det en viktig infotavle for ditt lag?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:260
+msgid "Create a dashboard now"
+msgstr "Lag ett panel nå"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:266
+msgid "What is your most important dashboard?"
+msgstr "Hva er ditt viktigste panel?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:285
+msgid "Do you have any commonly referenced metrics?"
+msgstr "Har du noen ofte brukte indikatorer?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:287
+msgid "Learn how to define a metric"
+msgstr "Lær hvordan man definerer en indikator"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:300
+msgid "What are your 3-5 most commonly referenced metrics?"
+msgstr "Hva er de 3-5 mest brukte indikatorene?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:344
+msgid "Add another metric"
+msgstr "Legg til enda en indikator"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:357
+msgid "Do you have any commonly referenced segments or tables?"
+msgstr "Har du noen segmenter eller tabeller som det ofte refereres til?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:359
+msgid "Learn how to create a segment"
+msgstr "Lær hvordan man lager ett segment"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:372
+msgid "What are 3-5 commonly referenced segments or tables that would be useful for this audience?"
+msgstr "Hva er 3-5 segmenter eller tabeller som det ofte henvises til og som vil være nyttige for dette publikumet?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:418
+msgid "Add another segment or table"
+msgstr "Legg til nytt segment eller tabell"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:427
+msgid "Is there anything your users should understand or know before they start accessing the data?"
+msgstr "Er det noe brukerne dine burde forstå eller vite om før de begynner å se på dataene?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:433
+msgid "What should a user of this data know before they start accessing it?"
+msgstr "Hva burde en bruker vite før de begynner å se på datene? "
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:437
+msgid "E.g., expectations around data privacy and use, common pitfalls or misunderstandings, information about data warehouse performance, legal notices, etc."
+msgstr "F.eks., forventninger rundt personvern og bruk av data, vanlige fallgruver eller misforståelser, informasjon om ytelsen til datavarehuset, juridiske merknader, osv."
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:448
+msgid "Is there someone your users could contact for help if they're confused about this guide?"
+msgstr "Er det noen som brukere kan kontakte for å få hjelp dersom de har noen spørsmål angående denne veilederen?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:457
+msgid "Who should users contact for help if they're confused about this data?"
+msgstr "Hvem skal brukere kontakte for å få hjelp dersom de har noen spørsmål om disse dataene?"
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:75
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:95
+msgid "Please enter a revision message"
+msgstr "Vennligst oppgi en revisjonsmelding"
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:213
+msgid "Why this Metric is interesting"
+msgstr "Hvorfor denne indikatoren er interessant"
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:223
+msgid "Things to be aware of about this Metric"
+msgstr "Ting å være klar over med denne indikatoren"
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:233
+msgid "How this Metric is calculated"
+msgstr "Hvordan denne indikatoren er beregnet"
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:235
+msgid "Nothing on how it's calculated yet"
+msgstr "Ingenting om hvordan det er beregnet enda"
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:293
+msgid "Other fields you can group this metric by"
+msgstr "Andre felter du kan gruppere denne indikatoren etter"
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:294
+msgid "Fields you can group this metric by"
+msgstr "Felter du kan gruppere denne indikatoren etter"
+
+#: frontend/src/metabase/reference/metrics/MetricList.jsx:23
+msgid "Metrics are the official numbers that your team cares about"
+msgstr "Indikatorer er de offisielle tallene som ditt lag bryr seg om"
+
+#: frontend/src/metabase/reference/metrics/MetricList.jsx:25
+msgid "Metrics will appear here once your admins have created some"
+msgstr "Indikatorer vil dukke opp her når administratorene har laget noen"
+
+#: frontend/src/metabase/reference/metrics/MetricList.jsx:27
+msgid "Learn how to create metrics"
+msgstr "Lær mer om hvordan man lager indikatorer"
+
+#: frontend/src/metabase/reference/metrics/MetricQuestions.jsx:35
+msgid "Questions about this metric will appear here as they're added"
+msgstr "Spørsmål om denne indikatoren vil dukke opp her ettersom de blir lagt til"
+
+#: frontend/src/metabase/reference/metrics/MetricRevisions.jsx:29
+msgid "There are no revisions for this metric"
+msgstr "Det er ingen revisjoner for denne indikatoren"
+
+#: frontend/src/metabase/reference/metrics/MetricRevisions.jsx:88
+#: frontend/src/metabase/reference/metrics/MetricSidebar.jsx:47
+#: frontend/src/metabase/reference/segments/SegmentRevisions.jsx:88
+msgid "Revision history for {0}"
+msgstr "Revisjonshistorikk for {0}"
+
+#: frontend/src/metabase/reference/metrics/MetricSidebar.jsx:39
+#, fuzzy
+msgid "X-ray this metric"
+msgstr "Gjør en X-ray på denne indikatoren"
+
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:217
+msgid "Why this Segment is interesting"
+msgstr "Hvorfor dette Segmentet er interessant"
+
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:227
+msgid "Things to be aware of about this Segment"
+msgstr "Ting å være klar over når det gjelder dette Segmentet"
+
+#: frontend/src/metabase/reference/segments/SegmentList.jsx:23
+msgid "Segments are interesting subsets of tables"
+msgstr "Segmenter er interessante subsett av tabeller"
+
+#: frontend/src/metabase/reference/segments/SegmentList.jsx:24
+msgid "Defining common segments for your team makes it even easier to ask questions"
+msgstr "Å definere vanlige segmenter for ditt lag gjør det enda enklere å stille spørsmål"
+
+#: frontend/src/metabase/reference/segments/SegmentList.jsx:25
+msgid "Segments will appear here once your admins have created some"
+msgstr "Segmenter vil vises her når en administrator har laget noen"
+
+#: frontend/src/metabase/reference/segments/SegmentList.jsx:27
+msgid "Learn how to create segments"
+msgstr "Lær hvordan man lager segmenter"
+
+#: frontend/src/metabase/reference/segments/SegmentQuestions.jsx:35
+msgid "Questions about this segment will appear here as they're added"
+msgstr "Spørsmål om dette segmentet vil vises her når de blir lagt til"
+
+#: frontend/src/metabase/reference/segments/SegmentRevisions.jsx:29
+msgid "There are no revisions for this segment"
+msgstr "Det er ingen revisjoner for dette segmentet"
+
+#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:33
+msgid "Fields in this segment"
+msgstr "Felter i dette segmentet"
+
+#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:39
+msgid "Questions about this segment"
+msgstr "Spørsmål om dette segmentet"
+
+#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:45
+msgid "X-ray this segment"
+msgstr "Gjør en X-ray av dette segmentet"
+
+#: frontend/src/metabase/routes.jsx:182
+msgid "Login"
+msgstr "Logg inn"
+
+#: frontend/src/metabase/nav/containers/Navbar.jsx:130
+#: frontend/src/metabase/routes.jsx:198
+msgid "Search"
+msgstr "Søk"
+
+#: frontend/src/metabase/routes.jsx:217
+msgid "Dashboard"
+msgstr "Panel"
+
+#: frontend/src/metabase/routes.jsx:227
+msgid "New Question"
+msgstr "Nytt spørsmål"
+
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:125
+msgid "Select the type of Database you use"
+msgstr "Velg hvilken database type du bruker"
+
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:141
+msgid "Add your data"
+msgstr "Legg til dataen din"
+
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:145
+msgid "I'll add my own data later"
+msgstr "Jeg vil legge til data senere"
+
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:146
+msgid "Connecting to {0}"
+msgstr "Kobler til {0}"
+
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:165
+msgid "You’ll need some info about your database, like the username and password. If you don’t have that right now, Metabase also comes with a sample dataset you can get started with."
+msgstr "Du trenger noe informasjon om databasen din, som for eksempel brukernavn og passord. Hvis du ikke har det akkurat nå, så kommer Metabase med et eksempel-datasett som du kan begynne med."
+
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:196
+msgid "I'll add my data later"
+msgstr "Jeg vil legge til data senere"
+
+#: frontend/src/metabase/setup/components/DatabaseSchedulingStep.jsx:41
+msgid "Control automatic scans"
+msgstr "Kontroller automatisk skanninger"
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:53
+msgid "Usage data preferences"
+msgstr "Innstillinger for bruk av data"
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:56
+msgid "Thanks for helping us improve"
+msgstr "Takk for hjelpen"
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:57
+msgid "We won't collect any usage events"
+msgstr "Vi vil ikke samle inn noen brukshendelser"
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:76
+msgid "In order to help us improve Metabase, we'd like to collect certain data about usage through Google Analytics."
+msgstr "For å kunne hjelpe oss med å forbedre Metabase, så vil vi gjerne samle inn enkelte data for bruk gjennom Google Analytics"
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:85
+msgid "Here's a full list of everything we track and why."
+msgstr "Her er en liste over det vi sporer og hvorfor."
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:98
+msgid "Allow Metabase to anonymously collect usage events"
+msgstr "Tillat Metabase å samle inn bruksdata anonymt"
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:105
+msgid "Metabase {0} collects anything about your data or question results."
+msgstr "Metabase {0} samler alt om dine data eller resultat på spørsmål."
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:106
+msgid "never"
+msgstr "aldri"
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:108
+msgid "All collection is completely anonymous."
+msgstr "All innsamling av data er anonymt."
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:110
+msgid "Collection can be turned off at any point in your admin settings."
+msgstr "Samling kan skrus av når som helst i admin-innstillingene"
+
+#: frontend/src/metabase/setup/components/Setup.jsx:45
+msgid "If you feel stuck"
+msgstr "Hvis du føler at du sitter fast"
+
+#: frontend/src/metabase/setup/components/Setup.jsx:52
+msgid "our getting started guide"
+msgstr "vår oppstartsguide"
+
+#: frontend/src/metabase/setup/components/Setup.jsx:53
+msgid "is just a click away."
+msgstr "er bare ett klikk unna"
+
+#: frontend/src/metabase/setup/components/Setup.jsx:95
+msgid "Welcome to Metabase"
+msgstr "Velkommen til Metabase"
+
+#: frontend/src/metabase/setup/components/Setup.jsx:96
+msgid "Looks like everything is working. Now let’s get to know you, connect to your data, and start finding you some answers!"
+msgstr "Ser ut som alt virker. La oss bli kjent, koble til dataen din og start med å finne noen svar!"
+
+#: frontend/src/metabase/setup/components/Setup.jsx:100
+msgid "Let's get started"
+msgstr "LÃ¥ oss starte"
+
+#: frontend/src/metabase/setup/components/Setup.jsx:145
+msgid "You're all set up!"
+msgstr "Alt er klart og satt opp!"
+
+#: frontend/src/metabase/setup/components/Setup.jsx:156
+msgid "Take me to Metabase"
+msgstr "Ta meg med til Metabase"
+
+#: frontend/src/metabase/setup/components/UserStep.jsx:155
+msgid "What should we call you?"
+msgstr "Hva skal vi kalle deg?"
+
+#: frontend/src/metabase/setup/components/UserStep.jsx:156
+msgid "Hi, {0}. nice to meet you!"
+msgstr "Hei, {0}. Hyggelig å møte deg!"
+
+#: frontend/src/metabase/setup/components/UserStep.jsx:243
+msgid "Create a password"
+msgstr "Lag ett nytt passord"
+
+#: frontend/src/metabase/setup/components/UserStep.jsx:259
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:116
+msgid "Shhh..."
+msgstr "Hyyysj..."
+
+#: frontend/src/metabase/setup/components/UserStep.jsx:269
+msgid "Confirm password"
+msgstr "Gjenta passord"
+
+#: frontend/src/metabase/setup/components/UserStep.jsx:278
+msgid "Shhh... but one more time so we get it right"
+msgstr "Hyyysj... Men en gang til å vi kan få det riktig"
+
+#: frontend/src/metabase/setup/components/UserStep.jsx:287
+msgid "Your company or team name"
+msgstr "Navn på din bedrift eller ditt lag"
+
+#: frontend/src/metabase/setup/components/UserStep.jsx:296
+msgid "Department of awesome"
+msgstr "Avdeling råkul"
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:26
+msgid "Metabot is admiring your integers…"
+msgstr "MetaBot beundrer dine heltall..."
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:27
+msgid "Metabot is performing billions of differential equations…"
+msgstr "MetaBot går igjennom flere milliarder av forskjellige ligninger..."
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:28
+msgid "Metabot is doing science…"
+msgstr "MetaBot gjør noen vitenskaplige kalkulasjoner..."
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:29
+msgid "Metabot is checking out your metrics…"
+msgstr "Metabot sjekker indikatoren din..."
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:30
+msgid "Metabot is looking for trends and outliers…"
+msgstr "MwtaBot ser etter trender og uthevelser..."
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:31
+#, fuzzy
+msgid "Metabot is consulting the quantum abacus…"
+msgstr "MetaBot konsulterer med moder jord..."
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:32
+msgid "Metabot is feeling pretty good about all this…"
+msgstr "MetaBot føler seg bra med dette..."
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:52
+msgid "We’ll show you some interesting explorations of your data in\n"
+"just a few minutes."
+msgstr "Vi vil vise deg noen interessante måter å se på dine data om noen øyeblikk."
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:72
+msgid "This seems to be taking a while. In the meantime, you can check out one of these example explorations to see what Metabase can do for you."
+msgstr "Dette ser ut til å ta en stund. I mellomtiden kan du se på en av disse eksemplene for å se hva Metabase kan gjøre for deg."
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:86
+msgid "I took a look at the data you just connected, and I have some explorations of interesting things I found. Hope you like them!"
+msgstr "Jeg har akkurat kikke litt på dataene du koblet på, og har noen interessante ting jeg fant. Håper du liker det!"
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:98
+msgid "I'm done exploring for now"
+msgstr "Jeg er ferdig med å utforske nå"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:20
+msgid "Welcome to the Query Builder!"
+msgstr "Velkommen til spørringsbyggeren!"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:22
+msgid "The Query Builder lets you assemble questions (or \"queries\") to ask about your data."
+msgstr "Spørringsbyggeren lar deg sette sammen spørsmål (eller \"spørringer\") for å se på din data."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:26
+msgid "Tell me more"
+msgstr "Fortell meg mer"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:43
+msgid "Start by picking the table with the data that you have a question about."
+msgstr "Start med å velge den tabellen med data du har spørsmål om."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:45
+msgid "Go ahead and select the \"Orders\" table from the dropdown menu."
+msgstr "Velg \"Orders\"-tabellen fra nedtrekksmenyen."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:78
+msgid "Filter your data to get just what you want."
+msgstr "Filtrer dataene fine for å få akkurat det du vil ha."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:79
+msgid "Click the plus button and select the \"Created At\" field."
+msgstr "Trykk på pluss-knappen og velg \"Created At\"-feltet."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:93
+msgid "Here we can pick how many days we want to see data for, try 10"
+msgstr "Her kan vi velge hvor mange dager vi ønsker å se data for. Prøv 10."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:116
+msgid "Here's where you can choose to add or average your data, count the number of rows in the table, or just view the raw data."
+msgstr "Her kan du velge å summere eller ta et gjennomsnitt av dine data, telle antall rader i tabellen, eller bare å vise rådataene."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:118
+msgid "Try it: click on <strong>Raw Data</strong> to change it to <strong>Count of rows</strong> so we can count how many orders there are in this table."
+msgstr "Prøv i vei: trykk på <strong>Rådata</strong> for å endre det til <strong>Antall rader</strong> sånn at vi kan telle hvor mange ordre det er i denne tabellen."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:142
+msgid "Add a grouping to break out your results by category, day, month, and more."
+msgstr "Legg til en gruppering for å bryte resultatene dine opp i kategori, dag, måned, og mere."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:144
+msgid "Let's do it: click on <strong>Add a grouping</strong>, and choose <strong>Created At: by Week</strong>."
+msgstr "La oss gjøre det: trykk på <strong>Legg til en gruppering</strong>, og velg <strong>Created At: etter Uke</strong>."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:152
+msgid "Click on \"by day\" to change it to \"Week.\""
+msgstr "Trykk på \"etter dag\" og bytt til \"Uke.\""
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:173
+msgid "Run Your Query."
+msgstr "Kjør spørringen din."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:175
+msgid "You're doing so well! Click <strong>Run query</strong> to get your results!"
+msgstr "Du gjør det bra! Trykk <strong>Kjør spørring</strong> for å få resultatet ditt"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:192
+msgid "You can view your results as a chart instead of a table."
+msgstr "Du kan se resultatene som en graf i stedet for en tabell"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:194
+msgid "Everbody likes charts! Click the <strong>Visualization</strong> dropdown and select <strong>Line</strong>."
+msgstr "Alle liker diagrammer! Trykk på <strong>Visualisering</strong>-nedtrekksmenyen og velg <strong>Linje</strong>."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:216
+msgid "Well done!"
+msgstr "Bra gjort!"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:218
+msgid "That's all! If you still have questions, check out our"
+msgstr "Det var alt! Hvis du fortsatt har noen spørsmål, sjekk ut vår"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:223
+msgid "User's Guide"
+msgstr "Brukerveiledning"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:223
+msgid "Have fun exploring your data!"
+msgstr "Kos deg med å utforske dataene dine!"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:226
+msgid "Thanks"
+msgstr "Takk"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:235
+msgid "Save Your Questions"
+msgstr "Lagre dine spørsmål"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:237
+msgid "By the way, you can save your questions so you can refer to them later. Saved Questions can also be put into dashboards or Pulses."
+msgstr "Forresten, du kan lagre spørsmålene dine sånn at du kan referere til dem senere. Lagrede spørsmål kan også legges på infotavler eller pulser."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:241
+msgid "Sounds good"
+msgstr "Høres bra ut"
+
+#: frontend/src/metabase/tutorial/Tutorial.jsx:248
+msgid "Whoops!"
+msgstr "Oops!"
+
+#: frontend/src/metabase/tutorial/Tutorial.jsx:249
+msgid "Sorry, it looks like something went wrong. Please try restarting the tutorial in a minute."
+msgstr "Beklager, det ser ut til at noe gikk galt. Vær så snill og start opplæringsveilederen om et øyeblikk."
+
+#: frontend/src/metabase/user/actions.js:34
+msgid "Password updated successfully!"
+msgstr "Passord vellykket oppdatert!"
+
+#: frontend/src/metabase/user/actions.js:53
+msgid "Account updated successfully!"
+msgstr "Konto vellykket oppdatert!"
+
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:107
+msgid "Current password"
+msgstr "Nåværende passord"
+
+#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:137
+msgid "Sign in with Google Email address"
+msgstr "Logg inn med Google E-postadresse"
+
+#: frontend/src/metabase/user/components/UserSettings.jsx:65
+msgid "User Details"
+msgstr "Bruker detaljer"
+
+#: frontend/src/metabase/visualizations/components/ChartSettings.jsx:225
+msgid "Reset to defaults"
+msgstr "Tilbakestill til standard"
+
+#: frontend/src/metabase/visualizations/components/ChoroplethMap.jsx:123
+msgid "unknown map"
+msgstr "ukjent kart"
+
+#: frontend/src/metabase/visualizations/components/LeafletGridHeatMap.jsx:26
+msgid "Grid map requires binned longitude/latitude."
+msgstr "Rutekartet krever grupperte lengdegrader/breddegrader."
+
+#: frontend/src/metabase/visualizations/components/LegendVertical.jsx:112
+msgid "more"
+msgstr "mer"
+
+#: frontend/src/metabase/visualizations/components/LineAreaBarChart.jsx:101
+msgid "Which fields do you want to use for the X and Y axes?"
+msgstr "Hvilke felter vil du bruke for X- og Y-aksene?"
+
+#: frontend/src/metabase/visualizations/components/LineAreaBarChart.jsx:103
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:59
+msgid "Choose fields"
+msgstr "Velg felt"
+
+#: frontend/src/metabase/visualizations/components/PinMap.jsx:204
+msgid "Save as default view"
+msgstr "Lagre som standardvisning"
+
+#: frontend/src/metabase/visualizations/components/PinMap.jsx:226
+msgid "Draw box to filter"
+msgstr "Tegn en boks for å filtrere"
+
+#: frontend/src/metabase/visualizations/components/PinMap.jsx:226
+msgid "Cancel filter"
+msgstr "Kanseler filter"
+
+#: frontend/src/metabase/visualizations/components/PinMap.jsx:47
+msgid "Pin Map"
+msgstr "Kartnål-kart"
+
+#: frontend/src/metabase/visualizations/components/TableInteractive.jsx:423
+msgid "Unset"
+msgstr "Ikke satt"
+
+#: frontend/src/metabase/visualizations/components/TableSimple.jsx:227
+msgid "Rows {0}-{1} of {2}"
+msgstr "Rader {0}-{1} av {2}"
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:184
+msgid "Data truncated to {0} rows."
+msgstr "Data redusert til {0} rader."
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:349
+msgid "Could not find visualization"
+msgstr "Kunne ikke finne visualisering"
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:356
+msgid "Could not display this chart with this data."
+msgstr "Kunne ikke vise grafen med denne dataen."
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:454
+msgid "No results!"
+msgstr "Ingen resultater!"
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:475
+msgid "Still Waiting..."
+msgstr "Venter fortsatt..."
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:478
+msgid "This usually takes an average of {0}."
+msgstr "Dette tar vanligvis gjennomsnittlig {0}."
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:484
+msgid "(This is a bit long for a dashboard)"
+msgstr "(dette er litt for langt for ett panel)"
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:488
+msgid "This is usually pretty fast but seems to be taking awhile right now."
+msgstr "Dette er vanligvis ganske kjapt men det ser ut som det tar lang tid nå."
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingFieldPicker.jsx:14
+msgid "Select a field"
+msgstr "Velg ett felt"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingFieldsPicker.jsx:42
+msgid "error"
+msgstr "feil"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingOrderedColumns.jsx:107
+msgid "Click and drag to change their order"
+msgstr "Klikk og dra for å endre rekkefølgen"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingOrderedColumns.jsx:119
+msgid "Add fields from the list below"
+msgstr "Legg til felter fra listen under"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:24
+msgid "less than"
+msgstr "mindre enn"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:25
+msgid "greater than"
+msgstr "mer enn"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:26
+msgid "less than or equal to"
+msgstr "mindre enn eller lik som"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:27
+msgid "greater than or equal to"
+msgstr "større enn eller lik som"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:28
+msgid "equal to"
+msgstr "lik som"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:29
+msgid "not equal to"
+msgstr "ikke lik som"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:169
+msgid "Conditional formatting"
+msgstr "Betinget formatering"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:171
+msgid "You can add rules to make the cells in this table change color if\n"
+"they meet certain conditions."
+msgstr "Du kan legge til regler for å lage celler i denne tabellen bytte farger avhengig av verdiene."
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:181
+msgid "Add a rule"
+msgstr "Legg til regel"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:186
+msgid "Rules will be applied in this order"
+msgstr "Regler vil bli lagt til i denne sorteringen"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:187
+msgid "Click and drag to reorder."
+msgstr "Klikk og dra for å endre rekkefølge."
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:220
+msgid "No columns selected"
+msgstr "Ingen kolonner valgt"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:277
+msgid "Cells in this column will be tinted based on their values."
+msgstr "Celler i denne kolonnen vil bli toner basert på deres verdier."
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:279
+msgid "When a cell in these columns is {0} it will be tinted this color."
+msgstr "NÃ¥r en celle i disse kolonnene er {0}, vil det bli toner i denne fargen."
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:290
+msgid "Which columns should be affected?"
+msgstr "Hvilke kolonner skal påvirkes?"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:300
+msgid "Formatting style"
+msgstr "Formatteringstil"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:304
+msgid "Single color"
+msgstr "Enkel farge"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:305
+msgid "Color range"
+msgstr "Fargeskala"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:312
+msgid "When a cell in this column is…"
+msgstr "NÃ¥r en celle i denne kolonnen er..."
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:325
+msgid "…turn its background this color:"
+msgstr "...endre bakgrunnsfargen til denne fargen:"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:331
+msgid "Highlight the whole row"
+msgstr "Framhev hele raden"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:339
+msgid "Colors"
+msgstr "Farger"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:344
+msgid "Start the range at"
+msgstr "Start omfanget med"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:349
+msgid "Smallest value in this column"
+msgstr "Minste verdi i denne kolonnen"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:351
+msgid "Smallest value in each column"
+msgstr "Minste verdi i hver kolonne"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:353
+msgid "Smallest value in all of these columns"
+msgstr "Minste verdi i alle disse kolonnene"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:357
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:379
+msgid "Custom value"
+msgstr "Egen verdi"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:366
+msgid "End the range at"
+msgstr "Avslutt området ved"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:371
+msgid "Largest value in this column"
+msgstr "Største verdi i denne kolonnen"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:373
+msgid "Largest value in each column"
+msgstr "Største verdi i hver kolonne"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:375
+msgid "Largest value in all of these columns"
+msgstr "Største verdi i alle disse kolonnene"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:407
+msgid "Add rule"
+msgstr "Legg til regel"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:407
+msgid "Update rule"
+msgstr "Oppdater regel"
+
+#: frontend/src/metabase/visualizations/index.js:30
+msgid "Visualization is null"
+msgstr "Visualisering er null"
+
+#: frontend/src/metabase/visualizations/index.js:35
+msgid "Visualization must define an 'identifier' static variable: "
+msgstr "Visualiseringen må definere en 'identifier' statisk variabel: "
+
+#: frontend/src/metabase/visualizations/index.js:41
+msgid "Visualization with that identifier is already registered: "
+msgstr "Visualiseringen med den indentifikatoren er allerede registrert: "
+
+#: frontend/src/metabase/visualizations/index.js:69
+msgid "No visualization for {0}"
+msgstr "Ingen visualisering for {0}"
+
+#: frontend/src/metabase/visualizations/lib/LineAreaBarRenderer.js:71
+msgid "\"{0}\" is an unaggregated field: if it has more than one value at a point on the x-axis, the values will be summed."
+msgstr "\"{0}\" er et ikke-aggregert felt: Hvis det har mer enn én verdi for et punkt på x-aksen, så vil verdiene bli summert."
+
+#: frontend/src/metabase/visualizations/lib/LineAreaBarRenderer.js:87
+msgid "This chart type requires at least 2 columns."
+msgstr "Denne grafetypen krever minst 2 kolonner."
+
+#: frontend/src/metabase/visualizations/lib/LineAreaBarRenderer.js:92
+msgid "This chart type doesn't support more than {0} series of data."
+msgstr "Denne grafetypen støtter ikke mer enn {0} serier med data."
+
+#: frontend/src/metabase/visualizations/lib/LineAreaBarRenderer.js:509
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:42
+msgid "Goal"
+msgstr "MÃ¥l"
+
+#: frontend/src/metabase/visualizations/lib/errors.js:9
+msgid "Doh! The data from your query doesn't fit the chosen display choice. This visualization requires at least {0} {1} of data."
+msgstr "Å nei! Dataene fra spørringen din passer ikke med den valgte visningen. Denne visualiseringen trenger minst {0} {1} med data."
+
+#: frontend/src/metabase/visualizations/lib/errors.js:9
+msgid "column"
+msgid_plural "columns"
+msgstr[0] "kolonne"
+msgstr[1] "kolonner"
+
+#: frontend/src/metabase/visualizations/lib/errors.js:21
+msgid "No dice. We have {0} data {1} to show and that's not enough for this visualization."
+msgstr "Sjanseløst. Vi har {0} data {1} å vise og det er ikke nok for denne visualiseringen."
+
+#: frontend/src/metabase/visualizations/lib/errors.js:21
+msgid "point"
+msgid_plural "points"
+msgstr[0] "poeng"
+msgstr[1] "poenger"
+
+#: frontend/src/metabase/visualizations/lib/errors.js:33
+msgid "Bummer. We can't actually do a pin map for this data because we require both a latitude and longitude column."
+msgstr "Kjipern. Vi kan faktisk ikke lage et kartnål-kart for denne dataen fordi vi trenger kolonner både for lengdegrad og breddegrad."
+
+#: frontend/src/metabase/visualizations/lib/errors.js:42
+msgid "Please configure this chart in the chart settings"
+msgstr "Vennligst konfigurer denne grafen i grafinstillingene"
+
+#: frontend/src/metabase/visualizations/lib/errors.js:44
+msgid "Edit Settings"
+msgstr "Rediger innstillinger"
+
+#: frontend/src/metabase/visualizations/lib/fill_data.js:38
+msgid "xValues missing!"
+msgstr "xVerdier mangler!"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:56
+#: frontend/src/metabase/visualizations/visualizations/RowChart.jsx:31
+msgid "X-axis"
+msgstr "X-akse"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:82
+msgid "Add a series breakout..."
+msgstr "Bryt ut serien..."
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:93
+#: frontend/src/metabase/visualizations/visualizations/RowChart.jsx:35
+msgid "Y-axis"
+msgstr "Y-akse"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:118
+msgid "Add another series..."
+msgstr "Legg til en ny serie..."
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:132
+msgid "Bubble size"
+msgstr "Bobblestørrelse"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:161
+#: frontend/src/metabase/visualizations/visualizations/LineChart.jsx:17
+msgid "Line"
+msgstr "Linje"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:162
+msgid "Curve"
+msgstr "Kurve"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:163
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:67
+msgid "Step"
+msgstr "Steg"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:170
+msgid "Show point markers on lines"
+msgstr "Vis punktmarkeringer på linjer"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:178
+msgid "Stacking"
+msgstr "Stabling"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:182
+msgid "Don't stack"
+msgstr "Ikke stable"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:183
+msgid "Stack"
+msgstr "Stable"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:184
+msgid "Stack - 100%"
+msgstr "Stabling - 100%"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:201
+msgid "Show goal"
+msgstr "Vis mål"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:207
+msgid "Goal value"
+msgstr "MÃ¥lverdi"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:218
+msgid "Replace missing values with"
+msgstr "Erstatt manglende verdier med"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:223
+msgid "Zero"
+msgstr "Null"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:224
+msgid "Nothing"
+msgstr "Ingenting"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:225
+msgid "Linear Interpolated"
+msgstr "Linjært Interpolert"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:284
+msgid "X-axis scale"
+msgstr "Skalering av X-akse"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:301
+msgid "Timeseries"
+msgstr "Tidsserie"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:304
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:322
+msgid "Linear"
+msgstr "Lineær"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:306
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:323
+msgid "Power"
+msgstr "Kraft"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:307
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:324
+msgid "Log"
+msgstr "Logg"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:309
+msgid "Histogram"
+msgstr "Histogram"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:311
+msgid "Ordinal"
+msgstr "Ordens"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:317
+msgid "Y-axis scale"
+msgstr "Y-akse skala"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:330
+msgid "Show x-axis line and marks"
+msgstr "Vis linje og merker for x-akse"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:336
+msgid "Compact"
+msgstr "Kompakt"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:337
+msgid "Rotate 45°"
+msgstr "Roter 45°"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:338
+msgid "Rotate 90°"
+msgstr "Roter 90°"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:345
+msgid "Show y-axis line and marks"
+msgstr "Vis linje og merker for y-akse"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:357
+msgid "Auto y-axis range"
+msgstr "Automatisk område for y-aksen"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:401
+msgid "Use a split y-axis when necessary"
+msgstr "Bruk en delt y-akse hvis det er nødvendig"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:408
+msgid "Show label on x-axis"
+msgstr "Vis etikett på x-akse"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:414
+msgid "X-axis label"
+msgstr "X-akse etikett"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:423
+msgid "Show label on y-axis"
+msgstr "Vis etikett på y-akse"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:429
+msgid "Y-axis label"
+msgstr "Y-akse etikett"
+
+#: frontend/src/metabase/visualizations/lib/utils.js:116
+msgid "Standard Deviation"
+msgstr "Standardavvik"
+
+#: frontend/src/metabase/visualizations/visualizations/AreaChart.jsx:18
+msgid "Area"
+msgstr "Område"
+
+#: frontend/src/metabase/visualizations/visualizations/AreaChart.jsx:21
+msgid "area chart"
+msgstr "områdediagram"
+
+#: frontend/src/metabase/visualizations/visualizations/BarChart.jsx:16
+msgid "Bar"
+msgstr "Søyle"
+
+#: frontend/src/metabase/visualizations/visualizations/BarChart.jsx:19
+msgid "bar chart"
+msgstr "søylediagram"
+
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:57
+msgid "Which fields do you want to use?"
+msgstr "Hvilke felter ønsker du å bruke?"
+
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:31
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:85
+msgid "Funnel"
+msgstr "Trakt"
+
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:74
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:67
+msgid "Measure"
+msgstr "MÃ¥le"
+
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:80
+msgid "Funnel type"
+msgstr "Trakttype"
+
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:86
+msgid "Bar chart"
+msgstr "Søylediagram"
+
+#: frontend/src/metabase/visualizations/visualizations/LineChart.jsx:20
+msgid "line chart"
+msgstr "linjediagram"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:211
+msgid "Please select longitude and latitude columns in the chart settings."
+msgstr "Vennligst velg kolonner for lengdegrad og breddegrad i diagraminnstillingene."
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:217
+msgid "Please select a region map."
+msgstr "Vennligst velg ett regionalkart."
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:221
+#, fuzzy
+msgid "Please select region and metric columns in the chart settings."
+msgstr "Vennligst velg region- og indikator-kolonner i diagraminnstillingene."
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:31
+msgid "Map"
+msgstr "Kart"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:45
+msgid "Map type"
+msgstr "Kart type"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:49
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:146
+msgid "Region map"
+msgstr "Regionalkart"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:50
+msgid "Pin map"
+msgstr "Kartnål-kart"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:96
+msgid "Pin type"
+msgstr "Type kartnål"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:101
+msgid "Tiles"
+msgstr "Fliser"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:102
+msgid "Markers"
+msgstr "Markører"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:118
+msgid "Latitude field"
+msgstr "Felt for breddegrad"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:128
+msgid "Longitude field"
+msgstr "Felt for lengdegrad"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:138
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:165
+msgid "Metric field"
+msgstr "Indikatorfelt"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:170
+msgid "Region field"
+msgstr "Felt for region"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:179
+msgid "Radius"
+msgstr "Radius"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:185
+msgid "Blur"
+msgstr "Uskarphet"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:191
+msgid "Min Opacity"
+msgstr "Min gjennomsiktighet"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:197
+msgid "Max Zoom"
+msgstr "Maks zoom"
+
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:175
+msgid "No relationships found."
+msgstr "Ingen forhold funnet"
+
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:213
+msgid "via {0}"
+msgstr "via {0}"
+
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:290
+msgid "This {0} is connected to:"
+msgstr "{0} er koblet til:"
+
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:47
+msgid "Object Detail"
+msgstr "Objekt detaljer"
+
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:50
+msgid "object"
+msgstr "objekt"
+
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:254
+msgid "Total"
+msgstr "Total"
+
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:53
+msgid "Which columns do you want to use?"
+msgstr "Hvilke kolonner vil du bruke?"
+
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:40
+msgid "Pie"
+msgstr "Pai"
+
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:62
+msgid "Dimension"
+msgstr "Dimensjon"
+
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:72
+#, fuzzy
+msgid "Show legend"
+msgstr "Vis forklaring"
+
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:77
+#, fuzzy
+msgid "Show percentages in legend"
+msgstr "Vis prosenter i forklaring"
+
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:83
+msgid "Minimum slice percentage"
+msgstr "Minste oppstykkings-prosent"
+
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:136
+msgid "Goal met"
+msgstr "Mål møtt"
+
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:138
+msgid "Goal exceeded"
+msgstr "MÃ¥l overskredet"
+
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:208
+msgid "Goal {0}"
+msgstr "MÃ¥l {0}"
+
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:35
+msgid "Progress visualization requires a number."
+msgstr "Fremdriftsvisualisering krever et tall."
+
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:23
+msgid "Progress"
+msgstr "Fremdrift"
+
+#: frontend/src/metabase/entities/collections.js:101
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:48
+msgid "Color"
+msgstr "Farge"
+
+#: frontend/src/metabase/visualizations/visualizations/RowChart.jsx:13
+msgid "Row Chart"
+msgstr "Raddiagram"
+
+#: frontend/src/metabase/visualizations/visualizations/RowChart.jsx:16
+msgid "row chart"
+msgstr "raddiagram"
+
+#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:75
+msgid "Separator style"
+msgstr "Separatorstil"
+
+#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:88
+msgid "Number of decimal places"
+msgstr "Nummer med desimal punkter"
+
+#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:92
+msgid "Add a prefix"
+msgstr "Legg til prefiks"
+
+#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:96
+msgid "Add a suffix"
+msgstr "Legg til suffiks"
+
+#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:100
+msgid "Multiply by a number"
+msgstr "Multipliser med ett tall"
+
+#: frontend/src/metabase/visualizations/visualizations/ScatterPlot.jsx:16
+msgid "Scatter"
+msgstr "Spredning"
+
+#: frontend/src/metabase/visualizations/visualizations/ScatterPlot.jsx:19
+msgid "scatter plot"
+msgstr "spredningsdiagram"
+
+#: frontend/src/metabase/visualizations/visualizations/Table.jsx:61
+msgid "Pivot the table"
+msgstr "Pivoter tabellen"
+
+#: frontend/src/metabase/visualizations/visualizations/Table.jsx:73
+msgid "Visible fields"
+msgstr "Synlige felter"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:167
+msgid "Write here, and use Markdown if you''d like"
+msgstr "Skriv her, og bruk gjerne Markdown hvis du vil"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:73
+msgid "Vertical Alignment"
+msgstr "Loddrett justering"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:77
+msgid "Top"
+msgstr "Topp"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:78
+msgid "Middle"
+msgstr "Midten"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:79
+msgid "Bottom"
+msgstr "Bunn"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:86
+msgid "Horizontal Alignment"
+msgstr "Vannrett justering"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:90
+msgid "Left"
+msgstr "Venstre"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:91
+msgid "Center"
+msgstr "Senter"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:92
+msgid "Right"
+msgstr "Høyre"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:99
+msgid "Show background"
+msgstr "Vis bakgrunn"
+
+#: frontend/src/metabase-lib/lib/Dimension.js:497
+msgid "{0} bin"
+msgid_plural "{0} bins"
+msgstr[0] "{0} samling"
+msgstr[1] "{0} samlinger"
+
+#: frontend/src/metabase-lib/lib/Dimension.js:503
+msgid "Auto binned"
+msgstr "Auto samling"
+
+#: src/metabase/api/alert.clj
+msgid "DELETE /api/alert/:id is deprecated. Instead, change its `archived` value via PUT /api/alert/:id."
+msgstr "DELETE /api/alert/:id er foreldet. Bruk heller dens`archived`-verdi via PUT /api/alert/:id."
+
+#: src/metabase/api/automagic_dashboards.clj
+msgid "invalid show value"
+msgstr "ukjent vis verdi"
+
+#: src/metabase/api/automagic_dashboards.clj
+msgid "invalid value for prefix"
+msgstr "ukjent verdi for prefiks"
+
+#: src/metabase/api/automagic_dashboards.clj
+msgid "invalid value for rule name"
+msgstr "ukjent verdi for regelnavn"
+
+#: src/metabase/api/automagic_dashboards.clj
+msgid "value couldn''t be parsed as base64 encoded JSON"
+msgstr "verdi kunne ikke leses som base64 kodet JSON"
+
+#: src/metabase/api/automagic_dashboards.clj
+msgid "Invalid entity type"
+msgstr "Ukjent enhetstype"
+
+#: src/metabase/api/automagic_dashboards.clj
+msgid "Invalid comparison entity type. Can only be one of \"table\", \"segment\", or \"adhoc\""
+msgstr "Ugyldig entitetstype for sammenligning. Kan kun være en av \"tabell\", \"segment\", eller \"adhoc\""
+
+#: src/metabase/api/card.clj
+msgid "Error running query to determine Card result metadata:"
+msgstr "Feil ved kjøring av spørring for å bestemme metadata for kort-resultat:"
+
+#: src/metabase/api/card.clj
+msgid "DELETE /api/card/:id is deprecated. Instead, change its `archived` value via PUT /api/card/:id."
+msgstr "DELETE /api/card/:id er utgående. Arkiver istedenfor ved bruk av `archived` verdi via PUT /api/card/:id."
+
+#: src/metabase/api/common.clj src/metabase/api/common/internal.clj
+msgid "Invalid field: {0}"
+msgstr "Ukjent felt: {0}"
+
+#: src/metabase/api/common.clj
+msgid "Invalid value ''{0}'' for ''{1}'': {2}"
+msgstr "Ukjent verdi \"{0}\" for \"{1}\": {2}"
+
+#: src/metabase/api/common.clj
+msgid "must be one of: {0}"
+msgstr "Må være en av: {0}"
+
+#: src/metabase/api/common.clj
+#, fuzzy
+msgid "Invalid Request."
+msgstr "Ukjent forespørsel."
+
+#: src/metabase/api/common.clj
+msgid "Not found."
+msgstr "Ikke funnet."
+
+#: src/metabase/api/common.clj
+msgid "You don''t have permissions to do that."
+msgstr "Du har ikke tilganger til å gjøre dette."
+
+#: src/metabase/api/common.clj
+msgid "Internal server error."
+msgstr "Intern tjenerfeil."
+
+#: src/metabase/api/common.clj
+msgid "Warning: endpoint {0}/{1} does not have a docstring."
+msgstr "Advarsel: endepunktet {0}/{1} har ikke en docstring."
+
+#: src/metabase/api/common.clj
+msgid "starting streaming request"
+msgstr "starter strømningsforespørsel"
+
+#: src/metabase/api/common.clj
+msgid "connection closed, canceling request"
+msgstr "Kobling lukket, avbryter forespørsel"
+
+#. a newline padding character as it's harmless and will allow us to check if the client is connected. If
+#. sending this character fails because the connection is closed, the chan will then close.  Newlines are
+#. no-ops when reading JSON which this depends upon.
+#: src/metabase/api/common.clj
+msgid "Response not ready, writing one byte & sleeping..."
+msgstr "Respons ikke tilgjengelig, skriver en byte & sover..."
+
+#: src/metabase/api/common.clj
+msgid "Public sharing is not enabled."
+msgstr "Offentlig deling er ikke aktivert."
+
+#: src/metabase/api/common.clj
+msgid "Embedding is not enabled."
+msgstr "Innebygging er ikke aktivert."
+
+#: src/metabase/api/common.clj
+msgid "The object has been archived."
+msgstr "Objektet har blitt arkivert."
+
+#: src/metabase/api/common/internal.clj
+msgid "Attempted to return a boolean as an API response. This is not allowed!"
+msgstr "Forsøkte å returnere en boolsk verdi som et API-svar. Dette er ikke tillatt!"
+
+#: src/metabase/api/dataset.clj
+msgid "Source query for this query is Card {0}"
+msgstr "Kilde-spørring for denne spørringen er kort {0}"
+
+#: src/metabase/api/dataset.clj
+msgid "Invalid export format: {0}"
+msgstr "Ukjent eksport format: {0}"
+
+#: src/metabase/api/geojson.clj
+msgid "Invalid JSON URL or resource: {0}"
+msgstr "Ukjent JSON nettlink eller resurs: {0}"
+
+#: src/metabase/api/geojson.clj
+msgid "JSON containing information about custom GeoJSON files for use in map visualizations instead of the default US State or World GeoJSON."
+msgstr "JSON som inneholder informasjon om egendefinert GeoJSON-filer for bruk i kart-visualiseringer istedenfor det innebygde kartet over stater i USA eller verdenskartet."
+
+#: src/metabase/api/geojson.clj
+msgid "Invalid custom GeoJSON key: {0}"
+msgstr "Ukjent tilpasset GeoJSON nøkkel: {0}"
+
+#. ...but if we *still* couldn't find a match, throw an Exception, because we don't want people
+#. trying to inject new params
+#: src/metabase/api/public.clj
+msgid "Invalid param: {0}"
+msgstr "Ukjent parameter: {0}"
+
+#: src/metabase/api/pulse.clj
+msgid "DELETE /api/pulse/:id is deprecated. Instead, change its `archived` value via PUT /api/pulse/:id."
+msgstr "DELETE /api/pulse/:id er avleggs. Istedenfor bruk arkiverings API via PUT /api/pulsen/:id."
+
+#: src/metabase/api/routes.clj
+msgid "API endpoint does not exist."
+msgstr "API punkt fins ikke."
+
+#: src/metabase/api/session.clj
+msgid "Password did not match stored password."
+msgstr "Passord er ikke likt lagret passord."
+
+#: src/metabase/api/session.clj
+msgid "did not match stored password"
+msgstr "er ikke likt lagret passord"
+
+#: src/metabase/api/session.clj
+msgid "Problem connecting to LDAP server, will fallback to local authentication {0}"
+msgstr "Problem tilkobling til LDAP-tjener, vil falle tilbake til lokal autorisering {0}"
+
+#: src/metabase/api/session.clj
+msgid "Invalid reset token"
+msgstr "Ugyldig tilbakestillingsnøkkel"
+
+#: src/metabase/api/session.clj
+msgid "Client ID for Google Auth SSO. If this is set, Google Auth is considered to be enabled."
+msgstr "Klient-ID for Google Auth SSO. Hvis denne er satt, så er Google Auth ansett for å være aktivert."
+
+#: src/metabase/api/session.clj
+msgid "When set, allow users to sign up on their own if their Google account email address is from this domain."
+msgstr "Når denne er satt, så kan brukere registrere seg med sin egen Google-konto dersom e-postadressen er fra dette domenet."
+
+#: src/metabase/api/session.clj
+msgid "Invalid Google Auth token."
+msgstr "Ugyldig Google Auth nøkkel."
+
+#: src/metabase/api/session.clj
+msgid "Email is not verified."
+msgstr "E-postadresse ikke verifisert."
+
+#: src/metabase/api/session.clj
+msgid "You''ll need an administrator to create a Metabase account before you can use Google to log in."
+msgstr "En administrator må lage en Metabase-konto før du kan bruke Google til å logge inn."
+
+#: src/metabase/api/session.clj
+msgid "Successfully authenticated Google Auth token for: {0} {1}"
+msgstr "Autentisering med Google Auth token var vellykket for: {0} {1}"
+
+#: src/metabase/api/setup.clj
+msgid "Add a database"
+msgstr "Legg til database"
+
+#: src/metabase/api/setup.clj
+msgid "Get connected"
+msgstr "Koble til"
+
+#: src/metabase/api/setup.clj
+msgid "Connect to your data so your whole team can start to explore."
+msgstr "Koble til dine data sånn at hele laget ditt kan starte å utforske."
+
+#: src/metabase/api/setup.clj
+msgid "Set up email"
+msgstr "Sett opp e-post"
+
+#: src/metabase/api/setup.clj
+#, fuzzy
+msgid "Add email credentials so you can more easily invite team members and get updates via Pulses."
+msgstr "Legg til legitimasjon for e-post sånn at du enklere kan invitere lagmedlemmer og motta oppdateringer via pulser."
+
+#: src/metabase/api/setup.clj
+msgid "Set Slack credentials"
+msgstr "Sett Slack innlogging"
+
+#: src/metabase/api/setup.clj
+msgid "Does your team use Slack? If so, you can send automated updates via pulses and ask questions with MetaBot."
+msgstr "Bruker laget ditt Slack? I så fall kan du sende automatiske oppdateringer via pulser og stille spørsmål med MetaBot."
+
+#: src/metabase/api/setup.clj
+msgid "Invite team members"
+msgstr "Inviter lagmedlemmer"
+
+#: src/metabase/api/setup.clj
+msgid "Share answers and data with the rest of your team."
+msgstr "Del svar og data med resten av ditt lag."
+
+#: src/metabase/api/setup.clj
+msgid "Hide irrelevant tables"
+msgstr "Skjul irrelevante tabeller"
+
+#: src/metabase/api/setup.clj
+msgid "Curate your data"
+msgstr "Organiser dataene dine"
+
+#: src/metabase/api/setup.clj
+msgid "If your data contains technical or irrelevant info you can hide it."
+msgstr "Hvis dataene inneholder teknisk eller irrelevant informasjon så kan du skjule det."
+
+#: src/metabase/api/setup.clj
+msgid "Organize questions"
+msgstr "Organiser spørsmål"
+
+#: src/metabase/api/setup.clj
+msgid "Have a lot of saved questions in {0}? Create collections to help manage them and add context."
+msgstr "Har du mange lagrede spørsmål i {0}? Lag samlinger for å håndtere dem og sette dem i kontekst."
+
+#. 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
+#: src/metabase/api/setup.clj
+msgid "Metabase"
+msgstr "Metabase"
+
+#: src/metabase/api/setup.clj
+msgid "Create metrics"
+msgstr "Lag indikator"
+
+#: src/metabase/api/setup.clj
+msgid "Define canonical metrics to make it easier for the rest of your team to get the right answers."
+msgstr "Definer standardindikatorer for å gjøre det enklere for resten av ditt lag å finne de riktige svarene."
+
+#: src/metabase/api/setup.clj
+msgid "Create segments"
+msgstr "Lag segmenter"
+
+#: src/metabase/api/setup.clj
+msgid "Keep everyone on the same page by creating canonical sets of filters anyone can use while asking questions."
+msgstr "Hjelp alle med å forstå hverandre ved å lage standard-sett med filtere som alle kan bruke når de stiller spørsmål."
+
+#: src/metabase/api/table.clj
+msgid "Table ''{0}'' is now visible. Resyncing."
+msgstr "Tabell \"{0}\" er synlig. Synkroniserer på nytt."
+
+#: src/metabase/api/table.clj
+msgid "Auto bin"
+msgstr "Auto samle"
+
+#: src/metabase/api/table.clj
+msgid "Don''t bin"
+msgstr "Ikke samle"
+
+#: src/metabase/api/table.clj
+msgid "Day"
+msgstr "Dag"
+
+#. note the order of these options corresponds to the order they will be shown to the user in the UI
+#: src/metabase/api/table.clj
+msgid "Minute"
+msgstr "Minutt"
+
+#: src/metabase/api/table.clj
+msgid "Hour"
+msgstr "Time"
+
+#: src/metabase/api/table.clj
+msgid "Quarter"
+msgstr "Kvartal"
+
+#: src/metabase/api/table.clj
+msgid "Minute of Hour"
+msgstr "Minutt i time"
+
+#: src/metabase/api/table.clj
+msgid "Hour of Day"
+msgstr "Time på dagen"
+
+#: src/metabase/api/table.clj
+msgid "Day of Week"
+msgstr "Dag i uke"
+
+#: src/metabase/api/table.clj
+msgid "Day of Month"
+msgstr "Dag i måned"
+
+#: src/metabase/api/table.clj
+msgid "Day of Year"
+msgstr "Dag i år"
+
+#: src/metabase/api/table.clj
+msgid "Week of Year"
+msgstr "Uke i år"
+
+#: src/metabase/api/table.clj
+msgid "Month of Year"
+msgstr "Måned i år"
+
+#: src/metabase/api/table.clj
+msgid "Quarter of Year"
+msgstr "Kvartal i år"
+
+#: src/metabase/api/table.clj
+msgid "10 bins"
+msgstr "10 samlinger"
+
+#: src/metabase/api/table.clj
+msgid "50 bins"
+msgstr "50 samlinger"
+
+#: src/metabase/api/table.clj
+msgid "100 bins"
+msgstr "100 samlinger"
+
+#: src/metabase/api/table.clj
+msgid "Bin every 0.1 degrees"
+msgstr "Samle hver 0,1 grader"
+
+#: src/metabase/api/table.clj
+msgid "Bin every 1 degree"
+msgstr "Samle hver grad"
+
+#: src/metabase/api/table.clj
+msgid "Bin every 10 degrees"
+msgstr "Samle hver 10ende grad"
+
+#: src/metabase/api/table.clj
+msgid "Bin every 20 degrees"
+msgstr "Samle hver 20ende grad"
+
+#. returns `true` if successful -- see JavaDoc
+#: src/metabase/api/tiles.clj src/metabase/pulse/render.clj
+msgid "No appropriate image writer found!"
+msgstr "Ingen passende bildeforfatter funnet!"
+
+#: src/metabase/api/user.clj
+msgid "Email address already in use."
+msgstr "E-postadresse allerede brukt."
+
+#: src/metabase/api/user.clj
+msgid "Email address already associated to another user."
+msgstr "E-postadresse allerede assosiert med annen bruker."
+
+#: src/metabase/api/user.clj
+msgid "Not able to reactivate an active user"
+msgstr "Kan ikke aktivere en aktiv bruker"
+
+#: src/metabase/api/user.clj
+msgid "Invalid password"
+msgstr "Ugyldig passord"
+
+#: src/metabase/automagic_dashboards/comparison.clj
+msgid "All {0}"
+msgstr "Alle {0}"
+
+#: src/metabase/automagic_dashboards/comparison.clj
+msgid "{0}, all {1}"
+msgstr "{0}, alle {1}"
+
+#: src/metabase/automagic_dashboards/comparison.clj
+msgid "Comparison of {0} and {1}"
+msgstr "Sammenligning av {0} og {1}"
+
+#: src/metabase/automagic_dashboards/comparison.clj
+msgid "Automatically generated comparison dashboard comparing {0} and {1}"
+msgstr "Automatisk generert sammenlignings panel sammenlign {0} og {1}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "sum"
+msgstr "sum"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "average"
+msgstr "gjennomsnitt"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "minumum"
+msgstr "minimum"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "maximum"
+msgstr "maksimum"
+
+#: src/metabase/automagic_dashboards/core.clj
+#, fuzzy
+msgid "distinct count"
+msgstr "distinkt telling"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "standard deviation"
+msgstr "standardavvik"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "cumulative count"
+msgstr "kumulativt antall"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "cumulative sum"
+msgstr "kumulativ sum"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} and {1}"
+msgstr "{0} og {1}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} of {1}"
+msgstr "{0} av {1}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} by {1}"
+msgstr "{0} med {1}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} in the {1} segment"
+msgstr "{0} i {1} segmentet"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} segment"
+msgstr "{0} segment"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} metric"
+msgstr "{0} indikator"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} field"
+msgstr "{0} felt"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "\"{0}\" question"
+msgstr "\"{0}\" spørsmål"
+
+#: src/metabase/automagic_dashboards/comparison.clj
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Compare with {0}"
+msgstr "Sammenlign med {0}"
+
+#: src/metabase/automagic_dashboards/comparison.clj
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Compare with entire dataset"
+msgstr "Sammenlign med hele datasettet"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Applying heuristic %s to %s."
+msgstr "Bruk heuristikk %s til %s"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Dimensions bindings:n%s"
+msgstr "Dimensjoner bindinger:n%s"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Using definitions:nMetrics:n%snFilters:n%s"
+msgstr "Bruker definisjoner:nBeregninger:n%snFiltere:n%s"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Can''t create dashboard for {0}"
+msgstr "Kan ikke lage panel for {0}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0}st"
+msgstr "{0}ste"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0}nd"
+msgstr "{0}dre"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0}rd"
+msgstr "{0}dje"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0}th"
+msgstr "{0}de"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "at {0}"
+msgstr "på {0}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "on {0}"
+msgstr "på {0}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "in {0} week - {1}"
+msgstr "om {0} uke - {1}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "in {0}"
+msgstr "om {0}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "in Q{0} - {1}"
+msgstr "i K{0} - {1}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Q{0}"
+msgstr "K{0}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} is {1}"
+msgstr "{0} er {1}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} is between {1} and {2}"
+msgstr "{0} er mellom {1} og {2}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} is between {1} and {2}; and {3} is between {4} and {5}"
+msgstr "{0} er mellom {1} og {2}, og {3} er mellom {4} og {5}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "where {0}"
+msgstr "hvor {0}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "A closer look at {0}"
+msgstr "En grundigere titt på {0}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "A closer look at the {0}"
+msgstr "En grundigere titt på {0}"
+
+#: src/metabase/automagic_dashboards/populate.clj
+msgid "Adding %s cards to dashboard %s:n%s"
+msgstr "Legger til %s kort til panel %s:n%s"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "0 <= score <= {0}"
+msgstr "0 <= poengsum <= {0}"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "1 <= width <= {0}"
+msgstr "1 <= bredde <= {0}"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid metrics references"
+msgstr "Gyldige indikatorreferanser"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid filters references"
+msgstr "Gyldig filter referanse"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid group references"
+msgstr "Gyldig gruppe referanser"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid order_by references"
+msgstr "Gyldig order_by referanser"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid dashboard filters references"
+msgstr "Gyldig panel filter referanser"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid dimension references"
+msgstr "Gyldig dimensjonelle referanser"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid card dimension references"
+msgstr "Gyldig kort dimensjon referanser"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Error parsing %s:n%s"
+msgstr "Feil under tolking av %s:n%s"
+
+#: src/metabase/cmd/reset_password.clj
+msgid "No user found with email address ''{0}''. "
+msgstr "Fant ingen bruker med epostadressen \"{0}\""
+
+#: src/metabase/cmd/reset_password.clj
+msgid "Please check the spelling and try again."
+msgstr "Kontroller skrivemåten og prøv igjen"
+
+#: src/metabase/cmd/reset_password.clj
+msgid "Resetting password for {0}..."
+msgstr "Tilbakestiller passord for {0}..."
+
+#: src/metabase/cmd/reset_password.clj
+msgid "OK [[[{0}]]]"
+msgstr "Ok [[[{0}]]]"
+
+#: src/metabase/cmd/reset_password.clj
+msgid "FAIL [[[{0}]]]"
+msgstr "FEIL [[[{0}]]]"
+
+#: src/metabase/core.clj
+msgid "Please use the following URL to setup your Metabase installation:"
+msgstr "Vennligst bruk følgende URL for å sette opp Metabase-installasjonen din:"
+
+#: src/metabase/core.clj
+msgid "Metabase Shutting Down ..."
+msgstr "Metabase avslutter..."
+
+#: src/metabase/core.clj
+msgid "Metabase Shutdown COMPLETE"
+msgstr "Avslutning av Metabase FERDIG"
+
+#: src/metabase/core.clj
+msgid "Starting Metabase version {0} ..."
+msgstr "Starter Metabase versjon {0} ..."
+
+#: src/metabase/core.clj
+msgid "System timezone is ''{0}'' ..."
+msgstr "System tidssone er \"{0}\" ..."
+
+#. startup database.  validates connection & runs any necessary migrations
+#: src/metabase/core.clj
+msgid "Setting up and migrating Metabase DB. Please sit tight, this may take a minute..."
+msgstr "Setter opp og migrerer Metabase DB. Vennligst vent litt, dette kan ta noen øyeblikk..."
+
+#: src/metabase/core.clj
+msgid "Looks like this is a new installation ... preparing setup wizard"
+msgstr "Det ser ut som at dette er en ny installasjon ... forbereder installasjonsveilederen"
+
+#: src/metabase/core.clj
+msgid "Metabase Initialization COMPLETE"
+msgstr "Metabase oppstart ferdig"
+
+#: src/metabase/core.clj
+msgid "Launching Embedded Jetty Webserver with config:"
+msgstr "Starter Embedded Jetty Webserver med konfigurasjon:"
+
+#: src/metabase/core.clj
+msgid "Shutting Down Embedded Jetty Webserver"
+msgstr "Stopper Embedded Jetty Webserver"
+
+#: src/metabase/core.clj
+msgid "Starting Metabase in STANDALONE mode"
+msgstr "Starter Metabase i STANDALONE-modus"
+
+#: src/metabase/core.clj
+msgid "Metabase Initialization FAILED"
+msgstr "Metabase oppstart feilet"
+
+#: src/metabase/db.clj
+msgid "Database has migration lock; cannot run migrations."
+msgstr "Database har migreringer, kan ikke kjøre migreringer."
+
+#: src/metabase/db.clj
+msgid "You can force-release these locks by running `java -jar metabase.jar migrate release-locks`."
+msgstr "Du kan løse opp låsningene ved å kjøre `java -jar metabase.jar migrate release-locks`."
+
+#: src/metabase/db.clj
+msgid "Checking if Database has unrun migrations..."
+msgstr "Sjekker om databasen har ukjørte migreringer..."
+
+#: src/metabase/db.clj
+msgid "Database has unrun migrations. Waiting for migration lock to be cleared..."
+msgstr "Database her ukjørte migreringer. Vent mens migreringslåsen blir løst..."
+
+#: src/metabase/db.clj
+msgid "Migration lock is cleared. Running migrations..."
+msgstr "Migreringslås er løst. Kjører migreringer..."
+
+#: src/metabase/db.clj
+msgid "Migration lock cleared, but nothing to do here! Migrations were finished by another instance."
+msgstr "Migreringslås er løst, men ingenting å gjøre her! Migreringer ble ferdige i en annen instanse."
+
+#. Set up liquibase and let it do its thing
+#: src/metabase/db.clj
+msgid "Setting up Liquibase..."
+msgstr "Setter opp Liquibase..."
+
+#: src/metabase/db.clj
+msgid "Liquibase is ready."
+msgstr "Liquibase er ferdig."
+
+#: src/metabase/db.clj
+msgid "Verifying {0} Database Connection ..."
+msgstr "Verifiserer {0} sin database tilkobling..."
+
+#: src/metabase/db.clj
+msgid "Verify Database Connection ... "
+msgstr "Verifiserer database tilkobling... "
+
+#: src/metabase/db.clj
+msgid "Running Database Migrations..."
+msgstr "Kjører Database migreringer..."
+
+#: src/metabase/db.clj
+msgid "Database Migrations Current ... "
+msgstr "Database nåværende migrering... "
+
+#: src/metabase/driver.clj
+msgid "Hmm, we couldn''t connect to the database."
+msgstr "Hmm, vi klarte ikke koble til databasen."
+
+#: src/metabase/driver.clj
+msgid "Make sure your host and port settings are correct"
+msgstr "Sjekk at vert og port innstillingene er riktige"
+
+#: src/metabase/driver.clj
+msgid "We couldn''t connect to the ssh tunnel host."
+msgstr "Vi klarte ikke koble til SSH tunnelverten."
+
+#: src/metabase/driver.clj
+msgid "Check the username, password."
+msgstr "Sjekk brukernavn og passord."
+
+#: src/metabase/driver.clj
+msgid "Check the hostname and port."
+msgstr "Sjekk vertsnavn og port."
+
+#: src/metabase/driver.clj
+msgid "Looks like the database name is incorrect."
+msgstr "Ser ut som databasenavnet er feil."
+
+#: src/metabase/driver.clj
+msgid "It looks like your host is invalid."
+msgstr "Det ser ut som vertsnavnet er feil."
+
+#: src/metabase/driver.clj
+msgid "Please double-check it and try again."
+msgstr "Vennligst dobbeltsjekk og prøv igjen."
+
+#: src/metabase/driver.clj
+msgid "Looks like your password is incorrect."
+msgstr "Ser ut som passordet er feil."
+
+#: src/metabase/driver.clj
+msgid "Looks like you forgot to enter your password."
+msgstr "Ser ut som du har glemt å legge inn passordet ditt."
+
+#: src/metabase/driver.clj
+msgid "Looks like your username is incorrect."
+msgstr "Ser ut som brukernavn er feil."
+
+#: src/metabase/driver.clj
+msgid "Looks like the username or password is incorrect."
+msgstr "Ser ut som brukernavn eller passord er feil."
+
+#. ## CONFIG
+#: src/metabase/driver.clj
+msgid "Connection timezone to use when executing queries. Defaults to system timezone."
+msgstr "Tilkoblingstidssone som skal brukes ved kjøring av spørringer. Standard er systemets tidssone"
+
+#: src/metabase/driver.clj
+msgid "Registered driver {0} {1}"
+msgstr "Registrert driver {0} {1}"
+
+#: src/metabase/driver.clj
+msgid "No -init-driver function found for ''{0}''"
+msgstr "Ingen -init-driver funksjon funnet for \"{0}\""
+
+#: src/metabase/driver.clj
+msgid "Unable to parse date string ''{0}'' for database engine ''{1}''"
+msgstr "Klarer ikke å tolke dato-teksten \"{0}\" for databasemotoren \"{1}\""
+
+#. all-NULL columns in DBs like Mongo w/o explicit types
+#: src/metabase/driver.clj
+msgid "Don''t know how to map class ''{0}'' to a Field base_type, falling back to :type/*."
+msgstr "Vet ikke hvordan klassen \"{0}\" skal tilordnes en Field base_type, faller tilbake på :type/*."
+
+#: src/metabase/driver.clj
+msgid "Failed to connect to database: {0}"
+msgstr "Feilet ved tilkobling til database: {0}"
+
+#: src/metabase/driver/bigquery.clj
+msgid "Invalid BigQuery identifier: ''{0}''"
+msgstr "Ugyldig BigQuery-identifikator: \"{0}\""
+
+#: src/metabase/driver/bigquery.clj
+msgid "BigQuery statements can't be parameterized!"
+msgstr "BigQuery-uttrykk kan ikke parametriseres!"
+
+#: src/metabase/driver/generic_sql/query_processor.clj
+msgid "Failed to set timezone:"
+msgstr "Feilet ved oppsett av tidssone:"
+
+#: src/metabase/driver/googleanalytics.clj
+msgid "You must enable the Google Analytics API. Use this link to go to the Google Developers Console: {0}"
+msgstr "Du må aktivere Google Analytics APIet. Bruk denne lenken for å gå til Google Developers Console: {0}"
+
+#: src/metabase/driver/h2.clj
+msgid "Running SQL queries against H2 databases using the default (admin) database user is forbidden."
+msgstr "Å kjøre SQL-spørringer mot H2-databaser med standard (admin) database-bruker er forbudt."
+
+#: src/metabase/driver/sparksql.clj
+msgid "Error: metabase.driver.FixedHiveDriver is registered, but JDBC does not seem to be using it."
+msgstr "Feil: metabase.driver.FixedHiveDriver er registrert, men JDBC ser ikke ut til å bruke den."
+
+#: src/metabase/driver/sparksql.clj
+msgid "Found metabase.driver.FixedHiveDriver."
+msgstr "Fant metabase.driver.FixedHiveDriver."
+
+#: src/metabase/driver/sparksql.clj
+msgid "Successfully registered metabase.driver.FixedHiveDriver with JDBC."
+msgstr "Registrering av metabase.driver.FixedHiveDriver til JDBC vellykket."
+
+#. CONFIG
+#. TODO - smtp-port should be switched to type :integer
+#: src/metabase/email.clj
+msgid "Email address you want to use as the sender of Metabase."
+msgstr "E-postadresse du vil skal være av sender fra Metabase."
+
+#: src/metabase/email.clj
+msgid "The address of the SMTP server that handles your emails."
+msgstr "E-postadresse for SMTP-tjeneren som håndterer dine e-poster."
+
+#: src/metabase/email.clj
+msgid "SMTP username."
+msgstr "SMTP brukernavn:"
+
+#: src/metabase/email.clj
+msgid "SMTP password."
+msgstr "SMTP passord."
+
+#: src/metabase/email.clj
+msgid "The port your SMTP server uses for outgoing emails."
+msgstr "Porten SMTP-tjeneren bruker for utgående e-poster."
+
+#: src/metabase/email.clj
+msgid "SMTP secure connection protocol. (tls, ssl, starttls, or none)"
+msgstr "SMTP sikker koblingsprotokoll. (TLS, SSL, STARTTLS eller ingen)"
+
+#: src/metabase/email.clj
+msgid "none"
+msgstr "ingen"
+
+#: src/metabase/email.clj
+msgid "SMTP host is not set."
+msgstr "SMTP vert er ikke satt."
+
+#: src/metabase/email.clj
+msgid "Failed to send email"
+msgstr "Feilet ved sending av e-post."
+
+#: src/metabase/email.clj
+msgid "Error testing SMTP connection"
+msgstr "Feil ved testing av SMTP kobling"
+
+#: src/metabase/integrations/ldap.clj
+msgid "Enable LDAP authentication."
+msgstr "Aktiver LDAP autorisering."
+
+#: src/metabase/integrations/ldap.clj
+msgid "Server hostname."
+msgstr "Vertsnavn til tjener."
+
+#: src/metabase/integrations/ldap.clj
+msgid "Server port, usually 389 or 636 if SSL is used."
+msgstr "Port til tjener, vanligvis 389 eller 636 hvis SSL er i bruk."
+
+#: src/metabase/integrations/ldap.clj
+msgid "Use SSL, TLS or plain text."
+msgstr "Bruk SSL, TLS eller ren tekst."
+
+#: src/metabase/integrations/ldap.clj
+msgid "The Distinguished Name to bind as (if any), this user will be used to lookup information about other users."
+msgstr "Det Fremtredende Navnet å binde som (om noe), denne brukeren vil bli brukt til å hente informasjon om andre brukere."
+
+#: src/metabase/integrations/ldap.clj
+msgid "The password to bind with for the lookup user."
+msgstr "Passordet til å binde med for å søke etter bruker."
+
+#: src/metabase/integrations/ldap.clj
+msgid "Search base for users. (Will be searched recursively)"
+msgstr "Søk base for brukere. (Vil gjøre gjentagende søk)"
+
+#: src/metabase/integrations/ldap.clj
+msgid "User lookup filter, the placeholder '{login}' will be replaced by the user supplied login."
+msgstr "Oppslagsfilter for bruker, plassholderteksten '{login}' vil bli erstattet med den brukerdefinerte innloggingen."
+
+#: src/metabase/integrations/ldap.clj
+msgid "Attribute to use for the user's email. (usually ''mail'', ''email'' or ''userPrincipalName'')"
+msgstr "Attributt som brukes for brukerens e-post. (vanligvis \"mail\", \"email\", eller \"userPrincipalName\")"
+
+#: src/metabase/integrations/ldap.clj
+msgid "Attribute to use for the user''s first name. (usually ''givenName'')"
+msgstr "Attributt å bruke for brukerens fornavn (vanligvis \"givenName\")"
+
+#: src/metabase/integrations/ldap.clj
+msgid "Attribute to use for the user''s last name. (usually ''sn'')"
+msgstr "Attributt å bruke for brukerens etternavn (vanligvis \"sn\")"
+
+#: src/metabase/integrations/ldap.clj
+msgid "Enable group membership synchronization with LDAP."
+msgstr "Aktiver synkronisering av gruppemedlemsskap med LDAP."
+
+#: src/metabase/integrations/ldap.clj
+msgid "Search base for groups, not required if your LDAP directory provides a ''memberOf'' overlay. (Will be searched recursively)"
+msgstr "Base for søk i grupper, ikke påkrevd hvis din LDAP-katalog tilbyr et \"memberOf\" overlegg. (Vil bli søkt rekursivt)"
+
+#. Should be in the form: {"cn=Some Group,dc=...": [1, 2, 3]} where keys are LDAP groups and values are lists of MB groups IDs
+#: src/metabase/integrations/ldap.clj
+msgid "JSON containing LDAP to Metabase group mappings."
+msgstr "JSON som knytter sammen LDAP- og Metabase-grupper."
+
+#. Define a setting which captures our Slack api token
+#: src/metabase/integrations/slack.clj
+msgid "Slack API bearer token obtained from https://api.slack.com/web#authentication"
+msgstr "Slack API bearer token som fåes fra https://api.slack.com/web#authentication"
+
+#: src/metabase/metabot.clj
+msgid "Enable MetaBot, which lets you search for and view your saved questions directly via Slack."
+msgstr "Aktiver MetaBot, som lar deg søke etter og vise spørsmålene dine direkte via Slack."
+
+#: src/metabase/metabot.clj
+msgid "Last MetaBot checkin was {0} ago."
+msgstr "Sist MetaBot sjekket inn var {0} siden."
+
+#: src/metabase/metabot.clj
+msgid "This instance will now handle MetaBot duties."
+msgstr "Denne instansen vil nå ta hånd om MetaBase forespørsler."
+
+#: src/metabase/metabot.clj
+msgid "Here''s what I can {0}:"
+msgstr "Her er hva jeg kan {0}:"
+
+#: src/metabase/metabot.clj
+msgid "I don''t know how to {0} `{1}`.n{2}"
+msgstr "Jeg vet ikke hvordan man {0} `{1}`.n{2}"
+
+#: src/metabase/metabot.clj
+msgid "Uh oh! :cry:n> {0}"
+msgstr "Oi sann! :cry:n> {0}"
+
+#: src/metabase/metabot.clj
+msgid "Here''s your {0} most recent cards:n{1}"
+msgstr "Her er dine {0} siste kort:n{1}"
+
+#: src/metabase/metabot.clj
+msgid "Could you be a little more specific? I found these cards with names that matched:n{0}"
+msgstr "Kan du være litt mer spesifikk? Jeg fant disse kortene med navn som stemmer overens:n{0}"
+
+#: src/metabase/metabot.clj
+msgid "I don''t know what Card `{0}` is. Give me a Card ID or name."
+msgstr "Jeg vet ikke hvilket kort `{0}` er. Gi meg et kort-ID eller navn."
+
+#: src/metabase/metabot.clj
+msgid "Show which card? Give me a part of a card name or its ID and I can show it to you. If you don''t know which card you want, try `metabot list`."
+msgstr "Vise hvilket kort? Gi meg en del av navnet på kortet eller dets ID så kan jeg vise deg det. Hvis du ikke vet hvilket kort du vil ha, prøv `metabot list`."
+
+#: src/metabase/metabot.clj
+msgid "Ok, just a second..."
+msgstr "Ok, vent ett lite sekund..."
+
+#: src/metabase/metabot.clj
+msgid "Not Found"
+msgstr "Ikke funnet"
+
+#: src/metabase/metabot.clj
+msgid "Loading Kanye quotes..."
+msgstr "Laster Kanye sitater..."
+
+#: src/metabase/metabot.clj
+msgid "Evaluating Metabot command:"
+msgstr "Evaluerer MetaBot kommando:"
+
+#: src/metabase/metabot.clj
+msgid "Go home websocket, you're drunk."
+msgstr "GÃ¥ hjem nettplugg, du er full."
+
+#: src/metabase/metabot.clj
+msgid "Error launching metabot:"
+msgstr "Feil innlasting av MetaBot:"
+
+#: src/metabase/metabot.clj
+msgid "MetaBot WebSocket is closed. Reconnecting now."
+msgstr "MetaBot WebSocket er lukket. Kobler opp på nytt nå."
+
+#: src/metabase/metabot.clj
+msgid "Error connecting websocket:"
+msgstr "Feil ved kobling til websocket:"
+
+#: src/metabase/metabot.clj
+msgid "This instance is performing MetaBot duties."
+msgstr "Denne instansen utfører MetaBot-plikter."
+
+#: src/metabase/metabot.clj
+msgid "Another instance is already handling MetaBot duties."
+msgstr "En annen instans utfører allerede MetaBot-plikter."
+
+#: src/metabase/metabot.clj
+msgid "Starting MetaBot threads..."
+msgstr "Starter MetaBase tråder..."
+
+#: src/metabase/metabot.clj
+msgid "Stopping MetaBot...  🤖"
+msgstr "Stopper MetaBot... 🤖"
+
+#: src/metabase/metabot.clj
+msgid "MetaBot already running. Killing the previous WebSocket listener first."
+msgstr "MetaBot kjører allerede. Avslutter den forrige websocket kjøringen først."
+
+#: src/metabase/middleware.clj
+msgid "Base-64 encoded public key for this site's SSL certificate."
+msgstr "Base-64 kodet offentlig nøkkel for denne siden sin SSL sertifikat."
+
+#: src/metabase/middleware.clj
+msgid "Specify this to enable HTTP Public Key Pinning."
+msgstr "Spesifiser dette for å aktivere HTTP Public Key Pinning."
+
+#: src/metabase/middleware.clj
+msgid "See {0} for more information."
+msgstr "Se {0} for mer informasjon."
+
+#: src/metabase/models/card.clj
+msgid "Cannot save Question: source query has circular references."
+msgstr "Kan ikke lagre spørsmål: hovedspørring har sirkulære referanser."
+
+#: src/metabase/models/card.clj src/metabase/models/query/permissions.clj
+#: src/metabase/query_processor/middleware/permissions.clj
+msgid "Card {0} does not exist."
+msgstr "Kort {0} fins ikke."
+
+#: src/metabase/models/card.clj
+msgid "You do not have permissions to run ad-hoc native queries against Database {0}."
+msgstr "Du har ikke tilganger til å kjøre lokal spørring i nåtid mot database {0}."
+
+#: src/metabase/models/collection.clj
+msgid "Invalid color"
+msgstr "Ugyldig farge"
+
+#: src/metabase/models/collection.clj
+msgid "must be a valid 6-character hex color code"
+msgstr "må være en gyldig 6-tegn hex fargekode"
+
+#: src/metabase/models/collection.clj
+msgid "Collection name cannot be blank!"
+msgstr "Kolleksjonsnavn kan ikke være blank!"
+
+#: src/metabase/models/collection.clj
+msgid "cannot be blank"
+msgstr "kan ikke være blank"
+
+#: src/metabase/models/collection.clj
+msgid "Invalid Collection location: path is invalid."
+msgstr "Ugyldig kolleksjonslokasjon: lokasjon er ugyldig."
+
+#: src/metabase/models/collection.clj
+msgid "You cannot move a Personal Collection."
+msgstr "Du kan ikke flytte en personlig kolleksjon."
+
+#: src/metabase/models/collection.clj
+msgid "Invalid Collection location: some or all ancestors do not exist."
+msgstr "Ugyldig kolleksjon lokasjon: noen eller alle toppkolleksjoner fins ikke."
+
+#: src/metabase/models/collection.clj
+msgid "You cannot archive the Root Collection."
+msgstr "Du kan ikke arkivere hovedkolleksjonen."
+
+#: src/metabase/models/collection.clj
+msgid "You cannot archive a Personal Collection."
+msgstr "Du kan ikke arkivere en personlig kolleksjon."
+
+#: src/metabase/models/collection.clj
+msgid "You cannot move the Root Collection."
+msgstr "Du kan ikke flytte hovedkolleksjonen."
+
+#: src/metabase/models/collection.clj
+msgid "You cannot move a Collection into itself or into one of its descendants."
+msgstr "Du kan ikke flytte en kolleksjon til seg selv eller til en av underkolleksjonene."
+
+#. first move this Collection
+#: src/metabase/models/collection.clj
+msgid "Moving Collection {0} and its descendants from {1} to {2}"
+msgstr "Flytter samlingen {0} og dens etterkommere fra {1} til {2}"
+
+#: src/metabase/models/collection.clj
+msgid "You're not allowed to change the owner of a Personal Collection."
+msgstr "Du har ikke lov til å endre eieren av en personlig samling."
+
+#: src/metabase/models/collection.clj
+msgid "You're not allowed to move a Personal Collection."
+msgstr "Du har ikke lov til å flytte en personlig samling."
+
+#: src/metabase/models/collection.clj
+msgid "You cannot move a Collection and archive it at the same time."
+msgstr "Du kan ikke flytte en samling og arkivere den samtidig."
+
+#: src/metabase/models/collection.clj
+msgid "You cannot delete a Personal Collection!"
+msgstr "Du kan ikke slette en Personling samling"
+
+#: src/metabase/models/collection.clj
+msgid "{0} {1}''s Personal Collection"
+msgstr "{0} {1}s personlige samling"
+
+#: src/metabase/models/collection_revision.clj
+msgid "You cannot update a CollectionRevision!"
+msgstr "Du kan ikke oppdatere en CollectionRevision!"
+
+#: src/metabase/models/field_values.clj
+msgid "Field {0} was previously automatically set to show a list widget, but now has {1} values."
+msgstr "Feltet {0} var tidligere automatisk satt til å vise en liste-widget, men har nå {1} verdier."
+
+#: src/metabase/models/field_values.clj
+msgid "Switching Field to use a search widget instead."
+msgstr "Endrer feltet til å vise en søke-widget istedenfor."
+
+#: src/metabase/models/field_values.clj
+msgid "Storing updated FieldValues for Field {0}..."
+msgstr "Lagrer oppdaterte feltverdier for feltet {0}..."
+
+#: src/metabase/models/field_values.clj
+msgid "Storing FieldValues for Field {0}..."
+msgstr "Lagrer feltverdier for feltet {0}..."
+
+#: src/metabase/models/humanization.clj
+msgid "Metabase can attempt to transform your table and field names into more sensible, human-readable versions, e.g. \"somehorriblename\" becomes \"Some Horrible Name\"."
+msgstr "Metabase kan forsøke å transformere tabellen og feltnavnene til mer fornuftige, menneske-lesbare versjoner, f.eks. \"somehorriblename\" blir til \"Some Horrible Name\"."
+
+#: src/metabase/models/humanization.clj
+msgid "This doesn’t work all that well if the names are in a language other than English, however."
+msgstr "Men, dette virker ikke så bra for feltnavn som ikke er på engelsk."
+
+#: src/metabase/models/humanization.clj
+msgid "Do you want us to take a guess?"
+msgstr "Vil du at vi skal gjette?"
+
+#: src/metabase/models/permissions.clj
+msgid "You cannot create or revoke permissions for the 'Admin' group."
+msgstr "Du kan ikke gi eller frata rettigheter for 'Admin'-gruppen."
+
+#: src/metabase/models/permissions.clj
+msgid "Invalid permissions object path: ''{0}''."
+msgstr "Ugyldig sti for rettighetsobjekt: \"{0}\"."
+
+#: src/metabase/models/permissions.clj
+msgid "You cannot update a permissions entry!"
+msgstr "Du kan ikke oppdatere en tilgangsmodell!"
+
+#: src/metabase/models/permissions.clj
+msgid "Delete it and create a new one."
+msgstr "Slett den og lag en ny en."
+
+#: src/metabase/models/permissions.clj
+msgid "You cannot edit permissions for a Personal Collection or its descendants."
+msgstr "Du kan ikke redigere tilganger for personlig kolleksjon eller underkolleksjonene."
+
+#: src/metabase/models/permissions.clj
+msgid "Looks like someone else edited the permissions and your data is out of date."
+msgstr "Det ser ut som om noen andre endret rettighetene og dine data er utdatert."
+
+#: src/metabase/models/permissions.clj
+msgid "Please fetch new data and try again."
+msgstr "Vennligst last inn ny data og prøv igjen."
+
+#: src/metabase/models/permissions_group.clj
+msgid "Created magic permissions group ''{0}'' (ID = {1})"
+msgstr "Lag magisk tilgangseksjonen \"{0}\" (ID = {1})"
+
+#: src/metabase/models/permissions_group.clj
+msgid "A group with that name already exists."
+msgstr "En gruppe med det navnet eksisterer allerede."
+
+#: src/metabase/models/permissions_group.clj
+msgid "You cannot edit or delete the ''{0}'' permissions group!"
+msgstr "Du kan ikke redigere eller slette \"{0}\" tilgangsgruppen!"
+
+#: src/metabase/models/permissions_group_membership.clj
+msgid "You cannot add or remove users to/from the 'MetaBot' group."
+msgstr "Du kan ikke legge til eller slette brukere til/fra 'MetaBot' gruppen."
+
+#: src/metabase/models/permissions_group_membership.clj
+msgid "You cannot add or remove users to/from the 'All Users' group."
+msgstr "Du kan ikke legge til eller slette brukere til/fra 'alle brukere' gruppen."
+
+#: src/metabase/models/permissions_group_membership.clj
+msgid "You cannot remove the last member of the 'Admin' group!"
+msgstr "Du kan ikke slette siste medlem av 'Administrator' gruppen!"
+
+#: src/metabase/models/permissions_revision.clj
+msgid "You cannot update a PermissionsRevision!"
+msgstr "Du kan ikke oppdatere en tilgangseksjonen!"
+
+#. if there's still not a Card, throw an Exception!
+#: src/metabase/models/pulse.clj
+msgid "Invalid Alert: Alert does not have a Card assoicated with it"
+msgstr "Ugyldig varsel: Varselet er ikke tilknyttet et kort"
+
+#: src/metabase/models/pulse.clj
+msgid "value must be a map with the keys `{0}`, `{1}`, and `{2}`."
+msgstr "verdien må være et oppslagsverk med nøklene `{0}`, `{1}` og `{2}`."
+
+#: src/metabase/models/pulse.clj
+msgid "value must be a map with the following keys `({0})`"
+msgstr "verdien må være et oppslagsverk med disse nøklene `({0})`"
+
+#: src/metabase/models/query/permissions.clj
+msgid "Error calculating permissions for query: {0}"
+msgstr "En feil oppstod ved utregning av tillatelser for spørringen: {0}"
+
+#: src/metabase/models/query/permissions.clj
+msgid "Invalid query type: {0}"
+msgstr "Ugyldig spørringstype: {0}"
+
+#: src/metabase/models/query_execution.clj
+msgid "You cannot update a QueryExecution!"
+msgstr "Du kan ikke oppdatere en QueryExecution!"
+
+#: src/metabase/models/revision.clj
+msgid "You cannot update a Revision!"
+msgstr "Du kan ikke oppdatere en Revision!"
+
+#: src/metabase/models/setting.clj
+msgid "Setting {0} does not exist.nFound: {1}"
+msgstr "Innstillingen {0} finnes ikke.nFant: {1}"
+
+#: src/metabase/models/setting.clj
+msgid "Updating value of settings-last-updated in DB..."
+msgstr "Oppdaterer verdien for settings-last-updated i DB..."
+
+#: src/metabase/models/setting.clj
+msgid "Checking whether settings cache is out of date (requires DB call)..."
+msgstr "Sjekker om mellomlageret for innstillinger er utdatert (krever kall til DB)..."
+
+#: src/metabase/models/setting.clj
+msgid "Settings have been changed on another instance, and will be reloaded here."
+msgstr "Innstillingene har endret seg på en annen instans, og vil bli lastet inn på nytt her."
+
+#: src/metabase/models/setting.clj
+msgid "Refreshing Settings cache..."
+msgstr "Oppdaterer mellomlageret for innstillinger..."
+
+#: src/metabase/models/setting.clj
+msgid "Invalid value for string: must be either \"true\" or \"false\" (case-insensitive)."
+msgstr "Ugyldig verdi for tekststreng: må være enten \"true\" eller \"false\" (store/små bokstaver har ingen betydning)."
+
+#: src/metabase/models/setting.clj
+msgid "You cannot update `settings-last-updated` yourself! This is done automatically."
+msgstr "Du kan ikke oppdatere `settings-last-updated` selv! Dette gjøres automatisk."
+
+#. go ahead and log the Exception anyway on the off chance that it *wasn't* just a race condition issue
+#: src/metabase/models/setting.clj
+msgid "Error inserting a new Setting:"
+msgstr "En feil oppstod ved innsetting av en ny innstilling:"
+
+#: src/metabase/models/setting.clj
+msgid "Assuming Setting already exists in DB and updating existing value."
+msgstr "Antar at innstillingen allerede finnes i DB og oppdaterer eksisterende verdi."
+
+#: src/metabase/models/user.clj
+msgid "value must be a map with each value either a string or number."
+msgstr "verdi må være et oppslagsverk hvor hver verdi er enten en tekststreng eller et tall."
+
+#: src/metabase/plugins.clj
+msgid "Loading plugins in directory {0}..."
+msgstr "Laster programtillegget fra mappen {0}..."
+
+#: src/metabase/plugins.clj
+msgid "Loading plugin {0}... "
+msgstr "Laster inn plugin {0}... "
+
+#: src/metabase/plugins.clj
+#, fuzzy
+msgid "It looks like you have some external dependencies in your Metabase plugins directory."
+msgstr "Det ser ut som du har eksterne avhengigheter i din Metabase plugins-mappe."
+
+#: src/metabase/plugins.clj
+msgid "With Java 9 or higher, Metabase cannot automatically add them to your classpath."
+msgstr "Med Java 9 eller høyere, Metabase kan ikke automatisk legge de til i klassemappen"
+
+#: src/metabase/plugins.clj
+msgid "Instead, you should include them at launch with the -cp option. For example:"
+msgstr "Dermed må du inkludere disse ved oppstart med -cp konfigureringen. For eksempel:"
+
+#: src/metabase/plugins.clj
+msgid "See https://metabase.com/docs/latest/operations-guide/start.html#java-versions for more details."
+msgstr "Se https://metabase.com/docs/latest/operations-guide/start.html#java-versions for mer deltaljer."
+
+#: src/metabase/plugins.clj
+msgid "(If you're already running Metabase this way, you can ignore this message.)"
+msgstr "(Hvis du allerede kjører Metabase med denne måten, kan du ignorere denne meldingen)"
+
+#: src/metabase/public_settings.clj
+msgid "Identify when new versions of Metabase are available."
+msgstr "Identifiser når det kommer nye Metabase versjoner."
+
+#: src/metabase/public_settings.clj
+msgid "Information about available versions of Metabase."
+msgstr "Informasjon om tilgjengelige versjoner av Metabase."
+
+#: src/metabase/public_settings.clj
+msgid "The name used for this instance of Metabase."
+msgstr "Navnet brukt for instansen av Metabase."
+
+#: src/metabase/public_settings.clj
+msgid "The base URL of this Metabase instance, e.g. \"http://metabase.my-company.com\"."
+msgstr "Base nettadresse for Metabase instansen, f.eks \"http://metabase.my-company.com\"."
+
+#: src/metabase/public_settings.clj
+msgid "The default language for this Metabase instance."
+msgstr "Standard språk for denne Metabase instansen."
+
+#: src/metabase/public_settings.clj
+msgid "This only applies to emails, Pulses, etc. Users'' browsers will specify the language used in the user interface."
+msgstr "Dette gjelder kun for e-poster, pulser, osv. Brukeres nettlesere def"
+
+#: src/metabase/public_settings.clj
+msgid "The email address users should be referred to if they encounter a problem."
+msgstr "E-postadressen brukere skal referere til hvis det oppstår ett problem."
+
+#: src/metabase/public_settings.clj
+msgid "Enable the collection of anonymous usage data in order to help Metabase improve."
+msgstr "Aktiver logging av anonym brukerdata for å hjelpe Metabase med å bli bedre."
+
+#: src/metabase/public_settings.clj
+msgid "The map tile server URL template used in map visualizations, for example from OpenStreetMaps or MapBox."
+msgstr "URL-malen for kartflis-tjeneren som brukes i kart-visualiseringer, for eksempel fra OpenStreetMaps eller MapBox."
+
+#: src/metabase/public_settings.clj
+msgid "Enable admins to create publicly viewable links (and embeddable iframes) for Questions and Dashboards?"
+msgstr "Gjør det mulig for administratorer å lage offentlig tilgjengelige lenker (og innbyggbare iframes) for spørsmål og infotavler?"
+
+#: src/metabase/public_settings.clj
+msgid "Allow admins to securely embed questions and dashboards within other applications?"
+msgstr "Gjør det mulig for administratorer å bygge inn spørsmål og infotavler på en sikker måte inni andre applikasjoner?"
+
+#: src/metabase/public_settings.clj
+msgid "Allow using a saved question as the source for other queries?"
+msgstr "Godkjenn bruk av lagrede spørsmål som grunnen til andre spørringer."
+
+#: src/metabase/public_settings.clj
+msgid "Enabling caching will save the results of queries that take a long time to run."
+msgstr "Aktivering av mellomlagring vil lagre resultater av spørringer som tar lang til å kjøre."
+
+#: src/metabase/public_settings.clj
+msgid "The maximum size of the cache, per saved question, in kilobytes:"
+msgstr "Maks-størrelsen til mellomlageret, per lagrede spørsmål, i kilobytes:"
+
+#: src/metabase/public_settings.clj
+msgid "The absolute maximum time to keep any cached query results, in seconds."
+msgstr "Den absolutt lengste tiden som mellomlagrede spørringsresultater skal beholdes, i sekunder."
+
+#: src/metabase/public_settings.clj
+msgid "Metabase will cache all saved questions with an average query execution time longer than this many seconds:"
+msgstr "Metabase vil mellomlagre alle lagrede spørsmål med en gjennomsnittlig utførelse tid som er lenger enn dette i sekunder:"
+
+#: src/metabase/public_settings.clj
+msgid "To determine how long each saved question''s cached result should stick around, we take the query''s average execution time and multiply that by whatever you input here."
+msgstr "For å avgjøre hvor lenge hvert lagrede spørsmåls mellomlagrede resultat skal bli værende, så tar vi spørringens gjennomsnittlige kjøretid og ganger det med hva enn du skriver inn her."
+
+#: src/metabase/public_settings.clj
+msgid "So if a query takes on average 2 minutes to run, and you input 10 for your multiplier, its cache entry will persist for 20 minutes."
+msgstr "Så hvis en spørring tar gjennomsnittlig 2 minutter å kjøre, og du skriver inn 10 for din multipliserer, så vil mellomlagringen være tilgjengelig i 20 minutter."
+
+#: src/metabase/public_settings.clj
+msgid "When using the default binning strategy and a number of bins is not provided, this number will be used as the default."
+msgstr "NÃ¥r den vanlige inndelings-strategien brukes og et antall inndelinger ikke er gitt, vil dette antallet bli brukt som standard."
+
+#: src/metabase/public_settings.clj
+msgid "When using the default binning strategy for a field of type Coordinate (such as Latitude and Longitude), this number will be used as the default bin width (in degrees)."
+msgstr "Når den vanlige inndelings-strategien brukes for et koordinat-felt (sånn som lengdegrad og breddegrad), så vil dette tallet bli brukt som standardstørrelse på inndelingen (i grader)."
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Unable to validate token."
+msgstr "Klarte ikke å validere token."
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Error fetching token status:"
+msgstr "En feil oppstod ved henting av status for token:"
+
+#: src/metabase/public_settings/metastore.clj
+msgid "There was an error checking whether this token was valid."
+msgstr "Det oppstod en feil ved validering av tokenen."
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Token validation timed out."
+msgstr "Tidsavbrudd for token-validering."
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Invalid token: token isn't in the right format."
+msgstr "Invalid token: token er ikke på riktig format."
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Checking with the MetaStore to see whether {0} is valid..."
+msgstr "Sjekker med MetaStore for å se om {0} er valid..."
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Token for premium embedding. Go to the MetaStore to get yours!"
+msgstr "Token for premium-innebygging. Gå til MetaStore for å få din egen!"
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Token is valid."
+msgstr "Nøkkel er gyldig."
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Error setting premium embedding token"
+msgstr "En feil oppstod ved setting av premium innebyggingstoken."
+
+#: src/metabase/pulse.clj
+msgid "Unable to compare results to goal for alert."
+msgstr "Klarte ikke å sammenligne resultatene med målet for varselet."
+
+#: src/metabase/pulse.clj
+msgid "Question ID is ''{0}'' with visualization settings ''{1}''"
+msgstr "Spørsmål-ID er \"{0}\" med visualiseringsinnstilingene \"{1}\""
+
+#: src/metabase/pulse.clj
+msgid "Unrecognized alert with condition ''{0}''"
+msgstr "Ikke gjenkjent varsel med betingelsen \"{0}\""
+
+#: src/metabase/pulse.clj
+msgid "Unrecognized channel type {0}"
+msgstr "Ikke gjenkjent kanal-type {0}"
+
+#: src/metabase/pulse.clj
+msgid "Error sending notification!"
+msgstr "Feil ved sending av varsel!"
+
+#: src/metabase/pulse/color.clj
+msgid "Can't find JS color selector at ''{0}''"
+msgstr "Kan ikke finne JS farge velger her \"{0}\""
+
+#: src/metabase/pulse/render.clj
+msgid "Card has errors: {0}"
+msgstr "Kort har feil: {0}"
+
+#: src/metabase/pulse/render.clj
+msgid "Pulse card render error"
+msgstr "Feil ved visning av pulskort"
+
+#: src/metabase/query_processor/middleware/fetch_source_query.clj
+msgid "Trimming trailing comment from card with id {0}"
+msgstr "Beskjærer etterfølgende kommentar fra kortet med id {0}"
+
+#: src/metabase/query_processor/middleware/parameters/sql.clj
+msgid "Can't find field with ID: {0}"
+msgstr "Kan ikke finne felt med ID: {0}"
+
+#: src/metabase/query_processor/middleware/parameters/sql.clj
+msgid "''{0}'' is a required param."
+msgstr "\"{0}\" er ett påkrevd parameter."
+
+#: src/metabase/query_processor/middleware/parameters/sql.clj
+msgid "Found ''{0}'' with no terminating ''{1}'' in query ''{2}''"
+msgstr "Fant \"{0}\" uten avsluttende \"{1}\" i spørringen \"{2}\""
+
+#: src/metabase/query_processor/middleware/parameters/sql.clj
+msgid "Unable to substitute ''{0}'': param not specified.nFound: {1}"
+msgstr "Klarte ikke å erstatte \"{0}\": param ikke spesifisert.nFant: {1}"
+
+#: src/metabase/query_processor/middleware/permissions.clj
+msgid "You do not have permissions to view Card {0}."
+msgstr "Du har ikke tilgang til å vise kort {0}."
+
+#: src/metabase/query_processor/middleware/permissions.clj
+msgid "You do not have permissions to run this query."
+msgstr "Du har ikke tilganger til å kjøre denne spørringen."
+
+#: src/metabase/sync/analyze.clj
+msgid "Fingerprint updates attempted {0}, updated {1}, no data found {2}, failed {3}"
+msgstr "Fingeravtrykk-oppdateringer prøvde {0}, oppdaterte {1}, ingen data funnet {2}, feilet {3}"
+
+#: src/metabase/sync/analyze.clj
+msgid "Total number of fields classified {0}, {1} failed"
+msgstr "Totalt antall felt klassifisert {0}, {1} feilet"
+
+#: src/metabase/sync/analyze.clj
+msgid "Total number of tables classified {0}, {1} updated"
+msgstr "Total antall tabeller klassifisert {0}, {1} oppdatert"
+
+#: src/metabase/sync/analyze/fingerprint/fingerprinters.clj
+msgid "Error generating fingerprint for {0}"
+msgstr "Feil ved generering av fingeravtrykk for {0}"
+
+#: src/metabase/sync/field_values.clj
+msgid "Updated {0} field value sets, created {1}, deleted {2} with {3} errors"
+msgstr "Oppdatert {0} felt verdisett, opprettet {1}, slettet {2} med {3} feil"
+
+#: src/metabase/sync/sync_metadata.clj
+msgid "Total number of fields sync''d {0}, number of fields updated {1}"
+msgstr "Total antall felt synkronisert {0}, antall felt oppdatert {1}"
+
+#: src/metabase/sync/sync_metadata.clj
+msgid "Total number of tables sync''d {0}, number of tables updated {1}"
+msgstr "Total antall tabeller synkronisert {0}, antall tabeller oppdatert {1}"
+
+#: src/metabase/sync/sync_metadata.clj
+msgid "Found timezone id {0}"
+msgstr "Fant tidssone id {0}"
+
+#: src/metabase/sync/sync_metadata.clj
+msgid "Total number of foreign keys sync''d {0}, {1} updated and {2} tables failed to update"
+msgstr "Total antall fremmednøkler synkronisert {0}, {1} oppdatert og {2} tabeller mislyktes å oppdatere"
+
+#: src/metabase/sync/util.clj
+msgid "{0} Database {1} ''{2}''"
+msgstr "{0} Database {1} \"{2}\""
+
+#: src/metabase/sync/util.clj
+msgid "Table {0} ''{1}''"
+msgstr "Tabell {0} \"{1}\""
+
+#: src/metabase/sync/util.clj
+msgid "Field {0} ''{1}''"
+msgstr "Felt {0} \"{1}\""
+
+#: src/metabase/sync/util.clj
+msgid "Field ''{0}''"
+msgstr "Felt \"{0}\""
+
+#: src/metabase/sync/util.clj
+msgid "step ''{0}'' for {1}"
+msgstr "steg \"{0}\" for {1}"
+
+#: src/metabase/sync/util.clj
+msgid "Completed {0} on {1}"
+msgstr "Ferdigstilt {0} på {1}"
+
+#: src/metabase/sync/util.clj
+msgid "Start: {0}"
+msgstr "Start: {0}"
+
+#: src/metabase/sync/util.clj
+msgid "End: {0}"
+msgstr "Slutt: {0}"
+
+#: src/metabase/sync/util.clj
+msgid "Duration: {0}"
+msgstr "Tid brukt: {0}"
+
+#: src/metabase/sync/util.clj
+msgid "Completed step ''{0}''"
+msgstr "Ferdigstilt steg \"{0}\""
+
+#: src/metabase/task.clj
+#, fuzzy
+msgid "Loading tasks namespace:"
+msgstr "Laster inn navnerom for oppgaver:"
+
+#: src/metabase/task.clj
+msgid "Starting Quartz Scheduler"
+msgstr "Starter Quartz Scheduler"
+
+#: src/metabase/task.clj
+msgid "Stopping Quartz Scheduler"
+msgstr "Stopper Quartz Scheduler"
+
+#: src/metabase/task.clj
+msgid "Job already exists:"
+msgstr "Jobben eksisterer allerede:"
+
+#. 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
+#: src/metabase/util.clj
+msgid "Loading Metabase..."
+msgstr "Laster inn Metabase..."
+
+#: src/metabase/util/date.clj
+msgid "Possible timezone conflict found on database {0}."
+msgstr "Mulig tidssone konflikt funnet i database {0}"
+
+#: src/metabase/util/date.clj
+msgid "JVM timezone is {0} and detected database timezone is {1}."
+msgstr "JVM tidssone er {0}, oppdaget database tidssone er {1}"
+
+#: src/metabase/util/date.clj
+msgid "Configure a report timezone to ensure proper date and time conversions."
+msgstr "Konfigurer en rapport tidssone for å sikre dato- og tidskonvertering"
+
+#: src/metabase/util/embed.clj
+msgid "Secret key used to sign JSON Web Tokens for requests to `/api/embed` endpoints."
+msgstr "Hemmelig nøkkel for å signere JSON Web Tokens for tilkobling til `/api/embed`."
+
+#: src/metabase/util/encryption.clj
+msgid "MB_ENCRYPTION_SECRET_KEY must be at least 16 characters."
+msgstr "MB_ENCRYPTION_SECRET_KEY må være minst 16 tegn."
+
+#: src/metabase/util/encryption.clj
+msgid "Saved credentials encryption is ENABLED for this Metabase instance."
+msgstr "Lagret legitimasjonskryptering er ENABLED for denne Metabase-instansen."
+
+#: src/metabase/util/encryption.clj
+msgid "Saved credentials encryption is DISABLED for this Metabase instance."
+msgstr "Lagret legitimasjonskryptering er DISABLED for denne Metabase-instansen."
+
+#: src/metabase/util/encryption.clj
+msgid "nFor more information, see"
+msgstr "For mer informasjon, se"
+
+#: src/metabase/util/schema.clj
+msgid "value must be an integer."
+msgstr "verdi må være ett heltall."
+
+#: src/metabase/util/schema.clj
+msgid "value must be a string."
+msgstr "verdi må være en streng."
+
+#: src/metabase/util/schema.clj
+#, fuzzy
+msgid "value must be a boolean."
+msgstr "verdien må være boolsk."
+
+#: src/metabase/util/schema.clj
+#, fuzzy
+msgid "value must be a string that matches the regex `{0}`."
+msgstr "verdi må være en streng som tilfredsstiller kravene til regex `{0}`."
+
+#: src/metabase/util/schema.clj
+msgid "value must satisfy one of the following requirements: "
+msgstr "verdi må tilfredsstille en av følgende krav: "
+
+#: src/metabase/util/schema.clj
+msgid "value may be nil, or if non-nil, {0}"
+msgstr "verdi må være null, eller hvis ikke null, {0}"
+
+#: src/metabase/util/schema.clj
+msgid "value must be one of: {0}."
+msgstr "verdi må være en av disse: {0}."
+
+#: src/metabase/util/schema.clj
+msgid "value must be an array."
+msgstr "verdi må være en matrise."
+
+#: src/metabase/util/schema.clj
+msgid "Each {0}"
+msgstr "Hver {0}"
+
+#: src/metabase/util/schema.clj
+msgid "The array cannot be empty."
+msgstr "Matrisen kan ikke være tom."
+
+#: src/metabase/util/schema.clj
+msgid "value must be a non-blank string."
+msgstr "verdi kan ikke være blank streng."
+
+#: src/metabase/util/schema.clj
+msgid "Integer greater than zero"
+msgstr "Heltall mer enn 0"
+
+#: src/metabase/util/schema.clj
+msgid "value must be an integer greater than zero."
+msgstr "verdi må være ett heltall over 0."
+
+#: src/metabase/util/schema.clj
+msgid "Number greater than zero"
+msgstr "Tall over 0"
+
+#: src/metabase/util/schema.clj
+msgid "value must be a number greater than zero."
+msgstr "verdi må være ett tall over null"
+
+#: src/metabase/util/schema.clj
+msgid "Keyword or string"
+msgstr "Søkeord eller streng"
+
+#: src/metabase/util/schema.clj
+msgid "Valid field type"
+msgstr "Gyldig felt type"
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid field type."
+msgstr "verdi må være en gyldig felt type."
+
+#: src/metabase/util/schema.clj
+msgid "Valid field type (keyword or string)"
+msgstr "Gyldig felt type (søkeord eller streng)"
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid field type (keyword or string)."
+msgstr "verdi må være en gyldig felt type (søkeord eller streng)"
+
+#: src/metabase/util/schema.clj
+msgid "Valid entity type (keyword or string)"
+msgstr "Gyldig enhetstype (søkeord eller streng)"
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid entity type (keyword or string)."
+msgstr "verdi må være en gyldig enhetstype (søkeord eller streng)"
+
+#: src/metabase/util/schema.clj
+msgid "Valid map"
+msgstr "Gyldig kart"
+
+#: src/metabase/util/schema.clj
+msgid "value must be a map."
+msgstr "verdi må være ett kart."
+
+#: src/metabase/util/schema.clj
+msgid "Valid email address"
+msgstr "Gyldig e-postadresse"
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid email address."
+msgstr "verdi må være en gyldig e-postadresse"
+
+#: src/metabase/util/schema.clj
+msgid "Insufficient password strength"
+msgstr "Ugyldig passord styrke"
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid integer."
+msgstr "verdi må være ett gyldig heltall"
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid integer greater than zero."
+msgstr "verdi må være ett gyldig heltall større enn null."
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid boolean string (''true'' or ''false'')."
+msgstr "verdi må være en gyldig boolean streng (\"true\" eller \"false\")"
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid JSON string."
+msgstr "verdi må være en gyldig JSON streng."
+
+#: src/metabase/util/schema.clj
+#, fuzzy
+msgid "value must be a valid embedding params map."
+msgstr "verdi må være en gyldig embedding params kart."
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsTabs.jsx:12
+msgid "Data permissions"
+msgstr "Data tilganger"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsTabs.jsx:13
+msgid "Collection permissions"
+msgstr "Kolleksjonstilganger"
+
+#: frontend/src/metabase/admin/permissions/containers/CollectionPermissionsModal.jsx:56
+msgid "See all collection permissions"
+msgstr "Se alle kolleksjonstilganger"
+
+#: frontend/src/metabase/admin/permissions/containers/TogglePropagateAction.jsx:25
+msgid "Also change sub-collections"
+msgstr "Endre også underkolleksjoner"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:278
+msgid "Can edit this collection and its contents"
+msgstr "Kan endre denne kolleksjonen og inneholdet"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:285
+msgid "Can view items in this collection"
+msgstr "Kan se elementer i denne kolleksjonen"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:745
+msgid "Collection Access"
+msgstr "Kolleksjonstilgang"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:821
+msgid "This group has permission to view at least one subcollection of this collection."
+msgstr "Denne gruppen har tilgang til å vise minst en underkolleksjonene av denne kolleksjonen."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:826
+msgid "This group has permission to edit at least one subcollection of this collection."
+msgstr "Denne gruppen har tilganger til å endre minst en av underkolleksjonene av denne kolleksjonen."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:839
+msgid "View sub-collections"
+msgstr "Hvis underkolleksjoner"
+
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:211
+msgid "Remember Me"
+msgstr "Husk meg"
+
+#: frontend/src/metabase/components/BrowseApp.jsx:118
+msgid "X-ray this schema"
+msgstr "Kjør X-ray på dette skjema"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:246
+msgid "Edit the permissions for this collection"
+msgstr "Endre tilgangenene for denne kolleksjonen"
+
+#: frontend/src/metabase/containers/AddToDashSelectDashModal.jsx:51
+msgid "Add this question to a dashboard"
+msgstr "Legg dette spørsmålet til ett panel"
+
+#: frontend/src/metabase/containers/AddToDashSelectDashModal.jsx:61
+msgid "Create a new dashboard"
+msgstr "Legg til nytt panel"
+
+#: frontend/src/metabase/containers/ErrorPages.jsx:45
+msgid "The page you asked for couldn't be found."
+msgstr "Siden du spurte etter fins ikke."
+
+#: frontend/src/metabase/containers/ItemSelect.jsx:30
+msgid "Select a {0}"
+msgstr "Velg en {0}"
+
+#: frontend/src/metabase/containers/Overworld.jsx:188
+msgid "Save dashboards, questions, and collections in \"{0}\""
+msgstr "Lagre panel, spørsmål og kolleksjoner i \"{0}\""
+
+#: frontend/src/metabase/containers/Overworld.jsx:191
+msgid "Access dashboards, questions, and collections in \"{0}\""
+msgstr "Adgang til paneler, spørsmål og kolleksjoner i \"{0}\""
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:216
+msgid "Compare"
+msgstr "Sammenlign"
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:224
+msgid "Zoom out"
+msgstr "Zoom ut"
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:228
+msgid "Related"
+msgstr "Relatert"
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:288
+msgid "More X-rays"
+msgstr "Flere X-ray spørringer"
+
+#: frontend/src/metabase/home/containers/SearchApp.jsx:50
+msgid "No results"
+msgstr "Ingen resultater"
+
+#: frontend/src/metabase/home/containers/SearchApp.jsx:51
+msgid "Metabase couldn't find any results for your search."
+msgstr "Metabase kan ikke finne noen resultater for ditt søk."
+
+#: frontend/src/metabase/new_query/containers/MetricSearch.jsx:111
+msgid "No metrics"
+msgstr "Ingen indikatorer"
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:31
+#, fuzzy
+msgid "Aggregations"
+msgstr "Aggregeringer"
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:32
+msgid "Operators"
+msgstr "Operatører"
+
+#: frontend/src/metabase/query_builder/components/expressions/Expressions.jsx:30
+msgid "Custom fields"
+msgstr "Egendefinerte felter"
+
+#. 2. Create the new collections.
+#: src/metabase/db/migrations.clj
+msgid "Migrated Dashboards"
+msgstr "Migrerte paneler"
+
+#: src/metabase/db/migrations.clj
+msgid "Migrated Pulses"
+msgstr "Migrerte pulser"
+
+#: src/metabase/db/migrations.clj
+msgid "Migrated Questions"
+msgstr "Migrerte spørsmål"
+
+#. 4. move everything not in this Collection to a new Collection
+#: src/metabase/db/migrations.clj
+msgid "Moving instances of {0} that aren't in a Collection to {1} Collection {2}"
+msgstr "Flytter instanser av {0} som ikke er i en samling til {1} samling {2}"
+
+#: src/metabase/models/permissions.clj
+msgid "Failed to grant permissions: {0}"
+msgstr "Klarte ikke å gi tillatelser: {0}"
+
+#: src/metabase/util/encryption.clj
+msgid "Cannot decrypt encrypted string. Have you changed or forgot to set MB_ENCRYPTION_SECRET_KEY?"
+msgstr "Kan ikke dekryptere kryptert tekststreng. Har du endret eller glemt å sette MB_ENCRYPTION_SECRET_KEY?"
+
+#: frontend/src/metabase/entities/collections.js:157
+msgid "All personal collections"
+msgstr "Alle personlige kolleksjoner"
+
+#: src/metabase/driver.clj
+msgid "Host"
+msgstr "Vert"
+
+#: src/metabase/driver.clj
+msgid "Port"
+msgstr "Port"
+
+#: src/metabase/driver.clj
+msgid "Database username"
+msgstr "Database brukernavn"
+
+#: src/metabase/driver.clj
+msgid "What username do you use to login to the database?"
+msgstr "Hvilket brukernavn vil du bruke ved tilkobling til databasen?"
+
+#: src/metabase/driver.clj
+msgid "Database password"
+msgstr "Database passord"
+
+#: src/metabase/driver.clj
+msgid "Database name"
+msgstr "Database navn"
+
+#: src/metabase/driver.clj
+msgid "birds_of_the_world"
+msgstr "birds_of_the_world"
+
+#: src/metabase/driver.clj
+msgid "Use a secure connection (SSL)?"
+msgstr "Bruk sikker kobling (SSL)?"
+
+#: src/metabase/driver.clj
+msgid "Additional JDBC connection string options"
+msgstr "Ekstra JDBC koblingsstreng alternativer"
+
+#: src/metabase/driver/bigquery.clj
+msgid "Project ID"
+msgstr "Prosjekt ID"
+
+#: src/metabase/driver/bigquery.clj
+msgid "praxis-beacon-120871"
+msgstr "praxis-beacon-120871"
+
+#: src/metabase/driver/bigquery.clj
+msgid "Dataset ID"
+msgstr "Datasett ID"
+
+#: src/metabase/driver/bigquery.clj
+msgid "toucanSightings"
+msgstr "toucanSightings"
+
+#: src/metabase/driver/bigquery.clj src/metabase/driver/googleanalytics.clj
+msgid "Client ID"
+msgstr "Klient ID"
+
+#: src/metabase/driver/bigquery.clj src/metabase/driver/googleanalytics.clj
+msgid "Client Secret"
+msgstr "Klient hemmelighet"
+
+#: src/metabase/driver/bigquery.clj src/metabase/driver/googleanalytics.clj
+msgid "Auth Code"
+msgstr "Autoriseringskode"
+
+#: src/metabase/driver/crate.clj
+msgid "Hosts"
+msgstr "Verter"
+
+#: src/metabase/driver/druid.clj
+#, fuzzy
+msgid "Broker node port"
+msgstr "Knuteport for megler"
+
+#: src/metabase/driver/googleanalytics.clj
+msgid "Google Analytics Account ID"
+msgstr "Google Analytics konto ID"
+
+#: src/metabase/driver/h2.clj
+msgid "Connection String"
+msgstr "Koblingsstring"
+
+#: src/metabase/driver/h2.clj
+#, fuzzy
+msgid "Users/camsaul/bird_sightings/toucans"
+msgstr "Brukere/camsaul/bird_sightings/toucans"
+
+#: src/metabase/driver/mongo.clj
+msgid "carrierPigeonDeliveries"
+msgstr "carrierPigeonDeliveries"
+
+#: src/metabase/driver/mongo.clj
+msgid "Authentication Database"
+msgstr "Autorisering Database"
+
+#: src/metabase/driver/mongo.clj
+msgid "Optional database to use when authenticating"
+msgstr "Alternative database å bruke ved autorisering"
+
+#: src/metabase/driver/mongo.clj
+msgid "Additional Mongo connection string options"
+msgstr "Ekstra Mongo koblingsstreng alternativer"
+
+#: src/metabase/driver/oracle.clj
+msgid "Oracle system ID (SID)"
+msgstr "Oracle system ID (SID)"
+
+#: src/metabase/driver/oracle.clj
+msgid "Usually something like ORCL or XE."
+msgstr "Bruker vanligvis være lignende ORCL eller XE"
+
+#: src/metabase/driver/oracle.clj
+msgid "Optional if using service name"
+msgstr "Alternativt hvis bruk av servicenavn"
+
+#: src/metabase/driver/oracle.clj
+msgid "Oracle service name"
+msgstr "Oracle service navn"
+
+#: src/metabase/driver/oracle.clj
+msgid "Optional TNS alias"
+msgstr "Valgfritt TNS alias"
+
+#: src/metabase/driver/presto.clj
+msgid "hive"
+msgstr "kube"
+
+#: src/metabase/driver/redshift.clj
+msgid "my-cluster-name.abcd1234.us-east-1.redshift.amazonaws.com"
+msgstr "mitt-kluster-navn.abcd1234.no-1.redshift.amazonaws.com"
+
+#: src/metabase/driver/redshift.clj
+msgid "toucan_sightings"
+msgstr "toucan_sightings"
+
+#: src/metabase/driver/sparksql.clj
+msgid "default"
+msgstr "standard"
+
+#: src/metabase/driver/sqlite.clj
+msgid "Filename"
+msgstr "Filnavn"
+
+#: src/metabase/driver/sqlite.clj
+msgid "/home/camsaul/toucan_sightings.sqlite 😋"
+msgstr "/home/camsaul/toucan_sightings.sqlite 😋"
+
+#: src/metabase/driver/sqlserver.clj
+msgid "BirdsOfTheWorld"
+msgstr "FugleneIVerden"
+
+#: src/metabase/driver/sqlserver.clj
+msgid "Database instance name"
+msgstr "Database instanse navn"
+
+#: src/metabase/driver/sqlserver.clj
+msgid "N/A"
+msgstr "I/T"
+
+#: src/metabase/driver/sqlserver.clj
+msgid "Windows domain"
+msgstr "Windows domene"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:407
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:413
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:422
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:428
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:437
+msgid "Labels"
+msgstr "Etiketter"
+
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:329
+msgid "Add members"
+msgstr "Legg til medlemmer"
+
+#: frontend/src/metabase/entities/collections.js:108
+msgid "Collection it's saved in"
+msgstr "Kolleksjon den er lagret i"
+
+#: frontend/src/metabase/lib/groups.js:4
+msgid "All Users"
+msgstr "Alle brukere"
+
+#: frontend/src/metabase/lib/groups.js:5
+msgid "Administrators"
+msgstr "Administratorer"
+
+#: frontend/src/metabase/lib/groups.js:6
+msgid "MetaBot"
+msgstr "MetaBot"
+
+#: frontend/src/metabase/public/components/widgets/EmbedModalContent.jsx:290
+msgid "Sharing"
+msgstr "Deling"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:156
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:169
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:177
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:200
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:206
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:217
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:233
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:81
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:71
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:76
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:82
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:41
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:47
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:72
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:85
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:98
+msgid "Display"
+msgstr "Visning"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:283
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:316
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:329
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:344
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:356
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:362
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:370
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:400
+msgid "Axes"
+msgstr "Akser"
+
+#: frontend/src/metabase/visualizations/visualizations/Table.jsx:93
+msgid "Formatting"
+msgstr "Formatering"
+
+#: frontend/src/metabase/containers/Overworld.jsx:105
+msgid "Try these x-rays based on your data."
+msgstr "Prøv disse X-ray spørsmålene basert på din data."
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:35
+msgid "There was a problem displaying this chart."
+msgstr "Det er ett problem med visning av denne grafen."
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:36
+msgid "Sorry, you don't have permission to see this card."
+msgstr "Beklager, du har ikke tilgang til å se dette kortet."
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:51
+msgid "Just a heads up:"
+msgstr "Bare en liten ting:"
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:59
+msgid "{0} without the Sample Dataset, the Query Builder tutorial won't work. You can always restore the Sample Dataset, but any questions you've saved using this data will be lost."
+msgstr "{0} uten eksempel-datasettet, så vil ikke spørrings-veilederen virke. Du kan alltids gjenopprette eksempel-datasettet, men spørsmål du har lagret med disse dataene vil gå tapt."
+
+#: frontend/src/metabase/qb/components/drill/AutomaticDashboardDrill.jsx:27
+msgid "X-ray"
+msgstr "X-ray"
+
+#: frontend/src/metabase/qb/components/drill/CompareToRestDrill.js:27
+msgid "Compare to the rest"
+msgstr "Sammenlignet med resten"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:247
+msgid "Use the Java Virtual Machine (JVM) timezone"
+msgstr "Bruk JVM-tidssone"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:249
+msgid "We suggest you leave this off unless you're doing manual timezone casting in\n"
+"many or most of your queries with this data."
+msgstr "Vi anbefaler å la denne være slått av, med mindre du endrer tidssone manuelt i mange eller alle dine spørringer mote disse dataene."
+
+#: frontend/src/metabase/containers/Overworld.jsx:313
+msgid "Your team's most important dashboards go here"
+msgstr "Ditt lags viktigste infoskjermer finnes her"
+
+#: frontend/src/metabase/containers/Overworld.jsx:314
+msgid "Pin dashboards in {0} to have them appear in this space for everyone"
+msgstr "Fest paneler i {0} for at de skal vises på denne plassen for alle"
+
+#: src/metabase/db.clj
+msgid "Unable to release the Liquibase lock after a migration failure"
+msgstr "Ikke mulig å åpne Liquibase-låsen etter en migreringsfeil"
+
+#: src/metabase/driver/bigquery.clj
+msgid "Use JVM Time Zone"
+msgstr "Bruk JVM-tidssone"
+
diff --git a/locales/pt.po b/locales/pt.po
new file mode 100644
index 0000000000000000000000000000000000000000..fdb1b73f316651737380a227c1d946eb68be0b47
--- /dev/null
+++ b/locales/pt.po
@@ -0,0 +1,9308 @@
+msgid ""
+msgstr ""
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: POEditor.com\n"
+"Project-Id-Version: Metabase\n"
+"Language: pt-br\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: frontend/src/metabase/admin/databases/components/CreatedDatabaseModal.jsx:19
+msgid "Your database has been added!"
+msgstr "Seu banco de dados foi adicionado!"
+
+#: frontend/src/metabase/admin/databases/components/CreatedDatabaseModal.jsx:22
+msgid "We took a look at your data, and we have some automated explorations that we can show you!"
+msgstr "Analisamos seus dados e temos algumas explorações automatizadas  que podemos mostrar para você!"
+
+#: frontend/src/metabase/admin/databases/components/CreatedDatabaseModal.jsx:27
+msgid "I'm good thanks"
+msgstr "Estou bem, obrigado"
+
+#: frontend/src/metabase/admin/databases/components/CreatedDatabaseModal.jsx:32
+msgid "Explore this data"
+msgstr "Explore esses dados"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseEditForms.jsx:42
+msgid "Select a database type"
+msgstr "Selecione um tipo de banco de dados"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseEditForms.jsx:75
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:170
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:435
+#: frontend/src/metabase/admin/permissions/containers/CollectionPermissionsModal.jsx:71
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:182
+#: frontend/src/metabase/components/ActionButton.jsx:51
+#: frontend/src/metabase/components/ButtonWithStatus.jsx:7
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:180
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:197
+#: frontend/src/metabase/reference/components/EditHeader.jsx:54
+#: frontend/src/metabase/reference/components/EditHeader.jsx:69
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:171
+#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:164
+msgid "Save"
+msgstr "Salvar"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:122
+msgid "To do some of its magic, Metabase needs to scan your database. We will also rescan it periodically to keep the metadata up-to-date. You can control when the periodic rescans happen below."
+msgstr "Para fazer um pouco de sua mágica, o Metabase precisa varrer o banco de dados. Faremos isso periodicamente para manter os metadados atualizados. Você pode controlar quando as revisões periódicas são feitas a seguir."
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:127
+msgid "Database syncing"
+msgstr "Sincronizando Banco de Dados"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:128
+msgid "This is a lightweight process that checks for\n"
+"updates to this database’s schema. In most cases, you should be fine leaving this\n"
+"set to sync hourly."
+msgstr "Este é um processo rápido que procura \n"
+"atualizações para o esquema deste banco de dados. Na maior parte dos casos, tudo bem você deixar isso \n"
+"configurado para sincronizar a cada hora."
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:147
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:184
+msgid "Scan"
+msgstr "Explorar"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:152
+msgid "Scanning for Filter Values"
+msgstr "Procurando por valores de filtragem"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:153
+msgid "Metabase can scan the values present in each\n"
+"field in this database to enable checkbox filters in dashboards and questions. This\n"
+"can be a somewhat resource-intensive process, particularly if you have a very large\n"
+"database."
+msgstr "Metabase pode digitalizar os valores presentes em cada \n"
+"campo neste banco de dados para ativar filtros checkbox em painéis e perguntas. Isso \n"
+"Pode ser um processo que consome muitos recursos, especialmente se você tiver um \n"
+"grande banco de dados."
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:159
+msgid "When should Metabase automatically scan and cache field values?"
+msgstr "Quando o Metabase deve verificar e armazenar automaticamente os valores dos campos?"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:164
+msgid "Regularly, on a schedule"
+msgstr "Regularmente, seguindo um cronograma"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:195
+msgid "Only when adding a new filter widget"
+msgstr "Somente quando um novo elemento de filtro é adicionado"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:199
+msgid "When a user adds a new filter to a dashboard or a SQL question, Metabase will\n"
+"scan the field(s) mapped to that filter in order to show the list of selectable values."
+msgstr "Quando um usuário adicionar um novo filtro a um dashboard ou a uma questão SQL, Metabase irá examinar o(s) campo(s) mapeado(s) para tal filtro, para mostrar a lista de valores selecionáveis "
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:210
+msgid "Never, I'll do this manually if I need to"
+msgstr "Nunca, eu farei isto manualmente se precisar"
+
+#: frontend/src/metabase/admin/databases/components/DatabaseSchedulingForm.jsx:222
+#: frontend/src/metabase/admin/settings/components/SettingsBatchForm.jsx:27
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:222
+#: frontend/src/metabase/components/ActionButton.jsx:52
+#: frontend/src/metabase/components/ButtonWithStatus.jsx:8
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:429
+msgid "Saving..."
+msgstr "Salvando..."
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:38
+#: frontend/src/metabase/components/form/FormMessage.jsx:4
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:144
+msgid "Server error encountered"
+msgstr "Um erro foi encontrado no servidor"
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:54
+msgid "Delete this database?"
+msgstr "Excluir este banco de dados?"
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:62
+msgid "All saved questions, metrics, and segments that rely on this database will be lost."
+msgstr "Todas as questões, métricas e segmentos que dependem desse banco de dados será perdido"
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:63
+msgid "This cannot be undone."
+msgstr "Esta ação não pode ser desfeita."
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:66
+msgid "If you're sure, please type"
+msgstr "Se tiver a certeza, por favor digite:"
+
+#. O comando para remover é DELETE, se traduzir este comando, o Metabase não irá habilitar a remoção de itens.
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:66
+msgid "DELETE"
+msgstr "DELETE"
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:67
+msgid "in this box:"
+msgstr "nesta caixa:"
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:78
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:50
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:87
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:93
+#: frontend/src/metabase/admin/people/components/AddRow.jsx:27
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:250
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:302
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:322
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:343
+#: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:49
+#: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:52
+#: frontend/src/metabase/admin/permissions/containers/CollectionPermissionsModal.jsx:58
+#: frontend/src/metabase/admin/permissions/selectors.js:156
+#: frontend/src/metabase/admin/permissions/selectors.js:166
+#: frontend/src/metabase/admin/permissions/selectors.js:181
+#: frontend/src/metabase/admin/permissions/selectors.js:220
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:355
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:181
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:247
+#: frontend/src/metabase/components/ConfirmContent.jsx:18
+#: frontend/src/metabase/components/DeleteModalWithConfirm.jsx:72
+#: frontend/src/metabase/components/HeaderModal.jsx:49
+#: frontend/src/metabase/components/form/StandardForm.jsx:55
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:196
+#: frontend/src/metabase/dashboard/components/AddSeriesModal.jsx:289
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:162
+#: frontend/src/metabase/dashboard/components/RemoveFromDashboardModal.jsx:42
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:189
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:192
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:352
+#: frontend/src/metabase/query_builder/components/RunButton.jsx:24
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:83
+#: frontend/src/metabase/query_builder/containers/ArchiveQuestionModal.jsx:48
+#: frontend/src/metabase/reference/components/EditHeader.jsx:34
+#: frontend/src/metabase/reference/components/RevisionMessageModal.jsx:52
+#: frontend/src/metabase/visualizations/components/ChartSettings.jsx:209
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:397
+msgid "Cancel"
+msgstr "Cancelar"
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:84
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:123
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:397
+msgid "Delete"
+msgstr "Excluir"
+
+#. Existem alguns campos traduzidos como Bancos de dados e outros como base de dados. Para seguir o padrão, ajustei para Banco de dados
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:128
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:76
+#: frontend/src/metabase/admin/permissions/selectors.js:316
+#: frontend/src/metabase/admin/permissions/selectors.js:323
+#: frontend/src/metabase/admin/permissions/selectors.js:419
+#: frontend/src/metabase/admin/routes.jsx:39
+#: frontend/src/metabase/nav/containers/Navbar.jsx:215
+#: frontend/src/metabase/reference/databases/DatabaseSidebar.jsx:18
+#: frontend/src/metabase/reference/databases/TableSidebar.jsx:18
+msgid "Databases"
+msgstr "Bancos de Dados"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:129
+msgid "Add Database"
+msgstr "Adicionar banco de dados"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:60
+msgid "Connection"
+msgstr "Conexão"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:61
+msgid "Scheduling"
+msgstr "Agendamento"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:170
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:78
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:84
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:253
+#: frontend/src/metabase/admin/settings/components/SettingsBatchForm.jsx:26
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:221
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:182
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:354
+#: frontend/src/metabase/reference/components/RevisionMessageModal.jsx:47
+msgid "Save changes"
+msgstr "Salvar alterações"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:185
+#: frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx:38
+#: frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx:38
+msgid "Actions"
+msgstr "Ações"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:193
+msgid "Sync database schema now"
+msgstr "Sincronize o esquema do banco de dados agora"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:194
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:206
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:788
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:796
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:112
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:120
+msgid "Starting…"
+msgstr "Iniciando..."
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:195
+msgid "Failed to sync"
+msgstr "Não foi possível sincronizar"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:196
+msgid "Sync triggered!"
+msgstr "Sincronização iniciada!"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:205
+msgid "Re-scan field values now"
+msgstr "Verificar novamente os valores do campo agora"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:207
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:789
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:113
+msgid "Failed to start scan"
+msgstr "A verificação não pôde ser iniciada"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:208
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:790
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:114
+msgid "Scan triggered!"
+msgstr "Verificação começou!"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:215
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:399
+msgid "Danger Zone"
+msgstr "Zona Perigosa"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:221
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:224
+msgid "Discard saved field values"
+msgstr "Descartar valores de campo salvos"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx:239
+msgid "Remove this database"
+msgstr "Remover este banco de dados"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:75
+msgid "Add database"
+msgstr "Adicionar banco de dados"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:87
+#: frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx:36
+#: frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx:36
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:468
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:183
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:91
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:402
+#: frontend/src/metabase/containers/EntitySearch.jsx:26
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:218
+#: frontend/src/metabase/entities/collections.js:86
+#: frontend/src/metabase/entities/dashboards.js:96
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:461
+#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:62
+msgid "Name"
+msgstr "Nome"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:88
+msgid "Engine"
+msgstr "Engine"
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:117
+msgid "Deleting..."
+msgstr "Removendo..."
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:147
+msgid "Loading ..."
+msgstr "Carregando..."
+
+#: frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx:163
+msgid "Bring the sample dataset back"
+msgstr "Recuperar o conjunto de dados de exemplo"
+
+#: frontend/src/metabase/admin/databases/database.js:175
+msgid "Couldn't connect to the database. Please check the connection details."
+msgstr "Não foi possível conectar-se ao banco de dados. Por favor, verifique o detalhes da conexão."
+
+#: frontend/src/metabase/admin/databases/database.js:383
+msgid "Successfully created!"
+msgstr "Criado com sucesso!"
+
+#: frontend/src/metabase/admin/databases/database.js:393
+msgid "Successfully saved!"
+msgstr "Salvo com sucesso!"
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectActionSelect.jsx:44
+#: frontend/src/metabase/dashboard/components/DashCard.jsx:276
+#: frontend/src/metabase/parameters/components/ParameterWidget.jsx:173
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:209
+#: frontend/src/metabase/reference/components/EditButton.jsx:18
+msgid "Edit"
+msgstr "Editar"
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectActionSelect.jsx:59
+msgid "Revision History"
+msgstr "Histórico de Revisão"
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:33
+msgid "Retire this {0}?"
+msgstr "Remover este {0}?"
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:38
+msgid "Saved questions and other things that depend on this {0} will continue to work, but this {1} will no longer be selectable from the query builder."
+msgstr "Perguntas gravadas e outras coisas que dependem disso {0} continuarão em execução, mas este {1} não pode mais ser selecionado no editor de consultas"
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:39
+msgid "If you're sure you want to retire this {0}, please write a quick explanation of why it's being retired:"
+msgstr "Se tiver certeza de que deseja remover este {0}, escreva uma explicação do porquê o que está sendo removido:"
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:43
+msgid "This will show up in the activity feed and in an email that will be sent to anyone on your team who created something that uses this {0}."
+msgstr "Isso será mostrado no resumo da atividade e em um email que ele será enviado para qualquer pessoa da sua equipe que tenha criado algo que eles usem isso {0}."
+
+#. I guess this translation is not accurate. In what context does it occur?
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:58
+msgid "Retire"
+msgstr "Retirar"
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:59
+msgid "Retiring…"
+msgstr "Retirando ..."
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:60
+msgid "Failed"
+msgstr "Falhou"
+
+#: frontend/src/metabase/admin/datamodel/components/ObjectRetireModal.jsx:61
+msgid "Success"
+msgstr "Sucesso"
+
+#: frontend/src/metabase/admin/datamodel/components/PartialQueryBuilder.jsx:118
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:110
+msgid "Preview"
+msgstr "Pré-visualizar"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnItem.jsx:97
+msgid "No column description yet"
+msgstr "Não há descrição da coluna"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnItem.jsx:133
+msgid "Select a field visibility"
+msgstr "Selecione uma visibilidade de campo"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnItem.jsx:189
+msgid "No special type"
+msgstr "Sem tipo especial"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnItem.jsx:190
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:34
+#: frontend/src/metabase/reference/components/Field.jsx:57
+#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:42
+msgid "Other"
+msgstr "Outro"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnItem.jsx:208
+msgid "Select a special type"
+msgstr "Selecione um tipo especial"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnItem.jsx:222
+msgid "Select a target"
+msgstr "Selecione uma referência"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnsList.jsx:17
+msgid "Columns"
+msgstr "Colunas"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnsList.jsx:22
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataSchema.jsx:44
+msgid "Column"
+msgstr "Coluna"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnsList.jsx:24
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:121
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:203
+msgid "Visibility"
+msgstr "Visibilidade"
+
+#: frontend/src/metabase/admin/datamodel/components/database/ColumnsList.jsx:25
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:214
+msgid "Type"
+msgstr "Tipo"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataHeader.jsx:87
+msgid "Current database:"
+msgstr "Banco de dados atual:"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataHeader.jsx:92
+msgid "Show original schema"
+msgstr "Mostrar esquema original"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataSchema.jsx:45
+msgid "Data Type"
+msgstr "Tipo de Dado"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataSchema.jsx:46
+msgid "Additional Info"
+msgstr "Informação adicional"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataSchemaList.jsx:44
+msgid "Find a schema"
+msgstr "Encontre um esquema"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataSchemaList.jsx:51
+msgid "{0} schema"
+msgid_plural "{0} schemas"
+msgstr[0] "{0} esquema"
+msgstr[1] "{0} esquemas"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:82
+msgid "Why Hide?"
+msgstr "Por que esconder isso?"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:83
+msgid "Technical Data"
+msgstr "Dados técnicos"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:84
+msgid "Irrelevant/Cruft"
+msgstr "Irrelevante"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:90
+msgid "Queryable"
+msgstr "Consultável"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:91
+msgid "Hidden"
+msgstr "Oculto"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:117
+msgid "No table description yet"
+msgstr "Não há descrição da tabela"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx:124
+msgid "Metadata Strength"
+msgstr "Força dos metadados"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTableList.jsx:87
+msgid "{0} Queryable Table"
+msgid_plural "{0} Queryable Tables"
+msgstr[0] "{0} Tabela Consultável"
+msgstr[1] "{0} Tabelas Consultáveis"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTableList.jsx:96
+msgid "{0} Hidden Table"
+msgid_plural "{0} Hidden Tables"
+msgstr[0] "{0} Tabela Escondida"
+msgstr[1] "{0} Tabelas Escondidas"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTableList.jsx:113
+msgid "Find a table"
+msgstr "Encontre uma tabela"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetadataTableList.jsx:126
+msgid "Schemas"
+msgstr "Esquemas"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx:24
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:137
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:33
+#: frontend/src/metabase/reference/guide/BaseSidebar.jsx:27
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:189
+#: frontend/src/metabase/reference/metrics/MetricList.jsx:56
+#: frontend/src/metabase/reference/metrics/MetricSidebar.jsx:18
+#: frontend/src/metabase/routes.jsx:231
+msgid "Metrics"
+msgstr "Métricas"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx:30
+msgid "Add a Metric"
+msgstr "Adicione uma métrica"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx:37
+#: frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx:37
+#: frontend/src/metabase/query_builder/components/QueryDefinitionTooltip.jsx:30
+msgid "Definition"
+msgstr "Definição"
+
+#: frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx:54
+msgid "Create metrics to add them to the View dropdown in the query builder"
+msgstr "Crie métricas para adicionar ao menu Visualizar no editor de consultas"
+
+#: frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx:24
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:922
+#: frontend/src/metabase/reference/guide/BaseSidebar.jsx:33
+#: frontend/src/metabase/reference/segments/SegmentFieldSidebar.jsx:19
+#: frontend/src/metabase/reference/segments/SegmentList.jsx:56
+#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:18
+msgid "Segments"
+msgstr "Segmentos"
+
+#: frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx:30
+msgid "Add a Segment"
+msgstr "Adicionar um segmento"
+
+#: frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx:54
+msgid "Create segments to add them to the Filter dropdown in the query builder"
+msgstr "Criar segmentos para adicionar ao menu Filtro no editor de consultas"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/Revision.jsx:24
+msgid "created"
+msgstr "criado"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/Revision.jsx:27
+msgid "reverted to a previous version"
+msgstr "revertido para uma versão anterior"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/Revision.jsx:33
+msgid "edited the title"
+msgstr "editou o título"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/Revision.jsx:35
+msgid "edited the description"
+msgstr "editou a descrição"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/Revision.jsx:37
+msgid "edited the "
+msgstr "editado em "
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/Revision.jsx:40
+msgid "made some changes"
+msgstr "fez algumas alterações"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/Revision.jsx:46
+#: frontend/src/metabase/home/components/Activity.jsx:80
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:343
+msgid "You"
+msgstr "Você"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/RevisionHistory.jsx:37
+msgid "Datamodel"
+msgstr "Modelo de dados"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/RevisionHistory.jsx:43
+msgid " History"
+msgstr " História"
+
+#: frontend/src/metabase/admin/datamodel/components/revisions/RevisionHistory.jsx:48
+msgid "Revision History for"
+msgstr "Histórico de revisão para"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:185
+msgid "{0} – Field Settings"
+msgstr "{0} - configuração"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:204
+msgid "Where this field will appear throughout Metabase"
+msgstr "Onde este campo aparecerá no Metabase"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:226
+msgid "Filtering on this field"
+msgstr "Filtrando neste campo"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:227
+msgid "When this field is used in a filter, what should people use to enter the value they want to filter on?"
+msgstr "Quando esse campo é usado em um filtro, quais valores ele deve aceitar?"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:334
+msgid "No description for this field yet"
+msgstr "Ainda não há descrição para este campo"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:413
+msgid "Original value"
+msgstr "Valor original"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:414
+msgid "Mapped value"
+msgstr "Valor atribuído"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:457
+msgid "Enter value"
+msgstr "Insira um valor"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:480
+msgid "Use original value"
+msgstr "Usar o valor original"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:481
+msgid "Use foreign key"
+msgstr "Usar chave estrangeira"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:482
+msgid "Custom mapping"
+msgstr "Mapeamento personalizado"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:510
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:610
+msgid "Unrecognized mapping type"
+msgstr "Tipo de mapeamento não reconhecido"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:544
+msgid "Current field isn't a foreign key or FK target table metadata is missing"
+msgstr "O campo atual não é uma chave estrangeira ou os metadados principais estão ausentes idioma estrangeiro na tabela de destino"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:641
+msgid "The selected field isn't a foreign key"
+msgstr "O campo selecionado não é uma chave estrangeira"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:703
+msgid "Display values"
+msgstr "Valores exibidos"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:704
+msgid "Choose to show the original value from the database, or have this field display associated or custom information."
+msgstr "Escolha se você deseja mostrar o valor original do banco de dados ou mostrar informações associadas ou personalizadas."
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:730
+msgid "Choose a field"
+msgstr "Escolha um campo"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:751
+msgid "Please select a column to use for display."
+msgstr "Selecione uma coluna para mostrar"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:771
+msgid "Tip:"
+msgstr "Conselho:"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:772
+msgid "You might want to update the field name to make sure it still makes sense based on your remapping choices."
+msgstr "Você pode querer atualizar o nome do campo para ter certeza de que ainda tem significado dependendo das opções de alocação."
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:781
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:105
+msgid "Cached field values"
+msgstr "Valores de campo no cache"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:782
+msgid "Metabase can scan the values for this field to enable checkbox filters in dashboards and questions."
+msgstr "o Metabase pode digitalizar os valores nesse campo para habilitar Filtros da caixa de seleção em painéis e perguntas."
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:787
+msgid "Re-scan this field"
+msgstr "Reexaminar este campo"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:795
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:119
+msgid "Discard cached field values"
+msgstr "Descartar valores de campo em cache"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:797
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:121
+msgid "Failed to discard values"
+msgstr "Erro ao descartar valores"
+
+#: frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx:798
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:122
+msgid "Discard triggered!"
+msgstr "Limpeza iniciada!"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetadataEditorApp.jsx:105
+msgid "Select any table to see its schema and add or edit metadata."
+msgstr "Selecione qualquer tabela para ver seu esquema e adicionar ou editar metadados."
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:37
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:34
+#: frontend/src/metabase/entities/collections.js:89
+msgid "Name is required"
+msgstr "Nome é obrigatório"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:40
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:37
+msgid "Description is required"
+msgstr "Descrição é obrigatória"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:44
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:41
+msgid "Revision message is required"
+msgstr "Mensagem da revisão é obrigatória"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:50
+msgid "Aggregation is required"
+msgstr "A agregação é obrigatória"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:110
+msgid "Edit Your Metric"
+msgstr "Edite sua métrica"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:111
+msgid "Create Your Metric"
+msgstr "Crie sua métrica"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:115
+msgid "Make changes to your metric and leave an explanatory note."
+msgstr "Faça alterações na sua métrica e deixe uma nota explicativa."
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:116
+msgid "You can create saved metrics to add a named metric option to this table. Saved metrics include the aggregation type, the aggregated field, and optionally any filter you add. As an example, you might use this to create something like the official way of calculating \"Average Price\" for an Orders table."
+msgstr "Você pode criar métricas e salvá-las como um campo calculado nessa tabela. As métricas salvas incluem o tipo de agregação, o campo agregado e opcionalmente, qualquer filtro que você adicionar. Por exemplo, você pode usar esse para definir a forma oficial de calcular o \"Preço Médio\" para una Tabela Pedidos."
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:149
+msgid "Result: "
+msgstr "Resultado: "
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:157
+msgid "Name Your Metric"
+msgstr "Nomeie sua métrica"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:158
+msgid "Give your metric a name to help others find it."
+msgstr "Dê um nome à sua métrica para ajudar outras pessoas a encontrarem."
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:162
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:166
+msgid "Something descriptive but not too long"
+msgstr "Algo descritivo, mas não muito longo"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:166
+msgid "Describe Your Metric"
+msgstr "Descreva sua métrica"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:167
+msgid "Give your metric a description to help others understand what it's about."
+msgstr "Dê uma descrição à sua métrica para ajudar os outros a entender o que é tente"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:171
+msgid "This is a good place to be more specific about less obvious metric rules"
+msgstr "Este é um bom lugar para ser mais específico sobre as regras de métrica menos óbvio"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:175
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:179
+msgid "Reason For Changes"
+msgstr "Razão para alterações"
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:177
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:181
+msgid "Leave a note to explain what changes you made and why they were required."
+msgstr "Deixe uma nota para explicar que alterações você fez e por que elas foram necessário."
+
+#: frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx:181
+msgid "This will show up in the revision history for this metric to help everyone remember why things changed"
+msgstr "Isso aparecerá no histórico de revisão dessa métrica para ajudar todos para lembrar por que a mudança foi feita"
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:49
+msgid "At least one filter is required"
+msgstr "Pelo menos um filtro é necessário"
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:116
+msgid "Edit Your Segment"
+msgstr "Edite seu segmento"
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:117
+msgid "Create Your Segment"
+msgstr "Crie seu segmento"
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:121
+msgid "Make changes to your segment and leave an explanatory note."
+msgstr "Faça alterações no seu segmento e deixe uma nota explicativa."
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:122
+msgid "Select and add filters to create your new segment for the {0} table"
+msgstr "Selecione e adicione filtros para criar um novo segmento para a tabela {0}"
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:161
+msgid "Name Your Segment"
+msgstr "Nomeie seu segmento"
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:162
+msgid "Give your segment a name to help others find it."
+msgstr "Dê um nome ao seu segmento para ajudar outras pessoas a encontrá-lo."
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:170
+msgid "Describe Your Segment"
+msgstr "Descreva seu segmento"
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:171
+msgid "Give your segment a description to help others understand what it's about."
+msgstr "Dê ao seu segmento uma descrição para ajudar os outros a entender o que é tente"
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:175
+msgid "This is a good place to be more specific about less obvious segment rules"
+msgstr "Este é um bom lugar para ser mais específico sobre as regras de segmentação menos óbvia"
+
+#: frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx:185
+msgid "This will show up in the revision history for this segment to help everyone remember why things changed"
+msgstr "Isso aparecerá no histórico de revisão deste segmento para ajudar todos para lembrar por que a mudança foi feita"
+
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:91
+#: frontend/src/metabase/admin/routes.jsx:79
+#: frontend/src/metabase/admin/settings/containers/SettingsEditorApp.jsx:266
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:96
+#: frontend/src/metabase/nav/containers/Navbar.jsx:200
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorSidebar.jsx:99
+msgid "Settings"
+msgstr "Configuração"
+
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:106
+msgid "Metabase can scan the values in this table to enable checkbox filters in dashboards and questions."
+msgstr "o Metabase pode ler os valores nesta tabela para ativar filtros caixas de seleção em painéis e perguntas."
+
+#: frontend/src/metabase/admin/datamodel/containers/TableSettingsApp.jsx:111
+msgid "Re-scan this table"
+msgstr "Releia esta tabela"
+
+#: frontend/src/metabase/admin/people/components/AddRow.jsx:34
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:194
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:253
+#: frontend/src/metabase/dashboard/components/DashCard.jsx:276
+msgid "Add"
+msgstr "Adicionar"
+
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:80
+#: frontend/src/metabase/setup/components/UserStep.jsx:103
+#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:67
+msgid "Not a valid formatted email address"
+msgstr "Formato do e-mail incorreto"
+
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:135
+#: frontend/src/metabase/setup/components/UserStep.jsx:186
+#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:100
+msgid "First name"
+msgstr "Nome"
+
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:156
+#: frontend/src/metabase/setup/components/UserStep.jsx:203
+#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:117
+msgid "Last name"
+msgstr "Sobrenome"
+
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:178
+#: frontend/src/metabase/auth/containers/ForgotPasswordApp.jsx:77
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:158
+#: frontend/src/metabase/components/NewsletterForm.jsx:94
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:470
+#: frontend/src/metabase/setup/components/UserStep.jsx:222
+#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:138
+msgid "Email address"
+msgstr "Endereço de e-mail"
+
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:202
+msgid "Permission Groups"
+msgstr "Grupos de Permissões"
+
+#: frontend/src/metabase/admin/people/components/EditUserForm.jsx:238
+msgid "Make this user an admin"
+msgstr "Converta este usuário para administrador"
+
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:32
+msgid "All users belong to the {0} group and can't be removed from it. Setting permissions for this group is a great way to\n"
+"make sure you know what new Metabase users will be able to see."
+msgstr "Todos os usuários pertencem ao grupo \"{0}\" e não podem ser excluídos dele. Estabelecer permissões para esse grupo é uma ótima maneira de garantir que você sabe o que os novos usuários do Metabase podem ver"
+
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:41
+msgid "This is a special group whose members can see everything in the Metabase instance, and who can access and make changes to the\n"
+"settings in the Admin Panel, including changing permissions! So, add people to this group with care."
+msgstr "Este é um grupo especial cujos membros podem ver tudo na instância do Metabase, e quem pode acessar e fazer alterações na configuração em Painel de Administração, incluindo a alteração de permissões! Então, adicione pessoas para neste grupo com cuidado."
+
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:45
+msgid "To make sure you don't get locked out of Metabase, there always has to be at least one user in this group."
+msgstr "Para garantir que você não fique sem acesso ao Metabase, você deve sempre ter ao menos um usuário neste grupo."
+
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:177
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:219
+msgid "Members"
+msgstr "Membros"
+
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:177
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:470
+#: frontend/src/metabase/admin/settings/selectors.js:107
+#: frontend/src/metabase/lib/core.js:50
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:293
+msgid "Email"
+msgstr "E-mail"
+
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:203
+msgid "A group is only as good as its members."
+msgstr "Um grupo vale apenas o que seus membros valem."
+
+#: frontend/src/metabase/admin/people/components/GroupSummary.jsx:15
+#: frontend/src/metabase/admin/routes.jsx:34
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:50
+msgid "Admin"
+msgstr "Administrador"
+
+#: frontend/src/metabase/admin/people/components/GroupSummary.jsx:16
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:237
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:290
+msgid "and"
+msgstr "e"
+
+#: frontend/src/metabase/admin/people/components/GroupSummary.jsx:19
+#: frontend/src/metabase/admin/people/components/GroupSummary.jsx:31
+msgid "{0} other group"
+msgid_plural "{0} other groups"
+msgstr[0] "{0} outro grupo"
+msgstr[1] "{0} outros grupos"
+
+#: frontend/src/metabase/admin/people/components/GroupSummary.jsx:37
+msgid "Default"
+msgstr "Padrão"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:40
+msgid "Something like \"Marketing\""
+msgstr "Algo como \"Marketing\""
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:59
+msgid "Remove this group?"
+msgstr "Excluir este grupo?"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:61
+msgid "Are you sure? All members of this group will lose any permissions settings they have based on this group.\n"
+"This can't be undone."
+msgstr "Tem certeza? Todos os membros deste grupo perderão as configurações de permissões baseadas neste grupo.\n"
+"Isso não pode ser desfeito."
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:72
+#: frontend/src/metabase/components/ConfirmContent.jsx:17
+msgid "Yes"
+msgstr "Sim"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:75
+msgid "No"
+msgstr "Não"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:93
+msgid "Edit Name"
+msgstr "Editar nome"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:96
+msgid "Remove Group"
+msgstr "Excluir grupo"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:139
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:225
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:263
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:367
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:385
+#: frontend/src/metabase/components/HeaderModal.jsx:43
+#: frontend/src/metabase/dashboard/components/AddSeriesModal.jsx:282
+#: frontend/src/metabase/parameters/components/widgets/CategoryWidget.jsx:106
+#: frontend/src/metabase/query_builder/components/AggregationPopover.jsx:298
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:194
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:96
+#: frontend/src/metabase/visualizations/components/ChartSettings.jsx:215
+msgid "Done"
+msgstr "Feito"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:219
+msgid "Group name"
+msgstr "Nome do Grupo"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:399
+#: frontend/src/metabase/admin/people/containers/AdminPeopleApp.jsx:25
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:477
+#: frontend/src/metabase/admin/routes.jsx:72
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:154
+msgid "Groups"
+msgstr "Grupos"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:400
+msgid "Create a group"
+msgstr "Criar um grupo"
+
+#: frontend/src/metabase/admin/people/components/GroupsListing.jsx:406
+msgid "You can use groups to control your users' access to your data. Put users in groups and then go to the Permissions section to control each group's access. The Administrators and All Users groups are special default groups that can't be removed."
+msgstr "Você pode usar grupos para controlar o acesso de seus usuários aos dados. Coloque os usuários em grupos e vá para a seção Permissões para controlar o acesso de cada grupo. Os grupos \"Administradores\" e \"Todos os Usuários\" eles são grupos padrão especiais que não podem ser excluídos."
+
+#: frontend/src/metabase/admin/people/components/UserActionsSelect.jsx:79
+msgid "Edit Details"
+msgstr "Editar detalhes"
+
+#: frontend/src/metabase/admin/people/components/UserActionsSelect.jsx:85
+msgid "Re-send Invite"
+msgstr "Reenviar convite"
+
+#: frontend/src/metabase/admin/people/components/UserActionsSelect.jsx:90
+msgid "Reset Password"
+msgstr "Redefinir senha"
+
+#: frontend/src/metabase/admin/people/components/UserActionsSelect.jsx:97
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:304
+msgid "Deactivate"
+msgstr "Desativar"
+
+#: frontend/src/metabase/admin/people/containers/AdminPeopleApp.jsx:24
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:435
+#: frontend/src/metabase/admin/routes.jsx:70
+#: frontend/src/metabase/nav/containers/Navbar.jsx:205
+msgid "People"
+msgstr "Pessoas"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:192
+msgid "Who do you want to add?"
+msgstr "Quem você quer adicionar?"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:207
+msgid "Edit {0}'s details"
+msgstr "Edite os detalhes de {0}"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:220
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:258
+msgid "{0} has been added"
+msgstr "Adicionado {0}"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:224
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:262
+msgid "Add another person"
+msgstr "Adicione outra pessoa"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:231
+msgid "We couldn’t send them an email invitation,\n"
+"so make sure to tell them to log in using {0}\n"
+"and this password we’ve generated for them:"
+msgstr "Não foi possível enviar um convite por e-mail, então certifique-se de dizer a eles para entrarem com {0} e essa senha que geramos para eles:"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:242
+msgid "If you want to be able to send email invites, just go to the {0} page."
+msgstr "Se você quiser enviar convites por e-mail, simplesmente vá a página {0}."
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:268
+msgid "We’ve sent an invite to {0} with instructions to set their password."
+msgstr "Enviamos um convite para {0} com instruções para configurar a senha dele"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:283
+msgid "We've re-sent {0}'s invite"
+msgstr "Enviamos novamente um convite para: {0}"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:285
+#: frontend/src/metabase/query_builder/components/SavedQuestionIntroModal.jsx:22
+#: frontend/src/metabase/tutorial/Tutorial.jsx:253
+msgid "Okay"
+msgstr "Okay"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:289
+msgid "Any previous email invites they have will no longer work."
+msgstr "Qualquer convite por e-mail anterior que eles tenham não irá mais funcionar"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:300
+msgid "Deactivate {0}?"
+msgstr "Desativar {0}?"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:309
+msgid "{0} won't be able to log in anymore."
+msgstr "{0} não poderá mais fazer login."
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:320
+msgid "Reactivate {0}'s account?"
+msgstr "Reativar a conta de {0}?"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:326
+msgid "Reactivate"
+msgstr "Reativar"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:330
+msgid "They'll be able to log in again, and they'll be placed back into the groups they were in before their account was deactivated."
+msgstr "Eles poderão logar novamente, e serão colocados de volta nos grupos que eles estavam antes de terem as contas desativadas."
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:341
+msgid "Reset {0}'s password?"
+msgstr "Redefinir a senha de {0}?"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:347
+#: frontend/src/metabase/components/form/StandardForm.jsx:71
+msgid "Reset"
+msgstr "Redefinir"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:351
+#: frontend/src/metabase/components/ConfirmContent.jsx:13
+#: frontend/src/metabase/dashboard/components/ArchiveDashboardModal.jsx:44
+msgid "Are you sure you want to do this?"
+msgstr "Tem certeza de que quer fazer isso?"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:362
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:384
+msgid "{0}'s password has been reset"
+msgstr "A senha de {0} foi redefinida"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:371
+msgid "Here’s a temporary password they can use to log in and then change their password."
+msgstr "Aqui está uma senha temporária que você pode usar para fazer login e depois mude sua senha."
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:388
+msgid "We've sent them an email with instructions for creating a new password."
+msgstr "Nós lhe enviamos um email com instruções para criar uma nova senha."
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:443
+msgid "Active"
+msgstr "Ativo"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:444
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:473
+msgid "Deactivated"
+msgstr "Desativado"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:459
+msgid "Add someone"
+msgstr "Adicione alguém"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:478
+msgid "Last Login"
+msgstr "Último acesso"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:501
+msgid "Signed up via Google"
+msgstr "Acesso via Google"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:506
+msgid "Signed up via LDAP"
+msgstr "Acesso via LDAP"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:518
+msgid "Reactivate this account"
+msgstr "Reativar esta conta"
+
+#: frontend/src/metabase/admin/people/containers/PeopleListingApp.jsx:545
+msgid "Never"
+msgstr "Nunca"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:27
+#: frontend/src/metabase/query_builder/components/dataref/MainPane.jsx:24
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} table"
+msgid_plural "{0} tables"
+msgstr[0] "{0} tabela"
+msgstr[1] "{0} tabelas"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:45
+msgid " will be "
+msgstr " será "
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:48
+msgid "given access to"
+msgstr "dado acesso a"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:53
+msgid " and "
+msgstr " e "
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:56
+msgid "denied access to"
+msgstr "negado acesso a"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:70
+msgid " will no longer be able to "
+msgstr " não poderá mais "
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:71
+msgid " will now be able to "
+msgstr " agora será capaz de "
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx:79
+msgid " native queries for "
+msgstr " consultas nativas para "
+
+#: frontend/src/metabase/admin/permissions/routes.jsx:12
+#: frontend/src/metabase/nav/containers/Navbar.jsx:220
+msgid "Permissions"
+msgstr "Permissões"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:32
+msgid "Save permissions?"
+msgstr "Salvar permissões?"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:38
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:161
+msgid "Save Changes"
+msgstr "Salvar alterações"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:44
+msgid "Discard changes?"
+msgstr "Descartar as alterações?"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:46
+msgid "No changes to permissions will be made."
+msgstr "Nenhuma alteração será feita nas permissões."
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx:65
+msgid "You've made changes to permissions."
+msgstr "Você fez alterações nas permissões."
+
+#: frontend/src/metabase/admin/permissions/containers/CollectionPermissionsModal.jsx:52
+msgid "Permissions for this collection"
+msgstr "Permissões para esta coleção"
+
+#: frontend/src/metabase/admin/permissions/containers/PermissionsApp.jsx:53
+msgid "You have unsaved changes"
+msgstr "Tem alterações não salvas"
+
+#: frontend/src/metabase/admin/permissions/containers/PermissionsApp.jsx:54
+msgid "Do you want to leave this page and discard your changes?"
+msgstr "Você quer sair desta página e descartar suas alterações?"
+
+#: frontend/src/metabase/admin/permissions/permissions.js:137
+msgid "Sorry, an error occurred."
+msgstr "Desculpe, ocorreu um erro."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:59
+msgid "Administrators always have the highest level of access to everything in Metabase."
+msgstr "Os administradores sempre têm o nível mais alto de acesso a tudo no Metabase"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:61
+msgid "Every Metabase user belongs to the All Users group. If you want to limit or restrict a group's access to something, make sure the All Users group has an equal or lower level of access."
+msgstr "Todos os usuários do Metabase pertencem ao grupo \"Todos os usuários\". Se você deseja limitar ou restringir o acesso de um grupo a algo, certifique-se de que o grupo \"Todos os usuários\" tem um nível de acesso igual ou menor."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:63
+msgid "MetaBot is Metabase's Slack bot. You can choose what it has access to here."
+msgstr "O Metabot é o bot Slack do Metabase. Você pode escolher o que ele tem acesso aqui"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:115
+msgid "The \"{0}\" group may have access to a different set of {1} than this group, which may give this group additional access to some {2}."
+msgstr "O grupo \"{0}\" pode ter acesso a um conjunto diferente de {1} que este grupo, o que pode dar a este grupo acesso adicional a alguns {2}."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:120
+msgid "The \"{0}\" group has a higher level of access than this, which will override this setting. You should limit or revoke the \"{1}\" group's access to this item."
+msgstr "O grupo \"{0}\" tem um nível de acesso maior que este, o que anulará esta configuração. Você deveria limitar ou revogar o acceso do grupo '{1}' a este elemento."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:150
+msgid "Limit"
+msgstr "Limite"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:150
+msgid "Revoke"
+msgstr "Revogar"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:152
+msgid "access even though \"{0}\" has greater access?"
+msgstr "acesso apesar que o grupo \"{0}\" tem maior acceso?"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:155
+#: frontend/src/metabase/admin/permissions/selectors.js:254
+msgid "Limit access"
+msgstr "Limite de acesso"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:155
+#: frontend/src/metabase/admin/permissions/selectors.js:219
+#: frontend/src/metabase/admin/permissions/selectors.js:262
+msgid "Revoke access"
+msgstr "Revogar acesso"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:164
+msgid "Change access to this database to limited?"
+msgstr "Alterar o acesso a este banco de dados para limitado?"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:165
+msgid "Change"
+msgstr "Alterar"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:178
+msgid "Allow Raw Query Writing?"
+msgstr "Permitir a gravação de consultas diretas"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:179
+msgid "This will also change this group's data access to Unrestricted for this database."
+msgstr "Isso também alterará o acesso a dados desse grupo para sem restrições neste banco de dados."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:180
+msgid "Allow"
+msgstr "Permitir"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:217
+msgid "Revoke access to all tables?"
+msgstr "Revoga o acesso a todas as tabelas?"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:218
+msgid "This will also revoke this group's access to raw queries for this database."
+msgstr "Isso também revogará o acesso desse grupo a consultas não formatadas para este banco de dados."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:247
+msgid "Grant unrestricted access"
+msgstr "Conceder acesso sem restrições"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:248
+msgid "Unrestricted access"
+msgstr "Acesso sem restrições"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:255
+msgid "Limited access"
+msgstr "Acesso limitado"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:263
+msgid "No access"
+msgstr "Sem acesso"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:269
+msgid "Write raw queries"
+msgstr "Escrever consultas diretas"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:270
+msgid "Can write raw queries"
+msgstr "Você pode escrever consultas diretas"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:277
+msgid "Curate collection"
+msgstr "Pamper a coleção"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:284
+msgid "View collection"
+msgstr "Ver coleção"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:327
+#: frontend/src/metabase/admin/permissions/selectors.js:423
+#: frontend/src/metabase/admin/permissions/selectors.js:520
+msgid "Data Access"
+msgstr "Acesso aos dados"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:488
+#: frontend/src/metabase/admin/permissions/selectors.js:645
+#: frontend/src/metabase/admin/permissions/selectors.js:650
+msgid "View tables"
+msgstr "Veja as tabelas"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:586
+msgid "SQL Queries"
+msgstr "Consultas SQL"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:656
+msgid "View schemas"
+msgstr "Veja esquemas"
+
+#: frontend/src/metabase/admin/routes.jsx:45
+#: frontend/src/metabase/nav/containers/Navbar.jsx:210
+msgid "Data Model"
+msgstr "Modelo de dados"
+
+#: frontend/src/metabase/admin/settings/components/SettingsAuthenticationOptions.jsx:11
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:118
+msgid "Sign in with Google"
+msgstr "Entre com o Google"
+
+#: frontend/src/metabase/admin/settings/components/SettingsAuthenticationOptions.jsx:13
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:120
+msgid "Allows users with existing Metabase accounts to login with a Google account that matches their email address in addition to their Metabase username and password."
+msgstr "Permite que usuários com contas existentes do Metabase façam login com uma conta do Google que corresponde ao seu endereço de e-mail além do seu nome de usuário e senha do Metabase."
+
+#: frontend/src/metabase/admin/settings/components/SettingsAuthenticationOptions.jsx:17
+#: frontend/src/metabase/admin/settings/components/SettingsAuthenticationOptions.jsx:29
+#: frontend/src/metabase/components/ChannelSetupMessage.jsx:32
+msgid "Configure"
+msgstr "Configurar"
+
+#: frontend/src/metabase/admin/settings/components/SettingsAuthenticationOptions.jsx:23
+#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:13
+#: frontend/src/metabase/admin/settings/selectors.js:201
+msgid "LDAP"
+msgstr "LDAP"
+
+#: frontend/src/metabase/admin/settings/components/SettingsAuthenticationOptions.jsx:25
+msgid "Allows users within your LDAP directory to log in to Metabase with their LDAP credentials, and allows automatic mapping of LDAP groups to Metabase groups."
+msgstr "Permitir que usuários do seu diretório LDAP efetuem login no Metabase com suas credenciais LDAP e permite o mapeamento automático de grupos LDAP nos grupos do Metabase."
+
+#: frontend/src/metabase/admin/settings/components/SettingsBatchForm.jsx:17
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:69
+#: frontend/src/metabase/admin/settings/selectors.js:154
+msgid "That's not a valid email address"
+msgstr "Esse não é um endereço de e-mail válido"
+
+#: frontend/src/metabase/admin/settings/components/SettingsBatchForm.jsx:21
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:73
+msgid "That's not a valid integer"
+msgstr "Isso não é um inteiro válido"
+
+#: frontend/src/metabase/admin/settings/components/SettingsBatchForm.jsx:28
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:161
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:223
+msgid "Changes saved!"
+msgstr "Alterações salvas!"
+
+#: frontend/src/metabase/admin/settings/components/SettingsBatchForm.jsx:157
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:132
+msgid "Looks like we ran into some problems"
+msgstr "Parece que temos alguns problemas"
+
+#: frontend/src/metabase/admin/settings/components/SettingsEmailForm.jsx:12
+msgid "Send test email"
+msgstr "Enviar e-mail de verificação"
+
+#: frontend/src/metabase/admin/settings/components/SettingsEmailForm.jsx:13
+msgid "Sending..."
+msgstr "Enviando..."
+
+#: frontend/src/metabase/admin/settings/components/SettingsEmailForm.jsx:14
+msgid "Sent!"
+msgstr "Enviado!"
+
+#: frontend/src/metabase/admin/settings/components/SettingsEmailForm.jsx:82
+msgid "Clear"
+msgstr "Limpar"
+
+#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:12
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:113
+#: frontend/src/metabase/admin/settings/selectors.js:196
+msgid "Authentication"
+msgstr "Autenticação"
+
+#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:18
+msgid "Server Settings"
+msgstr "Configuração do Servidor"
+
+#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:29
+msgid "User Schema"
+msgstr "Esquema do usuário"
+
+#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:33
+msgid "Attributes"
+msgstr "Atributos"
+
+#: frontend/src/metabase/admin/settings/components/SettingsLdapForm.jsx:42
+msgid "Group Schema"
+msgstr "Esquema de grupo"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSetting.jsx:28
+msgid "Using "
+msgstr "Usando "
+
+#: frontend/src/metabase/admin/settings/components/SettingsSetupList.jsx:105
+msgid "Getting set up"
+msgstr "Preparando as coisas"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSetupList.jsx:106
+msgid "A few things you can do to get the most out of Metabase."
+msgstr "Algumas coisas que você pode fazer para tirar o máximo proveito do Metabase."
+
+#: frontend/src/metabase/admin/settings/components/SettingsSetupList.jsx:115
+msgid "Recommended next step"
+msgstr "Próximo passo recomendado"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:114
+msgid "Google Sign-In"
+msgstr "Login com Google"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:123
+msgid "To allow users to sign in with Google you'll need to give Metabase a Google Developers console application client ID. It only takes a few steps and instructions on how to create a key can be found {0}"
+msgstr "Para permitir que os usuários façam login no Google, você deve fornecer o Metabase com um ID de cliente de aplicativo do console Desenvolvimento do Google. Leva apenas alguns passos e encontre instruções sobre como criar uma chave {0}"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:137
+msgid "Your Google client ID"
+msgstr "O seu ID de cliente do Google"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSingleSignOnForm.jsx:142
+msgid "Allow users to sign up on their own if their Google account email address is from:"
+msgstr "Permite que os usuários se registrem sozinhos se o endereço de e-mail conta eletrônica da sua conta do Google é:"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:242
+msgid "Answers sent right to your Slack #channels"
+msgstr "Respostas enviadas diretamente para seus #canais no Slack"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:251
+msgid "Create a Slack Bot User for MetaBot"
+msgstr "Crie um usuário do Slack Bot para o MetaBot"
+
+#: frontend/src/metabase/admin/settings/components/SettingsSlackForm.jsx:261
+msgid "Once you're there, give it a name and click {0}. Then copy and paste the Bot API Token into the field below. Once you are done, create a \"metabase_files\" channel in Slack. Metabase needs this to upload graphs."
+msgstr "Quando estiver lá, dê um nome e clique em {0}. Então, copie e cole a API do Token do Bot no campo abaixo. Assim que você tiver terminado, crie um canal \"metabase_files\" no Slack. O Metabase precisa disso para fazer upload de gráficos."
+
+#: frontend/src/metabase/admin/settings/components/SettingsUpdatesForm.jsx:90
+msgid "You're running Metabase {0} which is the latest and greatest!"
+msgstr "Você está executando o Metabase {0}, que é o mais recente e o melhor!"
+
+#: frontend/src/metabase/admin/settings/components/SettingsUpdatesForm.jsx:99
+msgid "Metabase {0} is available.  You're running {1}"
+msgstr "Metabase {0} está disponivel. Você está executando {1}"
+
+#: frontend/src/metabase/admin/settings/components/SettingsUpdatesForm.jsx:112
+#: frontend/src/metabase/components/form/StandardForm.jsx:63
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:96
+msgid "Update"
+msgstr "Atualizar"
+
+#: frontend/src/metabase/admin/settings/components/SettingsUpdatesForm.jsx:116
+msgid "What's Changed:"
+msgstr "O que mudou:"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:131
+msgid "Add a map"
+msgstr "Adicione um mapa"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:184
+#: frontend/src/metabase/lib/core.js:100
+msgid "URL"
+msgstr "URL"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:199
+msgid "Delete custom map"
+msgstr "Remover mapa personalizado"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:201
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:327
+#: frontend/src/metabase/dashboard/components/RemoveFromDashboardModal.jsx:48
+#: frontend/src/metabase/parameters/components/ParameterWidget.jsx:177
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:104
+msgid "Remove"
+msgstr "Excluir"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:226
+#: frontend/src/metabase/dashboard/containers/DashCardCardParameterMapper.jsx:187
+#: frontend/src/metabase/parameters/components/ParameterValueWidget.jsx:241
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:142
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:184
+msgid "Select…"
+msgstr "Selecione..."
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:241
+msgid "Sample values:"
+msgstr "Valores da amostra:"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:279
+msgid "Add a new map"
+msgstr "Adicione um novo mapa"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:279
+msgid "Edit map"
+msgstr "Editar mapa"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:280
+msgid "What do you want to call this map?"
+msgstr "Como você quer chamar este mapa?"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:285
+msgid "e.g. United Kingdom, Brazil, Mars"
+msgstr "e.x. Reino Unido, Brasil, Marte"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:292
+msgid "URL for the GeoJSON file you want to use"
+msgstr "URL para o arquivo GeoJSON que você deseja usar"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:298
+msgid "Like https://my-mb-server.com/maps/my-map.json"
+msgstr "Como https://my-mb-server.com/maps/my-map.json"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:309
+#: frontend/src/metabase/query_builder/components/RunButton.jsx:33
+msgid "Refresh"
+msgstr "Atualizar"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:309
+msgid "Load"
+msgstr "Carregar"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:315
+msgid "Which property specifies the region’s identifier?"
+msgstr "Qual propriedade especifica o identificador da região?"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:324
+msgid "Which property specifies the region’s display name?"
+msgstr "Qual propriedade especifica o nome da região?"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:345
+msgid "Load a GeoJSON file to see a preview"
+msgstr "Carregue um arquivo GeoJSON para visualizar"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:363
+msgid "Save map"
+msgstr "Salvar mapa"
+
+#: frontend/src/metabase/admin/settings/components/widgets/CustomGeoJSONWidget.jsx:363
+msgid "Add map"
+msgstr "Adicionar mapa"
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLegalese.jsx:7
+msgid "Using embedding"
+msgstr "Usando incorporado"
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLegalese.jsx:9
+msgid "By enabling embedding you're agreeing to the embedding license located at"
+msgstr "Ao ativar a incorporação, você aceita a licença de incorporação localizada em"
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLegalese.jsx:19
+msgid "In plain English, when you embed charts or dashboards from Metabase in your own application, that application isn't subject to the Affero General Public License that covers the rest of Metabase, provided you keep the Metabase logo and the \"Powered by Metabase\" visible on those embeds. You should, however, read the license text linked above as that is the actual license that you will be agreeing to by enabling this feature."
+msgstr "Em linguagem simples, quando você insere tabelas ou painéis do Metabase em seu próprio aplicativo, esse aplicativo não está sujeito à licença AGPL que cobre o resto do Metabase, desde que você mantenha o logotipo do Metabase e o \"Powered by Metabase\" visível nessas inserções. No entanto, você deve ler o texto da licença, já que essa é a licença real que você concorda ao ativar esta função."
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLegalese.jsx:32
+msgid "Enable"
+msgstr "Ativar"
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLevel.jsx:24
+msgid "Premium embedding enabled"
+msgstr "Inserção premium habilitada"
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLevel.jsx:26
+msgid "Enter the token you bought from the Metabase Store"
+msgstr "Digite o token que você comprou na loja Metabase"
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLevel.jsx:53
+msgid "Premium embedding lets you disable \"Powered by Metabase\" on your embedded dashboards and questions."
+msgstr "A inserção premium te permite desativar o aviso \"Powered by Metabase\" em seus painéis e perguntas incorporadas."
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLevel.jsx:60
+msgid "Buy a token"
+msgstr "Compre um token"
+
+#: frontend/src/metabase/admin/settings/components/widgets/EmbeddingLevel.jsx:63
+msgid "Enter a token"
+msgstr "Digite um token"
+
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:134
+msgid "Edit Mappings"
+msgstr "Editar atribuições"
+
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:140
+msgid "Group Mappings"
+msgstr "Trabalhos de grupo"
+
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:147
+msgid "Create a mapping"
+msgstr "Crie uma tarefa"
+
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:149
+msgid "Mappings allow Metabase to automatically add and remove users from groups based on the membership information provided by the\n"
+"directory server. Membership to the Admin group can be granted through mappings, but will not be automatically removed as a\n"
+"failsafe measure."
+msgstr "Atribuições permitem que o Metabase adicione e remova automaticamente usuários de grupos de acordo com as informações de associação fornecidas pelo servidor de diretório. A associação ao grupo de administração pode ser conceder através de atribuições, mas não será automaticamente excluído como uma medida à prova de falhas."
+
+#: frontend/src/metabase/admin/settings/components/widgets/LdapGroupMappingsWidget.jsx:154
+msgid "Distinguished Name"
+msgstr "Nome distinto"
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:92
+msgid "Public Link"
+msgstr "Link público"
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:93
+msgid "Revoke Link"
+msgstr "Revogar link"
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:121
+msgid "Disable this link?"
+msgstr "Desativar este link?"
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:122
+msgid "They won't work anymore, and can't be restored, but you can create new links."
+msgstr "Eles não funcionarão mais e não poderão ser restaurados, mas você poderá criar novos links de acesso."
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:149
+msgid "Public Dashboard Listing"
+msgstr "Lista de Painéis Públicos"
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:152
+msgid "No dashboards have been publicly shared yet."
+msgstr "Nenhum painel foi compartilhado publicamente."
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:160
+msgid "Public Card Listing"
+msgstr "Lista de cartões públicos"
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:163
+msgid "No questions have been publicly shared yet."
+msgstr "Nenhuma pergunta foi compartilhada publicamente ainda."
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:172
+msgid "Embedded Dashboard Listing"
+msgstr "Lista de Painéis incorporados"
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:173
+msgid "No dashboards have been embedded yet."
+msgstr "Nenhum painel foi incorporado ainda."
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:183
+msgid "Embedded Card Listing"
+msgstr "Lista de cartões incorporados"
+
+#: frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing.jsx:184
+msgid "No questions have been embedded yet."
+msgstr "Nenhuma pergunta foi incorporada ainda."
+
+#: frontend/src/metabase/admin/settings/components/widgets/SecretKeyWidget.jsx:35
+msgid "Regenerate embedding key?"
+msgstr "Regenerar a chave de inserção?"
+
+#: frontend/src/metabase/admin/settings/components/widgets/SecretKeyWidget.jsx:36
+msgid "This will cause existing embeds to stop working until they are updated with the new key."
+msgstr "Isso fará com que as inserções existentes parem de funcionar até que seja atualizar com a nova senha."
+
+#: frontend/src/metabase/admin/settings/components/widgets/SecretKeyWidget.jsx:39
+msgid "Regenerate key"
+msgstr "Regenerar chave"
+
+#: frontend/src/metabase/admin/settings/components/widgets/SecretKeyWidget.jsx:47
+msgid "Generate Key"
+msgstr "Gerar chave"
+
+#: frontend/src/metabase/admin/settings/components/widgets/SettingToggle.jsx:11
+#: frontend/src/metabase/admin/settings/selectors.js:77
+#: frontend/src/metabase/admin/settings/selectors.js:86
+msgid "Enabled"
+msgstr "Habilitado"
+
+#: frontend/src/metabase/admin/settings/components/widgets/SettingToggle.jsx:11
+#: frontend/src/metabase/admin/settings/selectors.js:82
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:103
+msgid "Disabled"
+msgstr "Desativado"
+
+#: frontend/src/metabase/admin/settings/containers/SettingsEditorApp.jsx:116
+msgid "Unknown setting {0}"
+msgstr "A diretiva de configuração {0} é desconhecida"
+
+#: frontend/src/metabase/admin/settings/selectors.js:22
+msgid "Setup"
+msgstr "Configurar"
+
+#: frontend/src/metabase/admin/settings/selectors.js:27
+msgid "General"
+msgstr "Geral"
+
+#: frontend/src/metabase/admin/settings/selectors.js:32
+msgid "Site Name"
+msgstr "Nome do site"
+
+#: frontend/src/metabase/admin/settings/selectors.js:37
+msgid "Site URL"
+msgstr "URL do site"
+
+#: frontend/src/metabase/admin/settings/selectors.js:42
+msgid "Email Address for Help Requests"
+msgstr "Endereço de e-mail para solicitações de ajuda"
+
+#: frontend/src/metabase/admin/settings/selectors.js:47
+msgid "Report Timezone"
+msgstr "Fuso horário do relatório"
+
+#: frontend/src/metabase/admin/settings/selectors.js:50
+msgid "Database Default"
+msgstr "Padrão do banco de dados"
+
+#: frontend/src/metabase/admin/settings/selectors.js:53
+msgid "Select a timezone"
+msgstr "Selecione um fuso horário"
+
+#: frontend/src/metabase/admin/settings/selectors.js:54
+msgid "Not all databases support timezones, in which case this setting won't take effect."
+msgstr "Nem todos os bancos de dados suportam fusos horários, nesses casos a configuração não terá efeito."
+
+#: frontend/src/metabase/admin/settings/selectors.js:59
+msgid "Language"
+msgstr "Idioma"
+
+#: frontend/src/metabase/admin/settings/selectors.js:64
+msgid "Select a language"
+msgstr "Selecione um idioma"
+
+#: frontend/src/metabase/admin/settings/selectors.js:69
+msgid "Anonymous Tracking"
+msgstr "Acompanhamento anônimo"
+
+#: frontend/src/metabase/admin/settings/selectors.js:74
+msgid "Friendly Table and Field Names"
+msgstr "Nomes de tabelas e campos amigáveis"
+
+#: frontend/src/metabase/admin/settings/selectors.js:80
+msgid "Only replace underscores and dashes with spaces"
+msgstr "Apenas substitui sublinhados e hífens por espaços"
+
+#: frontend/src/metabase/admin/settings/selectors.js:90
+msgid "Enable Nested Queries"
+msgstr "Ativar consultas aninhadas"
+
+#: frontend/src/metabase/admin/settings/selectors.js:96
+msgid "Updates"
+msgstr "Atualizações"
+
+#: frontend/src/metabase/admin/settings/selectors.js:101
+msgid "Check for updates"
+msgstr "Verificar atualizações"
+
+#: frontend/src/metabase/admin/settings/selectors.js:112
+msgid "SMTP Host"
+msgstr "Servidor SMTP"
+
+#: frontend/src/metabase/admin/settings/selectors.js:120
+msgid "SMTP Port"
+msgstr "Porta SMTP"
+
+#: frontend/src/metabase/admin/settings/selectors.js:124
+#: frontend/src/metabase/admin/settings/selectors.js:224
+msgid "That's not a valid port number"
+msgstr "Esse não é um número de porta válido"
+
+#: frontend/src/metabase/admin/settings/selectors.js:128
+msgid "SMTP Security"
+msgstr "Segurança SMTP"
+
+#: frontend/src/metabase/admin/settings/selectors.js:136
+msgid "SMTP Username"
+msgstr "Usuário SMTP"
+
+#: frontend/src/metabase/admin/settings/selectors.js:143
+msgid "SMTP Password"
+msgstr "Senha SMTP"
+
+#: frontend/src/metabase/admin/settings/selectors.js:150
+msgid "From Address"
+msgstr "E-mail do remetente"
+
+#: frontend/src/metabase/admin/settings/selectors.js:164
+msgid "Slack API Token"
+msgstr "Slack API Token"
+
+#: frontend/src/metabase/admin/settings/selectors.js:166
+msgid "Enter the token you received from Slack"
+msgstr "Digite o token que você recebeu do Slack"
+
+#: frontend/src/metabase/admin/settings/selectors.js:183
+msgid "Single Sign-On"
+msgstr "Início de sessão única"
+
+#: frontend/src/metabase/admin/settings/selectors.js:207
+msgid "LDAP Authentication"
+msgstr "Autenticação LDAP"
+
+#: frontend/src/metabase/admin/settings/selectors.js:213
+msgid "LDAP Host"
+msgstr "Servidor LDAP"
+
+#: frontend/src/metabase/admin/settings/selectors.js:221
+msgid "LDAP Port"
+msgstr "Porta LDAP"
+
+#: frontend/src/metabase/admin/settings/selectors.js:228
+msgid "LDAP Security"
+msgstr "Segurança LDAP"
+
+#: frontend/src/metabase/admin/settings/selectors.js:236
+msgid "Username or DN"
+msgstr "Nome de usuário ou DN"
+
+#: frontend/src/metabase/admin/settings/selectors.js:241
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:188
+#: frontend/src/metabase/user/components/UserSettings.jsx:72
+msgid "Password"
+msgstr "Senha"
+
+#: frontend/src/metabase/admin/settings/selectors.js:246
+msgid "User search base"
+msgstr "Base de pesquisa do usuário"
+
+#: frontend/src/metabase/admin/settings/selectors.js:252
+msgid "User filter"
+msgstr "Filtro do usuário"
+
+#: frontend/src/metabase/admin/settings/selectors.js:258
+msgid "Check your parentheses"
+msgstr "Verifique os parênteses"
+
+#: frontend/src/metabase/admin/settings/selectors.js:264
+msgid "Email attribute"
+msgstr "Atributo do e-mail"
+
+#: frontend/src/metabase/admin/settings/selectors.js:269
+msgid "First name attribute"
+msgstr "Atributo do nome"
+
+#: frontend/src/metabase/admin/settings/selectors.js:274
+msgid "Last name attribute"
+msgstr "Atributo do sobrenome"
+
+#: frontend/src/metabase/admin/settings/selectors.js:279
+msgid "Synchronize group memberships"
+msgstr "Sincronizar membros do grupo"
+
+#: frontend/src/metabase/admin/settings/selectors.js:285
+msgid "Group search base"
+msgstr "Base de pesquisa de grupo"
+
+#: frontend/src/metabase/admin/settings/selectors.js:294
+msgid "Maps"
+msgstr "Mapas"
+
+#: frontend/src/metabase/admin/settings/selectors.js:299
+msgid "Map tile server URL"
+msgstr "URL do servidor da camada de mapa"
+
+#: frontend/src/metabase/admin/settings/selectors.js:300
+msgid "Metabase uses OpenStreetMaps by default."
+msgstr "O Metabase usa o OpenStreetMaps por padrão."
+
+#: frontend/src/metabase/admin/settings/selectors.js:305
+msgid "Custom Maps"
+msgstr "Mapas personalizados"
+
+#: frontend/src/metabase/admin/settings/selectors.js:306
+msgid "Add your own GeoJSON files to enable different region map visualizations"
+msgstr "Adicione seus próprios arquivos GeoJSON para permitir diferentes visualizações de mapas da região"
+
+#: frontend/src/metabase/admin/settings/selectors.js:313
+msgid "Public Sharing"
+msgstr "Compartilhamento público"
+
+#: frontend/src/metabase/admin/settings/selectors.js:318
+msgid "Enable Public Sharing"
+msgstr "Ativar o compartilhamento público"
+
+#: frontend/src/metabase/admin/settings/selectors.js:323
+msgid "Shared Dashboards"
+msgstr "Painéis compartilhados"
+
+#: frontend/src/metabase/admin/settings/selectors.js:329
+msgid "Shared Questions"
+msgstr "Perguntas compartilhadas"
+
+#: frontend/src/metabase/admin/settings/selectors.js:336
+msgid "Embedding in other Applications"
+msgstr "Incorporar em outras aplicações"
+
+#: frontend/src/metabase/admin/settings/selectors.js:363
+msgid "Enable Embedding Metabase in other Applications"
+msgstr "Habilitar incorporação do Metabase em outros aplicativos"
+
+#: frontend/src/metabase/admin/settings/selectors.js:373
+msgid "Embedding secret key"
+msgstr "Chave secreta de incorporação"
+
+#: frontend/src/metabase/admin/settings/selectors.js:379
+msgid "Embedded Dashboards"
+msgstr "Painéis embutidos"
+
+#: frontend/src/metabase/admin/settings/selectors.js:385
+msgid "Embedded Questions"
+msgstr "Perguntas incorporadas"
+
+#: frontend/src/metabase/admin/settings/selectors.js:392
+msgid "Caching"
+msgstr "Cache"
+
+#: frontend/src/metabase/admin/settings/selectors.js:397
+msgid "Enable Caching"
+msgstr "Ativar cache"
+
+#: frontend/src/metabase/admin/settings/selectors.js:402
+msgid "Minimum Query Duration"
+msgstr "Duração mínima da consulta"
+
+#: frontend/src/metabase/admin/settings/selectors.js:409
+msgid "Cache Time-To-Live (TTL) multiplier"
+msgstr "Cache do Time-to-Live Multiplier (TTL)"
+
+#: frontend/src/metabase/admin/settings/selectors.js:416
+msgid "Max Cache Entry Size"
+msgstr "Tamanho máximo de entrada de cache"
+
+#: frontend/src/metabase/alert/alert.js:60
+msgid "Your alert is all set up."
+msgstr "Seu alerta está definido."
+
+#: frontend/src/metabase/alert/alert.js:101
+msgid "Your alert was updated."
+msgstr "Seu alerta foi atualizado."
+
+#: frontend/src/metabase/alert/alert.js:149
+msgid "The alert was successfully deleted."
+msgstr "O alerta foi removido corretamente."
+
+#: frontend/src/metabase/auth/auth.js:33
+msgid "Please enter a valid formatted email address."
+msgstr "Por favor, insira um endereço de e-mail com um formato válido."
+
+#: frontend/src/metabase/auth/auth.js:116
+#: frontend/src/metabase/setup/components/UserStep.jsx:110
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:69
+msgid "Passwords do not match"
+msgstr "As senhas não correspondem"
+
+#: frontend/src/metabase/auth/components/BackToLogin.jsx:6
+msgid "Back to login"
+msgstr "Retornar ao login"
+
+#: frontend/src/metabase/auth/components/GoogleNoAccount.jsx:15
+msgid "No Metabase account exists for this Google account."
+msgstr "Não existe uma conta no Metabase para esta conta do Google."
+
+#: frontend/src/metabase/auth/components/GoogleNoAccount.jsx:17
+msgid "You'll need an administrator to create a Metabase account before you can use Google to log in."
+msgstr "Você precisará de um administrador para criar uma conta do Metabase antes de poder usar o Google para fazer login."
+
+#: frontend/src/metabase/auth/components/SSOLoginButton.jsx:18
+msgid "Sign in with {0}"
+msgstr "Entre com {0}"
+
+#: frontend/src/metabase/auth/containers/ForgotPasswordApp.jsx:56
+msgid "Please contact an administrator to have them reset your password"
+msgstr "Entre em contato com um administrador para redefinir sua senha"
+
+#: frontend/src/metabase/auth/containers/ForgotPasswordApp.jsx:69
+msgid "Forgot password"
+msgstr "Esqueci minha senha"
+
+#: frontend/src/metabase/auth/containers/ForgotPasswordApp.jsx:84
+msgid "The email you use for your Metabase account"
+msgstr "O e-mail que você usa para sua conta no Metabase"
+
+#: frontend/src/metabase/auth/containers/ForgotPasswordApp.jsx:99
+msgid "Send password reset email"
+msgstr "Enviar e-mail de redefinição de senha"
+
+#: frontend/src/metabase/auth/containers/ForgotPasswordApp.jsx:110
+msgid "Check your email for instructions on how to reset your password."
+msgstr "Consulte o seu e-mail para obter instruções sobre como redefinir sua senha"
+
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:128
+msgid "Sign in to Metabase"
+msgstr "Entre no Metabase"
+
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:138
+msgid "OR"
+msgstr "OU"
+
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:157
+msgid "Username or email address"
+msgstr "Nome de usuário ou endereço de e-mail"
+
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:217
+msgid "Sign in"
+msgstr "Entrar"
+
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:230
+msgid "I seem to have forgotten my password"
+msgstr "Parece que esqueci minha senha"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:102
+msgid "request a new reset email"
+msgstr "solicitar envio de e-mail de redefinição de senha"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:120
+msgid "Whoops, that's an expired link"
+msgstr "Oops, esse é um link expirado"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:122
+msgid "For security reasons, password reset links expire after a little while. If you still need\n"
+"to reset your password, you can {0}."
+msgstr "Por razões de segurança, links de redefinição de senha expiram depois de um tempo. \n"
+"Se você ainda precisar redefinir sua senha, você pode {0}."
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:147
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:126
+msgid "New password"
+msgstr "Nova senha"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:149
+msgid "To keep your data secure, passwords {0}"
+msgstr "Para manter seus dados seguros, senhas {0}"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:163
+msgid "Create a new password"
+msgstr "Crie uma nova senha"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:170
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:141
+msgid "Make sure its secure like the instructions above"
+msgstr "Você deve cumprir as instruções acima"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:184
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:150
+msgid "Confirm new password"
+msgstr "Confirme a nova senha"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:191
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:159
+msgid "Make sure it matches the one you just entered"
+msgstr "Tem que combinar com o que você acabou de colocar"
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:216
+msgid "Your password has been reset."
+msgstr "Sua senha foi redefinida."
+
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:222
+#: frontend/src/metabase/auth/containers/PasswordResetApp.jsx:227
+msgid "Sign in with your new password"
+msgstr "Entre com sua nova senha"
+
+#: frontend/src/metabase/components/ActionButton.jsx:53
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:182
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:184
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:251
+msgid "Save failed"
+msgstr "Falhou"
+
+#: frontend/src/metabase/components/ActionButton.jsx:54
+#: frontend/src/metabase/components/SaveStatus.jsx:60
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:183
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:124
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:185
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:225
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:252
+msgid "Saved"
+msgstr "Salvo"
+
+#: frontend/src/metabase/components/Alert.jsx:12
+msgid "Ok"
+msgstr "Ok"
+
+#: frontend/src/metabase/components/ArchiveCollectionModal.jsx:40
+msgid "Archive this collection?"
+msgstr "Arquivar esta coleção?"
+
+#: frontend/src/metabase/components/ArchiveCollectionModal.jsx:45
+msgid "The dashboards, collections, and pulses in this collection will also be archived."
+msgstr "Os painéis, coleções e notificações nesta coleção também serão arquivados."
+
+#: frontend/src/metabase/components/ArchiveCollectionModal.jsx:49
+#: frontend/src/metabase/components/CollectionLanding.jsx:587
+#: frontend/src/metabase/components/EntityItem.jsx:54
+#: frontend/src/metabase/components/EntityMenu.info.js:31
+#: frontend/src/metabase/components/EntityMenu.info.js:87
+#: frontend/src/metabase/home/containers/ArchiveApp.jsx:48
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:195
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:200
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:201
+#: frontend/src/metabase/query_builder/containers/ArchiveQuestionModal.jsx:40
+#: frontend/src/metabase/query_builder/containers/ArchiveQuestionModal.jsx:53
+#: frontend/src/metabase/routes.jsx:199
+msgid "Archive"
+msgstr "Arquivo"
+
+#: frontend/src/metabase/containers/ErrorPages.jsx:63
+msgid "This {0} has been archived"
+msgstr "Este {0} foi arquivado"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:679
+msgid "View the archive"
+msgstr "Veja o arquivo"
+
+#: frontend/src/metabase/components/ArchivedItem.jsx:39
+msgid "Unarchive this {0}"
+msgstr "Desarquivar este {0}"
+
+#: frontend/src/metabase/components/BrowseApp.jsx:93
+#: frontend/src/metabase/components/BrowseApp.jsx:152
+#: frontend/src/metabase/components/BrowseApp.jsx:243
+#: frontend/src/metabase/containers/Overworld.jsx:222
+msgid "Our data"
+msgstr "Nossos dados"
+
+#: frontend/src/metabase/components/BrowseApp.jsx:188
+#: frontend/src/metabase/reference/databases/TableSidebar.jsx:51
+msgid "X-ray this table"
+msgstr "Aplique raio-X nesta tabela"
+
+#: frontend/src/metabase/components/BrowseApp.jsx:201
+#: frontend/src/metabase/containers/Overworld.jsx:249
+msgid "Learn about this table"
+msgstr "Aprenda mais sobre essa tabela"
+
+#: frontend/src/metabase/components/Button.info.js:11
+#: frontend/src/metabase/components/Button.info.js:12
+#: frontend/src/metabase/components/Button.info.js:13
+msgid "Clickity click"
+msgstr "Clique"
+
+#: frontend/src/metabase/components/ButtonWithStatus.jsx:9
+msgid "Saved!"
+msgstr "Salvo!"
+
+#: frontend/src/metabase/components/ButtonWithStatus.jsx:10
+msgid "Saving failed."
+msgstr "Não foi possível salvar"
+
+#: frontend/src/metabase/components/Calendar.jsx:119
+msgid "Su"
+msgstr "Dom"
+
+#: frontend/src/metabase/components/Calendar.jsx:119
+msgid "Mo"
+msgstr "Seg"
+
+#: frontend/src/metabase/components/Calendar.jsx:119
+msgid "Tu"
+msgstr "Ter"
+
+#: frontend/src/metabase/components/Calendar.jsx:119
+msgid "We"
+msgstr "Qua"
+
+#: frontend/src/metabase/components/Calendar.jsx:119
+msgid "Th"
+msgstr "Qui"
+
+#: frontend/src/metabase/components/Calendar.jsx:119
+msgid "Fr"
+msgstr "Sex"
+
+#: frontend/src/metabase/components/Calendar.jsx:119
+msgid "Sa"
+msgstr "Sáb"
+
+#: frontend/src/metabase/components/ChannelSetupMessage.jsx:41
+msgid "Your admin's email address"
+msgstr "O endereço de e-mail do seu administrador"
+
+#: frontend/src/metabase/components/ChannelSetupModal.jsx:37
+msgid "To send {0}, you'll need to set up {1} integration."
+msgstr "Para enviar {0}, você terá que configurar a integração com {1}."
+
+#: frontend/src/metabase/components/ChannelSetupModal.jsx:38
+#: frontend/src/metabase/components/ChannelSetupModal.jsx:41
+msgid " or "
+msgstr " o "
+
+#: frontend/src/metabase/components/ChannelSetupModal.jsx:40
+msgid "To send {0}, an admin needs to set up {1} integration."
+msgstr "Para enviar {0}, um administrador precisa configurar a integração com {1}."
+
+#: frontend/src/metabase/components/CollectionEmptyState.jsx:15
+msgid "This collection is empty, like a blank canvas"
+msgstr "Essa coleção está vazia, como uma tela em branco"
+
+#: frontend/src/metabase/components/CollectionEmptyState.jsx:16
+msgid "You can use collections to organize and group dashboards, questions and pulses for your team or yourself"
+msgstr "Você pode usar coleções para organizar e agrupar painéis, questões e notificações para o seu time ou pra você."
+
+#: frontend/src/metabase/components/CollectionEmptyState.jsx:28
+msgid "Create another collection"
+msgstr "Criar outra coleção"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:61
+msgid "Dashboards let you collect and share data in one place."
+msgstr "Painéis permitem você agrupar e compartilhar dados em um único local."
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:70
+msgid "Pulses let you send out the latest data to your team on a schedule via email or slack."
+msgstr "Notificações te permitem enviar os dados atualizados para seu time seguindo um cronograma via email ou no slack."
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:79
+msgid "Questions are a saved look at your data."
+msgstr "Perguntas são visões salvas de seus dados."
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:275
+msgid "Pins"
+msgstr "Fixados"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:329
+msgid "Drag something here to pin it to the top"
+msgstr "Arraste algo aqui para fixar no topo da página."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:733
+#: frontend/src/metabase/components/CollectionLanding.jsx:341
+#: frontend/src/metabase/home/containers/SearchApp.jsx:35
+#: frontend/src/metabase/home/containers/SearchApp.jsx:96
+msgid "Collections"
+msgstr "Coleções"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:411
+#: frontend/src/metabase/components/CollectionLanding.jsx:434
+msgid "Drag here to un-pin"
+msgstr "Arraste aqui para desafixar"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:469
+msgid "{0} item selected"
+msgid_plural "{0} items selected"
+msgstr[0] "{0} item selecionado"
+msgstr[1] "{0} itens selecionados"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:487
+msgid "Move {0} items?"
+msgstr "Mover {0} itens?"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:488
+msgid "Move \"{0}\"?"
+msgstr "Mover \"{0}\"?"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:594
+#: frontend/src/metabase/components/EntityMenu.info.js:29
+#: frontend/src/metabase/components/EntityMenu.info.js:85
+#: frontend/src/metabase/containers/CollectionMoveModal.jsx:78
+msgid "Move"
+msgstr "Mover"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:656
+msgid "Edit this collection"
+msgstr "Editar coleção"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:664
+msgid "Archive this collection"
+msgstr "Arquivar esta coleção?"
+
+#: frontend/src/metabase/components/CollectionList.jsx:64
+#: frontend/src/metabase/entities/collections.js:148
+msgid "My personal collection"
+msgstr "Minha coleção pessoal"
+
+#: frontend/src/metabase/components/CollectionList.jsx:106
+#: frontend/src/metabase/containers/CollectionForm.jsx:9
+msgid "New collection"
+msgstr "Coleção nova"
+
+#: frontend/src/metabase/components/CopyButton.jsx:35
+msgid "Copied!"
+msgstr "Copiado!"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:216
+msgid "Use an SSH-tunnel for database connections"
+msgstr "Usa um túnel SSH para conexões com o banco de dados"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:218
+msgid "Some database installations can only be accessed by connecting through an SSH bastion host.\n"
+"This option also provides an extra layer of security when a VPN is not available.\n"
+"Enabling this is usually slower than a direct connection."
+msgstr "Algumas instalações de banco de dados só podem ser acessadas conectando através de um túnel SSH. \n"
+"Esta opção também fornece uma camada adicional de segurança quando não você tem uma VPN. \n"
+"Isso geralmente é mais lento que uma conexão direta."
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:274
+msgid "This is a large database, so let me choose when Metabase syncs and scans"
+msgstr "Este é um grande banco de dados, então deixe-me escolher quando o Metabase sincronizar e digitalizar"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:276
+msgid "By default, Metabase does a lightweight hourly sync and an intensive daily scan of field values.\n"
+"If you have a large database, we recommend turning this on and reviewing when and how often the field value scans happen."
+msgstr "Por padrão, o Metabase executa uma sincronização leve de hora em hora, e uma análise diária intensiva dos valores do campo.\n"
+"Se você tem um banco de dados grande, recomendamos ativá-lo e verificar quando e com que frequência as varreduras de valores de campo ocorrem."
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:292
+msgid "{0} to generate a Client ID and Client Secret for your project."
+msgstr "{0} para gerar um ID e uma chave secreta do cliente para seu projeto."
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:294
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:321
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:356
+msgid "Click here"
+msgstr "Clique aqui"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:297
+msgid "Choose \"Other\" as the application type. Name it whatever you'd like."
+msgstr "Escolhe \"Outro\" como tipo de aplicação. Nomeie-o como quiser"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:319
+msgid "{0} to get an auth code"
+msgstr "{0} para obter um código de autenticação"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:331
+msgid "with Google Drive permissions"
+msgstr "com permissões do Google Drive"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:351
+msgid "To use Metabase with this data you must enable API access in the Google Developers Console."
+msgstr "Para usar o Metabase com esses dados, você deve ativar o acesso à API em Google Developers Console."
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:354
+msgid "{0} to go to the console if you haven't already done so."
+msgstr "{0} para ir ao console, se você ainda não fez isso."
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:403
+msgid "How would you like to refer to this database?"
+msgstr "Como você gostaria de chamar esse banco de dados?"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:430
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:97
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:240
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:188
+#: frontend/src/metabase/setup/components/DatabaseSchedulingStep.jsx:74
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:116
+#: frontend/src/metabase/setup/components/UserStep.jsx:308
+msgid "Next"
+msgstr "Próximo"
+
+#: frontend/src/metabase/components/DeleteModalWithConfirm.jsx:80
+msgid "Delete this {0}"
+msgstr "Remova este {0}"
+
+#: frontend/src/metabase/components/EntityItem.jsx:42
+msgid "Pin this item"
+msgstr "Fixar este item"
+
+#: frontend/src/metabase/components/EntityItem.jsx:48
+msgid "Move this item"
+msgstr "Mover este item"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:24
+#: frontend/src/metabase/components/EntityMenu.info.js:80
+msgid "Edit this question"
+msgstr "Editar esta questão"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:26
+#: frontend/src/metabase/components/EntityMenu.info.js:47
+#: frontend/src/metabase/components/EntityMenu.info.js:82
+#: frontend/src/metabase/components/EntityMenu.info.js:99
+msgid "Action type"
+msgstr "Tipo de ação"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:28
+#: frontend/src/metabase/components/EntityMenu.info.js:84
+msgid "View revision history"
+msgstr "Veja o histórico de revisões"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:29
+#: frontend/src/metabase/components/EntityMenu.info.js:85
+msgid "Move action"
+msgstr "Ação de mover"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:33
+#: frontend/src/metabase/components/EntityMenu.info.js:89
+msgid "Archive action"
+msgstr "Ação de arquivamento"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:45
+#: frontend/src/metabase/components/EntityMenu.info.js:97
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:329
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:342
+msgid "Add to dashboard"
+msgstr "Adicionar ao Painel"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:49
+#: frontend/src/metabase/components/EntityMenu.info.js:101
+msgid "Download results"
+msgstr "Baixe os resultados"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:51
+#: frontend/src/metabase/components/EntityMenu.info.js:103
+#: frontend/src/metabase/public/components/widgets/EmbedWidget.jsx:52
+msgid "Sharing and embedding"
+msgstr "Compartilhando e Incorporando"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:53
+#: frontend/src/metabase/components/EntityMenu.info.js:105
+msgid "Another action type"
+msgstr "Outro tipo de ação"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:65
+#: frontend/src/metabase/components/EntityMenu.info.js:67
+#: frontend/src/metabase/components/EntityMenu.info.js:113
+#: frontend/src/metabase/components/EntityMenu.info.js:115
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:449
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:454
+msgid "Get alerts about this"
+msgstr "Receber alertas sobre isso"
+
+#: frontend/src/metabase/components/EntityMenu.info.js:69
+#: frontend/src/metabase/components/EntityMenu.info.js:117
+msgid "View the SQL"
+msgstr "Veja o SQL"
+
+#: frontend/src/metabase/components/EntitySegments.jsx:18
+msgid "Segments for this"
+msgstr "Segmentos para este"
+
+#: frontend/src/metabase/components/ErrorDetails.jsx:20
+msgid "Show error details"
+msgstr "Mostrar detalhes do erro"
+
+#: frontend/src/metabase/components/ErrorDetails.jsx:26
+msgid "Here's the full error message"
+msgstr "Aqui está a mensagem de erro completa"
+
+#: frontend/src/metabase/components/ExplorePane.jsx:19
+msgid "Hi, Metabot here."
+msgstr "Oi, eu sou o metabot"
+
+#: frontend/src/metabase/components/ExplorePane.jsx:95
+msgid "Based on the schema"
+msgstr "Baseado no schema"
+
+#. Alterei pois, é da tela principal e ficava como "Uma olhada no seu XXXX tabela"
+#. alterando para "Uma olhada em XXXX tabela"
+#: frontend/src/metabase/components/ExplorePane.jsx:174
+msgid "A look at your"
+msgstr "Uma olhada em"
+
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:234
+msgid "Search the list"
+msgstr "Pesquise na lista"
+
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:238
+msgid "Search by {0}"
+msgstr "Pesquise por {0}"
+
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:240
+msgid " or enter an ID"
+msgstr "ou insira um ID"
+
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:244
+msgid "Enter an ID"
+msgstr "Insira um ID"
+
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:246
+msgid "Enter a number"
+msgstr "Digite um número"
+
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:248
+msgid "Enter some text"
+msgstr "Digite um texto"
+
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:355
+msgid "No matching {0} found."
+msgstr "Nenhuma correspondência {0} foi encontrada."
+
+#: frontend/src/metabase/components/FieldValuesWidget.jsx:363
+msgid "Including every option in your filter probably won’t do much…"
+msgstr "Incluindo cada opção no seu filtro provavelmente não vai fazer muito..."
+
+#: frontend/src/metabase/containers/ErrorPages.jsx:24
+msgid "Something's gone wrong"
+msgstr "Algo deu errado."
+
+#: frontend/src/metabase/containers/ErrorPages.jsx:25
+msgid "We've run into an error. You can try refreshing the page, or just go back."
+msgstr "Encontramos um erro. Você pode tentar atualizar a página, ou voltar."
+
+#: frontend/src/metabase/components/Header.jsx:97
+#: frontend/src/metabase/components/HeaderBar.jsx:45
+#: frontend/src/metabase/components/ListItem.jsx:37
+#: frontend/src/metabase/reference/components/Detail.jsx:47
+#: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:158
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:213
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:191
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:205
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:209
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:209
+msgid "No description yet"
+msgstr "Ainda sem descrição"
+
+#: frontend/src/metabase/components/Header.jsx:112
+msgid "New {0}"
+msgstr "Novo {0}"
+
+#: frontend/src/metabase/components/Header.jsx:123
+msgid "Asked by {0}"
+msgstr "Perguntado por {0}"
+
+#: frontend/src/metabase/components/HistoryModal.jsx:13
+msgid "Today, "
+msgstr "Hoje "
+
+#: frontend/src/metabase/components/HistoryModal.jsx:15
+msgid "Yesterday, "
+msgstr "Ontem "
+
+#: frontend/src/metabase/components/HistoryModal.jsx:68
+msgid "First revision."
+msgstr "Primeira revisão."
+
+#: frontend/src/metabase/components/HistoryModal.jsx:70
+msgid "Reverted to an earlier revision and {0}"
+msgstr "Revertido para uma revisão anterior e {0}"
+
+#: frontend/src/metabase/components/HistoryModal.jsx:82
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:289
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:379
+#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:54
+msgid "Revision history"
+msgstr "Histórico de revisão"
+
+#: frontend/src/metabase/components/HistoryModal.jsx:90
+msgid "When"
+msgstr "Quando"
+
+#: frontend/src/metabase/components/HistoryModal.jsx:91
+msgid "Who"
+msgstr "Quem"
+
+#: frontend/src/metabase/components/HistoryModal.jsx:92
+msgid "What"
+msgstr "Que"
+
+#: frontend/src/metabase/components/HistoryModal.jsx:113
+msgid "Revert"
+msgstr "Reverter"
+
+#: frontend/src/metabase/components/HistoryModal.jsx:114
+msgid "Reverting…"
+msgstr "Revertendo..."
+
+#: frontend/src/metabase/components/HistoryModal.jsx:115
+msgid "Revert failed"
+msgstr "Reverter falhou"
+
+#: frontend/src/metabase/components/HistoryModal.jsx:116
+msgid "Reverted"
+msgstr "Revertido"
+
+#: frontend/src/metabase/components/ItemTypeFilterBar.jsx:13
+msgid "Everything"
+msgstr "Tudo"
+
+#: frontend/src/metabase/components/ItemTypeFilterBar.jsx:18
+#: frontend/src/metabase/home/containers/SearchApp.jsx:73
+msgid "Dashboards"
+msgstr "Painéis"
+
+#: frontend/src/metabase/components/ItemTypeFilterBar.jsx:23
+#: frontend/src/metabase/home/containers/SearchApp.jsx:119
+msgid "Questions"
+msgstr "Perguntas"
+
+#: frontend/src/metabase/components/ItemTypeFilterBar.jsx:28
+#: frontend/src/metabase/routes.jsx:320
+msgid "Pulses"
+msgstr "Notificações"
+
+#: frontend/src/metabase/components/LeftNavPane.jsx:36
+#: frontend/src/metabase/query_builder/components/dataref/DataReference.jsx:86
+msgid "Back"
+msgstr "Voltar"
+
+#: frontend/src/metabase/components/ListSearchField.jsx:18
+msgid "Find..."
+msgstr "Encontrar..."
+
+#: frontend/src/metabase/components/LoadingAndErrorWrapper.jsx:48
+msgid "An error occured"
+msgstr "Ocorreu um erro"
+
+#: frontend/src/metabase/components/LoadingAndErrorWrapper.jsx:35
+msgid "Loading..."
+msgstr "Carregando..."
+
+#: frontend/src/metabase/components/NewsletterForm.jsx:71
+msgid "Metabase Newsletter"
+msgstr "Boletim Metabase"
+
+#: frontend/src/metabase/components/NewsletterForm.jsx:81
+msgid "Get infrequent emails about new releases and feature updates."
+msgstr "Receba e-mails pouco frequentes sobre novos lançamentos e atualizações."
+
+#: frontend/src/metabase/components/NewsletterForm.jsx:99
+msgid "Subscribe"
+msgstr "Subscrever"
+
+#: frontend/src/metabase/components/NewsletterForm.jsx:106
+msgid "You're subscribed. Thanks for using Metabase!"
+msgstr "Você está inscrito. Obrigado por usar o Metabase!"
+
+#: frontend/src/metabase/containers/ErrorPages.jsx:44
+msgid "We're a little lost..."
+msgstr "Estamos um pouco perdidos..."
+
+#: frontend/src/metabase/components/PasswordReveal.jsx:27
+msgid "Temporary Password"
+msgstr "Senha Temporária"
+
+#: frontend/src/metabase/components/PasswordReveal.jsx:68
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:334
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:349
+msgid "Hide"
+msgstr "Esconder"
+
+#: frontend/src/metabase/components/PasswordReveal.jsx:68
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:335
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:350
+msgid "Show"
+msgstr "Mostrar"
+
+#: frontend/src/metabase/components/QuestionSavedModal.jsx:17
+msgid "Saved! Add this to a dashboard?"
+msgstr "Salvo! Adicionar isto a um painel?"
+
+#: frontend/src/metabase/components/QuestionSavedModal.jsx:25
+msgid "Yes please!"
+msgstr "Sim, por favor!"
+
+#: frontend/src/metabase/components/QuestionSavedModal.jsx:29
+msgid "Not now"
+msgstr "Agora não"
+
+#: frontend/src/metabase/components/SaveStatus.jsx:53
+msgid "Error:"
+msgstr "Erro:"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:23
+msgid "Sunday"
+msgstr "Domingo"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:24
+msgid "Monday"
+msgstr "Segunda-feira"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:25
+msgid "Tuesday"
+msgstr "Terça-feira"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:26
+msgid "Wednesday"
+msgstr "Quarta-feira"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:27
+msgid "Thursday"
+msgstr "Quinta-feira"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:28
+msgid "Friday"
+msgstr "Sexta-feira"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:29
+msgid "Saturday"
+msgstr "Sábado"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:33
+msgid "First"
+msgstr "Primeiro"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:34
+msgid "Last"
+msgstr "Último"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:35
+msgid "15th (Midpoint)"
+msgstr "15 (ponto médio)"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:125
+msgid "Calendar Day"
+msgstr "Dia do calendário"
+
+#: frontend/src/metabase/components/SchedulePicker.jsx:210
+msgid "your Metabase timezone"
+msgstr "o seu fuso horário no Metabase"
+
+#: frontend/src/metabase/components/SearchHeader.jsx:21
+msgid "Filter this list..."
+msgstr "Filtrar esta lista..."
+
+#: frontend/src/metabase/components/Select.info.js:8
+msgid "Blue"
+msgstr "Azul"
+
+#: frontend/src/metabase/components/Select.info.js:9
+msgid "Green"
+msgstr "Green"
+
+#: frontend/src/metabase/components/Select.info.js:10
+msgid "Red"
+msgstr "Vermelho"
+
+#: frontend/src/metabase/components/Select.info.js:11
+msgid "Yellow"
+msgstr "Amarelo"
+
+#: frontend/src/metabase/components/Select.info.js:14
+msgid "A component used to make a selection"
+msgstr "Um componente usado para fazer uma seleção"
+
+#: frontend/src/metabase/components/Select.info.js:20
+#: frontend/src/metabase/components/Select.info.js:28
+msgid "Selected"
+msgstr "Selecionado"
+
+#: frontend/src/metabase/components/Select.jsx:281
+msgid "Nothing to select"
+msgstr "Nada para selecionar"
+
+#: frontend/src/metabase/containers/ErrorPages.jsx:54
+msgid "Sorry, you don’t have permission to see that."
+msgstr "Desculpe, você não tem permissão para ver isso."
+
+#: frontend/src/metabase/components/form/FormMessage.jsx:5
+msgid "Unknown error encountered"
+msgstr "Erro desconhecido encontrado"
+
+#: frontend/src/metabase/components/form/StandardForm.jsx:63
+#: frontend/src/metabase/nav/containers/Navbar.jsx:300
+msgid "Create"
+msgstr "Criar"
+
+#: frontend/src/metabase/containers/DashboardForm.jsx:9
+msgid "Create dashboard"
+msgstr "Criar um painel"
+
+#: frontend/src/metabase/containers/EntitySearch.jsx:35
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:324
+#: frontend/src/metabase/visualizations/visualizations/Table.jsx:44
+msgid "Table"
+msgstr "Tabela"
+
+#: frontend/src/metabase/containers/EntitySearch.jsx:42
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:299
+msgid "Database"
+msgstr "Banco de dados"
+
+#: frontend/src/metabase/containers/EntitySearch.jsx:49
+msgid "Creator"
+msgstr "Criador"
+
+#: frontend/src/metabase/containers/EntitySearch.jsx:238
+msgid "No results found"
+msgstr "Não foram encontrados resultados"
+
+#: frontend/src/metabase/containers/EntitySearch.jsx:239
+msgid "Try adjusting your filter to find what you’re looking for."
+msgstr "Tente ajustar seu filtro para encontrar o que você está procurando."
+
+#: frontend/src/metabase/containers/EntitySearch.jsx:258
+msgid "View by"
+msgstr "Veja por"
+
+#: frontend/src/metabase/containers/EntitySearch.jsx:494
+#: frontend/src/metabase/query_builder/components/AggregationWidget.jsx:69
+#: frontend/src/metabase/tutorial/TutorialModal.jsx:34
+msgid "of"
+msgstr "de"
+
+#: frontend/src/metabase/containers/Overworld.jsx:78
+msgid "Don't tell anyone, but you're my favorite."
+msgstr "Não conte a ninguém, mas você é o meu favorito"
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:85
+msgid "Once you connect your own data, I can show you some automatic explorations called x-rays. Here are some examples with sample data."
+msgstr "Assim que você conectar seus próprios dados, eu posso mostrar algumas explorações automáticas, chamadas raios-X. Aqui estão alguns exemplos com dados de exemplo."
+
+#: frontend/src/metabase/containers/Overworld.jsx:131
+#: frontend/src/metabase/containers/Overworld.jsx:302
+#: frontend/src/metabase/reference/components/GuideHeader.jsx:12
+msgid "Start here"
+msgstr "Comece aqui"
+
+#: frontend/src/metabase/containers/Overworld.jsx:297
+#: frontend/src/metabase/entities/collections.js:140
+#: src/metabase/models/collection.clj
+msgid "Our analytics"
+msgstr "Nossas análises"
+
+#: frontend/src/metabase/containers/Overworld.jsx:206
+msgid "Browse all items"
+msgstr "Exibir todos os itens"
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:165
+msgid "Replace or save as new?"
+msgstr "Substituir ou salvar como novo?"
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:173
+msgid "Replace original question, \"{0}\""
+msgstr "Substituir a pergunta original, \"{0}\""
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:178
+msgid "Save as new question"
+msgstr "Salvar como uma nova pergunta"
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:187
+msgid "First, save your question"
+msgstr "Primeiro, salve sua pergunta"
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:188
+msgid "Save question"
+msgstr "Salvar a pergunta"
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:224
+msgid "What is the name of your card?"
+msgstr "Qual é o nome do seu cartão?"
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:232
+#: frontend/src/metabase/entities/collections.js:94
+#: frontend/src/metabase/entities/dashboards.js:102
+#: frontend/src/metabase/lib/core.js:45 frontend/src/metabase/lib/core.js:200
+#: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:156
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:211
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:189
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:203
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:207
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:207
+#: frontend/src/metabase/visualizations/lib/settings.js:163
+msgid "Description"
+msgstr "Descrição"
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:238
+#: frontend/src/metabase/entities/dashboards.js:104
+msgid "It's optional but oh, so helpful"
+msgstr "É opcional, mas tão útil"
+
+#: frontend/src/metabase/containers/SaveQuestionModal.jsx:245
+#: frontend/src/metabase/entities/dashboards.js:108
+msgid "Which collection should this go in?"
+msgstr "Em que coleção isso deveria ir?"
+
+#: frontend/src/metabase/containers/UndoListing.jsx:34
+msgid "modified"
+msgstr "modificado"
+
+#: frontend/src/metabase/containers/UndoListing.jsx:34
+msgid "item"
+msgstr "item"
+
+#: frontend/src/metabase/containers/UndoListing.jsx:81
+msgid "Undo"
+msgstr "Desfazer"
+
+#: frontend/src/metabase/dashboard/components/AddSeriesModal.jsx:270
+msgid "Applying Question"
+msgstr "Aplicando Pergunta"
+
+#: frontend/src/metabase/dashboard/components/AddSeriesModal.jsx:274
+msgid "That question isn't compatible"
+msgstr "Essa pergunta não é compatível"
+
+#: frontend/src/metabase/dashboard/components/AddSeriesModal.jsx:310
+msgid "Search for a question"
+msgstr "Pesquisar uma pergunta"
+
+#: frontend/src/metabase/dashboard/components/AddSeriesModal.jsx:339
+msgid "We're not sure if this question is compatible"
+msgstr "Não temos certeza se essa pergunta é compatível"
+
+#: frontend/src/metabase/dashboard/components/ArchiveDashboardModal.jsx:43
+msgid "Archive Dashboard"
+msgstr "Arquivar Painel"
+
+#: frontend/src/metabase/dashboard/components/DashCardParameterMapper.jsx:20
+msgid "Make sure to make a selection for each series, or the filter won't work on this card."
+msgstr "Certifique-se de fazer uma seleção para cada série ou o filtro não funcionará neste cartão."
+
+#: frontend/src/metabase/dashboard/components/Dashboard.jsx:284
+msgid "This dashboard is looking empty."
+msgstr "Este painel parece estar vazio."
+
+#: frontend/src/metabase/dashboard/components/Dashboard.jsx:287
+msgid "Add a question to start making it useful!"
+msgstr "Adicione uma pergunta para torná-lo útil!"
+
+#: frontend/src/metabase/dashboard/components/DashboardActions.jsx:36
+msgid "Daytime mode"
+msgstr "Modo claro"
+
+#: frontend/src/metabase/dashboard/components/DashboardActions.jsx:36
+msgid "Nighttime mode"
+msgstr "Modo escuro"
+
+#: frontend/src/metabase/dashboard/components/DashboardActions.jsx:53
+msgid "Exit fullscreen"
+msgstr "Desativar tela cheia"
+
+#: frontend/src/metabase/dashboard/components/DashboardActions.jsx:53
+msgid "Enter fullscreen"
+msgstr "Ativar tela cheia"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:181
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:183
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:250
+msgid "Saving…"
+msgstr "Salvando..."
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:216
+msgid "Add a question"
+msgstr "Adicione uma pergunta"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:219
+msgid "Add a question to this dashboard"
+msgstr "Adicione uma pergunta a este painel"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:248
+msgid "Add a filter"
+msgstr "Adicionar filtro"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:254
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:78
+msgid "Parameters"
+msgstr "Parâmetros"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:275
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:279
+msgid "Add a text box"
+msgstr "Adicionar uma caixa de texto"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:301
+msgid "Move dashboard"
+msgstr "Mover painel"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:313
+msgid "Edit dashboard"
+msgstr "Editar painel"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:317
+msgid "Edit Dashboard Layout"
+msgstr "Editar layout do Painel"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:352
+msgid "You are editing a dashboard"
+msgstr "Você está editando um painel"
+
+#: frontend/src/metabase/dashboard/components/DashboardHeader.jsx:357
+msgid "Select the field that should be filtered for each card"
+msgstr "Selecione o campo que deve ser filtrado para cada cartão"
+
+#: frontend/src/metabase/dashboard/components/DashboardMoveModal.jsx:28
+msgid "Move dashboard to..."
+msgstr "Mover painel para..."
+
+#: frontend/src/metabase/dashboard/components/DashboardMoveModal.jsx:52
+msgid "Dashboard moved to {0}"
+msgstr "Painel movido para {0}"
+
+#: frontend/src/metabase/dashboard/components/ParametersPopover.jsx:82
+msgid "What do you want to filter?"
+msgstr "O que você deseja filtrar?"
+
+#: frontend/src/metabase/dashboard/components/ParametersPopover.jsx:115
+msgid "What kind of filter?"
+msgstr "Que tipo de filtro?"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:13
+msgid "Off"
+msgstr "Desligado"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:14
+msgid "1 minute"
+msgstr "1 minuto"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:15
+msgid "5 minutes"
+msgstr "5 minutos"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:16
+msgid "10 minutes"
+msgstr "10 minutos"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:17
+msgid "15 minutes"
+msgstr "15 minutos"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:18
+msgid "30 minutes"
+msgstr "30 minutos"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:19
+msgid "60 minutes"
+msgstr "60 minutos"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:31
+msgid "Auto-refresh"
+msgstr "Atualizar automaticamente"
+
+#: frontend/src/metabase/dashboard/components/RefreshWidget.jsx:37
+msgid "Refreshing in"
+msgstr "Atualizando em"
+
+#: frontend/src/metabase/dashboard/components/RemoveFromDashboardModal.jsx:37
+msgid "Remove this question?"
+msgstr "Remover esta pergunta?"
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:70
+msgid "Your dashboard was saved"
+msgstr "Seu painel foi salvo."
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:75
+msgid "See it"
+msgstr "Veja"
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:132
+msgid "Save this"
+msgstr "Salvar isto"
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:165
+msgid "Show more about this"
+msgstr "Moste mais sobre isso"
+
+#: frontend/src/metabase/dashboard/containers/DashCardCardParameterMapper.jsx:140
+msgid "This card doesn't have any fields or parameters that can be mapped to this parameter type."
+msgstr "Este cartão ainda não tem campos ou parâmetros que podem ser mapeados para este tipo de parâmetro."
+
+#: frontend/src/metabase/dashboard/containers/DashCardCardParameterMapper.jsx:142
+msgid "The values in this field don't overlap with the values of any other fields you've chosen."
+msgstr "Os valores nesse campo não sobrepõe os valores de outros campos que você escolheu."
+
+#: frontend/src/metabase/dashboard/containers/DashCardCardParameterMapper.jsx:186
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingFieldPicker.jsx:15
+msgid "No valid fields"
+msgstr "Não há campos válidos"
+
+#: frontend/src/metabase/entities/collections.js:90
+msgid "Name must be 100 characters or less"
+msgstr "O nome deve ter 100 caracteres ou menos"
+
+#: frontend/src/metabase/entities/collections.js:104
+msgid "Color is required"
+msgstr "Cor é obrigatório"
+
+#: frontend/src/metabase/entities/dashboards.js:97
+msgid "What is the name of your dashboard?"
+msgstr "Qual é o nome do seu painel?"
+
+#: frontend/src/metabase/home/components/Activity.jsx:92
+msgid "did some super awesome stuff that's hard to describe"
+msgstr "eu fiz algumas coisas incríveis que são difíceis de descrever"
+
+#: frontend/src/metabase/home/components/Activity.jsx:101
+#: frontend/src/metabase/home/components/Activity.jsx:116
+msgid "created an alert about - "
+msgstr "criou um alerta sobre - "
+
+#: frontend/src/metabase/home/components/Activity.jsx:126
+#: frontend/src/metabase/home/components/Activity.jsx:141
+msgid "deleted an alert about - "
+msgstr "removeu um alerta sobre - "
+
+#: frontend/src/metabase/home/components/Activity.jsx:152
+msgid "saved a question about "
+msgstr "salvou uma pergunta sobre "
+
+#: frontend/src/metabase/home/components/Activity.jsx:165
+msgid "saved a question"
+msgstr "salvou uma pergunta"
+
+#: frontend/src/metabase/home/components/Activity.jsx:169
+msgid "deleted a question"
+msgstr "removeu uma questão"
+
+#: frontend/src/metabase/home/components/Activity.jsx:172
+msgid "created a dashboard"
+msgstr "criou um painel"
+
+#: frontend/src/metabase/home/components/Activity.jsx:175
+msgid "deleted a dashboard"
+msgstr "removeu um painel"
+
+#: frontend/src/metabase/home/components/Activity.jsx:181
+#: frontend/src/metabase/home/components/Activity.jsx:196
+msgid "added a question to the dashboard - "
+msgstr "adicionou uma pergunta ao painel - "
+
+#: frontend/src/metabase/home/components/Activity.jsx:206
+#: frontend/src/metabase/home/components/Activity.jsx:221
+msgid "removed a question from the dashboard - "
+msgstr "removeu uma pergunta do painel - "
+
+#: frontend/src/metabase/home/components/Activity.jsx:231
+#: frontend/src/metabase/home/components/Activity.jsx:238
+msgid "received the latest data from"
+msgstr "recebeu os dados mais recentes de"
+
+#: frontend/src/metabase/home/components/Activity.jsx:244
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:273
+msgid "Unknown"
+msgstr "Desconhecido"
+
+#: frontend/src/metabase/home/components/Activity.jsx:251
+msgid "Hello World!"
+msgstr "Olá mundo!"
+
+#: frontend/src/metabase/home/components/Activity.jsx:252
+msgid "Metabase is up and running."
+msgstr "O Metabase está funcionando."
+
+#: frontend/src/metabase/home/components/Activity.jsx:258
+#: frontend/src/metabase/home/components/Activity.jsx:288
+msgid "added the metric "
+msgstr "adicionou a métrica "
+
+#: frontend/src/metabase/home/components/Activity.jsx:272
+#: frontend/src/metabase/home/components/Activity.jsx:362
+msgid " to the "
+msgstr " a "
+
+#: frontend/src/metabase/home/components/Activity.jsx:282
+#: frontend/src/metabase/home/components/Activity.jsx:322
+#: frontend/src/metabase/home/components/Activity.jsx:372
+#: frontend/src/metabase/home/components/Activity.jsx:413
+msgid " table"
+msgstr " tabela"
+
+#: frontend/src/metabase/home/components/Activity.jsx:298
+#: frontend/src/metabase/home/components/Activity.jsx:328
+msgid "made changes to the metric "
+msgstr "fez alterações na métrica "
+
+#: frontend/src/metabase/home/components/Activity.jsx:312
+#: frontend/src/metabase/home/components/Activity.jsx:403
+msgid " in the "
+msgstr " no "
+
+#: frontend/src/metabase/home/components/Activity.jsx:335
+msgid "removed the metric "
+msgstr "removeu a métrica "
+
+#: frontend/src/metabase/home/components/Activity.jsx:338
+msgid "created a pulse"
+msgstr "criou uma notificação"
+
+#: frontend/src/metabase/home/components/Activity.jsx:341
+msgid "deleted a pulse"
+msgstr "removeu uma notificação"
+
+#: frontend/src/metabase/home/components/Activity.jsx:347
+#: frontend/src/metabase/home/components/Activity.jsx:378
+msgid "added the filter"
+msgstr "adicionou o filtro"
+
+#: frontend/src/metabase/home/components/Activity.jsx:388
+#: frontend/src/metabase/home/components/Activity.jsx:419
+msgid "made changes to the filter"
+msgstr "fez alterações no filtro"
+
+#: frontend/src/metabase/home/components/Activity.jsx:426
+msgid "removed the filter {0}"
+msgstr "filtro removido {0}"
+
+#: frontend/src/metabase/home/components/Activity.jsx:429
+msgid "joined!"
+msgstr "juntou-se!"
+
+#: frontend/src/metabase/home/components/Activity.jsx:529
+msgid "Hmmm, looks like nothing has happened yet."
+msgstr "Hmmm, parece que nada aconteceu ainda."
+
+#: frontend/src/metabase/home/components/Activity.jsx:532
+msgid "Save a question and get this baby going!"
+msgstr "Salve uma pergunta e faça esse trabalho!"
+
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:19
+msgid "Ask questions and explore"
+msgstr "Faça perguntas e explore"
+
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:20
+msgid "Click on charts or tables to explore, or ask a new question using the easy interface or the powerful SQL editor."
+msgstr "Clique nas caixas ou tabelas para explorar ou faça uma nova pergunta usando a interface simples ou o poderoso editor de SQL."
+
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:30
+msgid "Make your own charts"
+msgstr "Crie seus próprios gráficos"
+
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:31
+msgid "Create line charts, scatter plots, maps, and more."
+msgstr "Crie gráficos de linhas, gráficos de dispersão, mapas e muito mais."
+
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:41
+msgid "Share what you find"
+msgstr "Compartilhe o que você encontrar"
+
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:42
+msgid "Create powerful and flexible dashboards, and send regular updates via email or Slack."
+msgstr "Crie painéis poderosos e flexíveis e envie atualizações periodicamente por e-mail ou Slack."
+
+#: frontend/src/metabase/home/components/NewUserOnboardingModal.jsx:97
+msgid "Let's go"
+msgstr "Vamos lá"
+
+#: frontend/src/metabase/home/components/NextStep.jsx:34
+msgid "Setup Tip"
+msgstr "Conselho de configuração"
+
+#: frontend/src/metabase/home/components/NextStep.jsx:40
+msgid "View all"
+msgstr "Ver todos"
+
+#: frontend/src/metabase/home/components/RecentViews.jsx:40
+msgid "Recently Viewed"
+msgstr "Visualizado recentemente"
+
+#: frontend/src/metabase/home/components/RecentViews.jsx:75
+msgid "You haven't looked at any dashboards or questions recently"
+msgstr "Você não olhou nenhum painel ou pergunta recentemente"
+
+#: frontend/src/metabase/home/containers/ArchiveApp.jsx:82
+msgid "{0} items selected"
+msgstr "{0} itens selecionados"
+
+#: frontend/src/metabase/home/containers/ArchiveApp.jsx:102
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:172
+msgid "Unarchive"
+msgstr "Desarquivar"
+
+#: frontend/src/metabase/home/containers/HomepageApp.jsx:74
+#: frontend/src/metabase/nav/containers/Navbar.jsx:327
+msgid "Activity"
+msgstr "Atividade"
+
+#: frontend/src/metabase/home/containers/SearchApp.jsx:28
+msgid "Results for \"{0}\""
+msgstr "Resultados para \"{0}\""
+
+#: frontend/src/metabase/home/containers/SearchApp.jsx:142
+msgid "Pulse"
+msgstr "Notificação"
+
+#: frontend/src/metabase/lib/core.js:7
+msgid "Entity Key"
+msgstr "Chave de entidade"
+
+#: frontend/src/metabase/lib/core.js:8 frontend/src/metabase/lib/core.js:14
+#: frontend/src/metabase/lib/core.js:20
+msgid "Overall Row"
+msgstr "Linha Geral"
+
+#: frontend/src/metabase/lib/core.js:9
+msgid "The primary key for this table."
+msgstr "A chave principal desta tabela."
+
+#: frontend/src/metabase/lib/core.js:13
+msgid "Entity Name"
+msgstr "Nome da Entidade"
+
+#: frontend/src/metabase/lib/core.js:15
+msgid "The \"name\" of each record. Usually a column called \"name\", \"title\", etc."
+msgstr "O \"nome\" de cada registro. Normalmente, uma coluna chamada \"nome\", \"título\", etc."
+
+#: frontend/src/metabase/lib/core.js:19
+msgid "Foreign Key"
+msgstr "Chave estrangeira"
+
+#: frontend/src/metabase/lib/core.js:21
+msgid "Points to another table to make a connection."
+msgstr "Consulte outra tabela para estabelecer uma conexão."
+
+#: frontend/src/metabase/lib/core.js:25
+msgid "Avatar Image URL"
+msgstr "URL do avatar"
+
+#: frontend/src/metabase/lib/core.js:26 frontend/src/metabase/lib/core.js:31
+#: frontend/src/metabase/lib/core.js:36 frontend/src/metabase/lib/core.js:41
+#: frontend/src/metabase/lib/core.js:46 frontend/src/metabase/lib/core.js:51
+#: frontend/src/metabase/lib/core.js:56 frontend/src/metabase/lib/core.js:61
+#: frontend/src/metabase/lib/core.js:66 frontend/src/metabase/lib/core.js:71
+#: frontend/src/metabase/lib/core.js:76 frontend/src/metabase/lib/core.js:81
+#: frontend/src/metabase/lib/core.js:86 frontend/src/metabase/lib/core.js:91
+#: frontend/src/metabase/lib/core.js:96 frontend/src/metabase/lib/core.js:101
+#: frontend/src/metabase/lib/core.js:106 frontend/src/metabase/lib/core.js:111
+#: frontend/src/metabase/lib/core.js:116 frontend/src/metabase/lib/core.js:121
+#: frontend/src/metabase/lib/core.js:126 frontend/src/metabase/lib/core.js:131
+#: frontend/src/metabase/lib/core.js:136 frontend/src/metabase/lib/core.js:141
+#: frontend/src/metabase/lib/core.js:146 frontend/src/metabase/lib/core.js:151
+#: frontend/src/metabase/lib/core.js:156 frontend/src/metabase/lib/core.js:161
+#: frontend/src/metabase/lib/core.js:166 frontend/src/metabase/lib/core.js:171
+#: frontend/src/metabase/lib/core.js:176 frontend/src/metabase/lib/core.js:181
+#: frontend/src/metabase/lib/core.js:186 frontend/src/metabase/lib/core.js:191
+#: frontend/src/metabase/lib/core.js:196 frontend/src/metabase/lib/core.js:201
+#: frontend/src/metabase/lib/core.js:206 frontend/src/metabase/lib/core.js:211
+#: frontend/src/metabase/lib/core.js:216 frontend/src/metabase/lib/core.js:221
+#: frontend/src/metabase/lib/core.js:226
+msgid "Common"
+msgstr "Comum"
+
+#: frontend/src/metabase/lib/core.js:30
+#: frontend/src/metabase/meta/Dashboard.js:82
+#: frontend/src/metabase/qb/components/actions/PivotByCategoryAction.jsx:9
+msgid "Category"
+msgstr "Categoria"
+
+#: frontend/src/metabase/lib/core.js:35
+#: frontend/src/metabase/meta/Dashboard.js:62
+msgid "City"
+msgstr "Cidade"
+
+#: frontend/src/metabase/lib/core.js:40
+#: frontend/src/metabase/meta/Dashboard.js:74
+msgid "Country"
+msgstr "Country"
+
+#: frontend/src/metabase/lib/core.js:55
+msgid "Enum"
+msgstr "Enum"
+
+#: frontend/src/metabase/lib/core.js:60
+msgid "Image URL"
+msgstr "URL de Imagem"
+
+#: frontend/src/metabase/lib/core.js:65
+msgid "Field containing JSON"
+msgstr "Campo que contém JSON"
+
+#: frontend/src/metabase/lib/core.js:70
+msgid "Latitude"
+msgstr "Latitude"
+
+#: frontend/src/metabase/lib/core.js:75
+msgid "Longitude"
+msgstr "Longitude"
+
+#: frontend/src/metabase/lib/core.js:80
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:146
+#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:22
+msgid "Number"
+msgstr "Número"
+
+#: frontend/src/metabase/lib/core.js:85
+#: frontend/src/metabase/meta/Dashboard.js:66
+msgid "State"
+msgstr "Estado"
+
+#: frontend/src/metabase/lib/core.js:90
+msgid "UNIX Timestamp (Seconds)"
+msgstr "Data e Hora em formato UNIX Timestamp (segundos)"
+
+#: frontend/src/metabase/lib/core.js:95
+msgid "UNIX Timestamp (Milliseconds)"
+msgstr "Data e Hora em formato UNIX Timestamp (milissegundos)"
+
+#: frontend/src/metabase/lib/core.js:105
+msgid "Zip Code"
+msgstr "Código postal"
+
+#: frontend/src/metabase/lib/core.js:110
+msgid "Quantity"
+msgstr "Quantidade"
+
+#: frontend/src/metabase/lib/core.js:115
+msgid "Income"
+msgstr "Renda"
+
+#: frontend/src/metabase/lib/core.js:120
+msgid "Discount"
+msgstr "Desconto"
+
+#: frontend/src/metabase/lib/core.js:125
+msgid "Creation timestamp"
+msgstr "Data e hora de criação"
+
+#: frontend/src/metabase/lib/core.js:130
+msgid "Creation time"
+msgstr "Hora de criação"
+
+#: frontend/src/metabase/lib/core.js:135
+msgid "Creation date"
+msgstr "Data de criação"
+
+#: frontend/src/metabase/lib/core.js:140
+msgid "Product"
+msgstr "Produto"
+
+#: frontend/src/metabase/lib/core.js:145
+msgid "User"
+msgstr "Usuário"
+
+#: frontend/src/metabase/lib/core.js:150
+msgid "Source"
+msgstr "Fonte"
+
+#: frontend/src/metabase/lib/core.js:155
+msgid "Price"
+msgstr "Preço"
+
+#: frontend/src/metabase/lib/core.js:160
+msgid "Join timestamp"
+msgstr "Data e hora do registro"
+
+#: frontend/src/metabase/lib/core.js:165
+msgid "Join time"
+msgstr "Hora do registro"
+
+#: frontend/src/metabase/lib/core.js:170
+msgid "Join date"
+msgstr "Data do registro"
+
+#: frontend/src/metabase/lib/core.js:175
+msgid "Share"
+msgstr "Compartilhar"
+
+#: frontend/src/metabase/lib/core.js:180
+msgid "Owner"
+msgstr "Proprietário"
+
+#: frontend/src/metabase/lib/core.js:185
+msgid "Company"
+msgstr "Negócio"
+
+#: frontend/src/metabase/lib/core.js:190
+msgid "Subscription"
+msgstr "Assinatura"
+
+#: frontend/src/metabase/lib/core.js:195
+msgid "Score"
+msgstr "Pontuação"
+
+#: frontend/src/metabase/lib/core.js:205
+#: frontend/src/metabase/public/components/widgets/DisplayOptionsPane.jsx:49
+#: frontend/src/metabase/visualizations/lib/settings.js:156
+msgid "Title"
+msgstr "Título"
+
+#: frontend/src/metabase/lib/core.js:210
+msgid "Comment"
+msgstr "Comentário"
+
+#: frontend/src/metabase/lib/core.js:215
+msgid "Cost"
+msgstr "Custo"
+
+#: frontend/src/metabase/lib/core.js:220
+msgid "Gross margin"
+msgstr "Margem bruta"
+
+#: frontend/src/metabase/lib/core.js:225
+msgid "Birthday"
+msgstr "Aniversário"
+
+#: frontend/src/metabase/lib/core.js:236
+msgid "Search box"
+msgstr "Caixa de pesquisa"
+
+#: frontend/src/metabase/lib/core.js:237
+msgid "A list of all values"
+msgstr "Uma lista com todos os valores"
+
+#: frontend/src/metabase/lib/core.js:238
+msgid "Plain input box"
+msgstr "Caixa de texto"
+
+#: frontend/src/metabase/lib/core.js:244
+msgid "Everywhere"
+msgstr "Em todas as partes"
+
+#: frontend/src/metabase/lib/core.js:245
+msgid "The default setting. This field will be displayed normally in tables and charts."
+msgstr "A configuração padrão. Este campo será exibido normalmente em tabelas e gráficos."
+
+#: frontend/src/metabase/lib/core.js:249
+msgid "Only in Detail Views"
+msgstr "Apenas em vistas detalhadas"
+
+#: frontend/src/metabase/lib/core.js:250
+msgid "This field will only be displayed when viewing the details of a single record. Use this for information that's lengthy or that isn't useful in a table or chart."
+msgstr "Este campo só será exibido quando você ver os detalhes de um único registro. Use-o para obter informações longas ou que não sejam úteis em uma tabela ou graphic"
+
+#: frontend/src/metabase/lib/core.js:254
+msgid "Do Not Include"
+msgstr "Não incluir"
+
+#: frontend/src/metabase/lib/core.js:255
+msgid "Metabase will never retrieve this field. Use this for sensitive or irrelevant information."
+msgstr "O Metabase nunca recuperará este campo. Use-o para informações confidenciais ou irrelevante"
+
+#: frontend/src/metabase/lib/expressions/config.js:7
+#: frontend/src/metabase/lib/query.js:615
+#: frontend/src/metabase/visualizations/lib/utils.js:113
+msgid "Count"
+msgstr "Contagem"
+
+#: frontend/src/metabase/lib/expressions/config.js:8
+msgid "CumulativeCount"
+msgstr "Contagem cumulativa"
+
+#: frontend/src/metabase/lib/expressions/config.js:9
+#: frontend/src/metabase/qb/components/drill/SummarizeColumnDrill.js:17
+#: frontend/src/metabase/visualizations/lib/utils.js:114
+msgid "Sum"
+msgstr "Soma"
+
+#: frontend/src/metabase/lib/expressions/config.js:10
+msgid "CumulativeSum"
+msgstr "Soma cumulativa"
+
+#: frontend/src/metabase/lib/expressions/config.js:11
+#: frontend/src/metabase/visualizations/lib/utils.js:115
+msgid "Distinct"
+msgstr "Diferente"
+
+#: frontend/src/metabase/lib/expressions/config.js:12
+msgid "StandardDeviation"
+msgstr "Desvio padrão"
+
+#: frontend/src/metabase/lib/expressions/config.js:13
+#: frontend/src/metabase/visualizations/lib/utils.js:112
+msgid "Average"
+msgstr "Média"
+
+#: frontend/src/metabase/lib/expressions/config.js:14
+#: frontend/src/metabase/qb/components/drill/SummarizeColumnDrill.js:25
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:363
+msgid "Min"
+msgstr "Mínimo"
+
+#: frontend/src/metabase/lib/expressions/config.js:15
+#: frontend/src/metabase/qb/components/drill/SummarizeColumnDrill.js:29
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:371
+msgid "Max"
+msgstr "Máximo"
+
+#: frontend/src/metabase/lib/expressions/parser.js:384
+msgid "sad sad panda, lexing errors detected"
+msgstr "Panda triste triste, erros lexicais detectados"
+
+#: frontend/src/metabase/lib/formatting.js:447
+msgid "{0} second"
+msgid_plural "{0} seconds"
+msgstr[0] "{0} segundo"
+msgstr[1] "{0} segundos"
+
+#: frontend/src/metabase/lib/formatting.js:450
+msgid "{0} minute"
+msgid_plural "{0} minutes"
+msgstr[0] "{0} minuto"
+msgstr[1] "{0} minutos"
+
+#: frontend/src/metabase/lib/greeting.js:4
+msgid "Hey there"
+msgstr "Olá"
+
+#: frontend/src/metabase/lib/greeting.js:5
+#: frontend/src/metabase/lib/greeting.js:29
+msgid "How's it going"
+msgstr "Como vai?"
+
+#: frontend/src/metabase/lib/greeting.js:6
+msgid "Howdy"
+msgstr "Olá"
+
+#: frontend/src/metabase/lib/greeting.js:7
+msgid "Greetings"
+msgstr "Saudações"
+
+#: frontend/src/metabase/lib/greeting.js:8
+msgid "Good to see you"
+msgstr "Que bom te ver"
+
+#: frontend/src/metabase/lib/greeting.js:12
+msgid "What do you want to know?"
+msgstr "O que você quer saber?"
+
+#: frontend/src/metabase/lib/greeting.js:13
+msgid "What's on your mind?"
+msgstr "Que tem em mente?"
+
+#: frontend/src/metabase/lib/greeting.js:14
+msgid "What do you want to find out?"
+msgstr "O que você quer saber?"
+
+#: frontend/src/metabase/lib/query.js:613
+#: frontend/src/metabase/lib/schema_metadata.js:441
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:246
+msgid "Raw data"
+msgstr "Dados brutos"
+
+#: frontend/src/metabase/lib/query.js:617
+msgid "Cumulative count"
+msgstr "Contagem cumulativa"
+
+#: frontend/src/metabase/lib/query.js:620
+msgid "Average of "
+msgstr "Média de "
+
+#: frontend/src/metabase/lib/query.js:625
+msgid "Distinct values of "
+msgstr "Valores que não sejam "
+
+#: frontend/src/metabase/lib/query.js:630
+msgid "Standard deviation of "
+msgstr "Desvio padrão de "
+
+#: frontend/src/metabase/lib/query.js:635
+msgid "Sum of "
+msgstr "Soma de "
+
+#: frontend/src/metabase/lib/query.js:640
+msgid "Cumulative sum of "
+msgstr "Soma cumulativa de "
+
+#: frontend/src/metabase/lib/query.js:645
+msgid "Maximum of "
+msgstr "Máximo de "
+
+#: frontend/src/metabase/lib/query.js:650
+msgid "Minimum of "
+msgstr "Mínimo de "
+
+#: frontend/src/metabase/lib/query.js:664
+msgid "Grouped by "
+msgstr "Agrupado por "
+
+#: frontend/src/metabase/lib/query.js:678
+msgid "Filtered by "
+msgstr "Filtrado por "
+
+#: frontend/src/metabase/lib/query.js:706
+msgid "Sorted by "
+msgstr "Ordenado por "
+
+#: frontend/src/metabase/lib/schema_metadata.js:211
+msgid "True"
+msgstr "Verdadeiro"
+
+#: frontend/src/metabase/lib/schema_metadata.js:211
+msgid "False"
+msgstr "Falso"
+
+#: frontend/src/metabase/lib/schema_metadata.js:295
+msgid "Select longitude field"
+msgstr "Selecione o campo longitude"
+
+#: frontend/src/metabase/lib/schema_metadata.js:296
+msgid "Enter upper latitude"
+msgstr "Latitude Superior"
+
+#: frontend/src/metabase/lib/schema_metadata.js:297
+msgid "Enter left longitude"
+msgstr "Longitude Esquerdo"
+
+#: frontend/src/metabase/lib/schema_metadata.js:298
+msgid "Enter lower latitude"
+msgstr "Latitude Inferior"
+
+#: frontend/src/metabase/lib/schema_metadata.js:299
+msgid "Enter right longitude"
+msgstr "Longitude certo"
+
+#: frontend/src/metabase/lib/schema_metadata.js:335
+#: frontend/src/metabase/lib/schema_metadata.js:355
+#: frontend/src/metabase/lib/schema_metadata.js:365
+#: frontend/src/metabase/lib/schema_metadata.js:371
+#: frontend/src/metabase/lib/schema_metadata.js:379
+#: frontend/src/metabase/lib/schema_metadata.js:385
+#: frontend/src/metabase/lib/schema_metadata.js:390
+msgid "Is"
+msgstr "É"
+
+#: frontend/src/metabase/lib/schema_metadata.js:336
+#: frontend/src/metabase/lib/schema_metadata.js:356
+#: frontend/src/metabase/lib/schema_metadata.js:366
+#: frontend/src/metabase/lib/schema_metadata.js:380
+#: frontend/src/metabase/lib/schema_metadata.js:386
+msgid "Is not"
+msgstr "Não é"
+
+#: frontend/src/metabase/lib/schema_metadata.js:337
+#: frontend/src/metabase/lib/schema_metadata.js:351
+#: frontend/src/metabase/lib/schema_metadata.js:359
+#: frontend/src/metabase/lib/schema_metadata.js:367
+#: frontend/src/metabase/lib/schema_metadata.js:375
+#: frontend/src/metabase/lib/schema_metadata.js:381
+#: frontend/src/metabase/lib/schema_metadata.js:391
+msgid "Is empty"
+msgstr "Vazio"
+
+#: frontend/src/metabase/lib/schema_metadata.js:338
+#: frontend/src/metabase/lib/schema_metadata.js:352
+#: frontend/src/metabase/lib/schema_metadata.js:360
+#: frontend/src/metabase/lib/schema_metadata.js:368
+#: frontend/src/metabase/lib/schema_metadata.js:376
+#: frontend/src/metabase/lib/schema_metadata.js:382
+#: frontend/src/metabase/lib/schema_metadata.js:392
+msgid "Not empty"
+msgstr "Não vazio"
+
+#: frontend/src/metabase/lib/schema_metadata.js:344
+msgid "Equal to"
+msgstr "Igual à"
+
+#: frontend/src/metabase/lib/schema_metadata.js:345
+msgid "Not equal to"
+msgstr "Diferente"
+
+#: frontend/src/metabase/lib/schema_metadata.js:346
+msgid "Greater than"
+msgstr "Maior do que"
+
+#: frontend/src/metabase/lib/schema_metadata.js:347
+msgid "Less than"
+msgstr "Menos que"
+
+#: frontend/src/metabase/lib/schema_metadata.js:348
+#: frontend/src/metabase/lib/schema_metadata.js:374
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:289
+#: frontend/src/metabase/query_builder/components/filters/pickers/TimePicker.jsx:96
+msgid "Between"
+msgstr "Entre"
+
+#: frontend/src/metabase/lib/schema_metadata.js:349
+msgid "Greater than or equal to"
+msgstr "Maior que ou igual a"
+
+#: frontend/src/metabase/lib/schema_metadata.js:350
+msgid "Less than or equal to"
+msgstr "Menor ou igual a"
+
+#: frontend/src/metabase/lib/schema_metadata.js:357
+msgid "Contains"
+msgstr "Contém"
+
+#: frontend/src/metabase/lib/schema_metadata.js:358
+msgid "Does not contain"
+msgstr "Não contém"
+
+#: frontend/src/metabase/lib/schema_metadata.js:361
+msgid "Starts with"
+msgstr "Comece com"
+
+#: frontend/src/metabase/lib/schema_metadata.js:362
+msgid "Ends with"
+msgstr "Termina em"
+
+#: frontend/src/metabase/lib/schema_metadata.js:372
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:268
+#: frontend/src/metabase/query_builder/components/filters/pickers/TimePicker.jsx:74
+msgid "Before"
+msgstr "Antes"
+
+#: frontend/src/metabase/lib/schema_metadata.js:373
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:275
+#: frontend/src/metabase/query_builder/components/filters/pickers/TimePicker.jsx:85
+msgid "After"
+msgstr "Depois"
+
+#: frontend/src/metabase/lib/schema_metadata.js:387
+msgid "Inside"
+msgstr "Dentro"
+
+#: frontend/src/metabase/lib/schema_metadata.js:443
+msgid "Just a table with the rows in the answer, no additional operations."
+msgstr "Apenas uma tabela com as linhas na resposta, sem operações adicionais."
+
+#: frontend/src/metabase/lib/schema_metadata.js:449
+msgid "Count of rows"
+msgstr "Número de linhas"
+
+#: frontend/src/metabase/lib/schema_metadata.js:451
+msgid "Total number of rows in the answer."
+msgstr "Número total de linhas na resposta."
+
+#: frontend/src/metabase/lib/schema_metadata.js:457
+msgid "Sum of ..."
+msgstr "Soma de ..."
+
+#: frontend/src/metabase/lib/schema_metadata.js:459
+msgid "Sum of all the values of a column."
+msgstr "Soma de todos os valores de uma coluna."
+
+#. Acho que a agregação de "Medium of.... " está ficando errada, pois traduz em 
+#. "Média de... de" mas vou deixar a tradução assim por enquanto
+#: frontend/src/metabase/lib/schema_metadata.js:465
+msgid "Average of ..."
+msgstr "Média de ..."
+
+#: frontend/src/metabase/lib/schema_metadata.js:467
+msgid "Average of all the values of a column"
+msgstr "Média de todos os valores em uma coluna"
+
+#: frontend/src/metabase/lib/schema_metadata.js:473
+msgid "Number of distinct values of ..."
+msgstr "Número de valores diferentes de ..."
+
+#: frontend/src/metabase/lib/schema_metadata.js:475
+msgid "Number of unique values of a column among all the rows in the answer."
+msgstr "Número de valores exclusivos de uma coluna entre todas as linhas na resposta"
+
+#: frontend/src/metabase/lib/schema_metadata.js:481
+msgid "Cumulative sum of ..."
+msgstr "Soma acumulada de ..."
+
+#: frontend/src/metabase/lib/schema_metadata.js:483
+msgid "Additive sum of all the values of a column.\\ne.x. total revenue over time."
+msgstr "Soma aditiva de todos os valores em uma coluna. \n"
+" por exemplo, receita total ao longo do tempo"
+
+#: frontend/src/metabase/lib/schema_metadata.js:489
+msgid "Cumulative count of rows"
+msgstr "Contagem cumulativa de linhas"
+
+#: frontend/src/metabase/lib/schema_metadata.js:491
+msgid "Additive count of the number of rows.\\ne.x. total number of sales over time."
+msgstr "Contagem aditiva do número de linhas. \n"
+" por exemplo, número total de vendas ao longo do tempo"
+
+#: frontend/src/metabase/lib/schema_metadata.js:497
+msgid "Standard deviation of ..."
+msgstr "Desvio padrão de ..."
+
+#: frontend/src/metabase/lib/schema_metadata.js:499
+msgid "Number which expresses how much the values of a column vary among all rows in the answer."
+msgstr "Número que expressa o quanto os valores de uma coluna variam entre todos os linhas na resposta."
+
+#: frontend/src/metabase/lib/schema_metadata.js:505
+msgid "Minimum of ..."
+msgstr "Mínimo de ..."
+
+#: frontend/src/metabase/lib/schema_metadata.js:507
+msgid "Minimum value of a column"
+msgstr "Valor Mínimo de uma Coluna"
+
+#: frontend/src/metabase/lib/schema_metadata.js:513
+msgid "Maximum of ..."
+msgstr "Máximo de ..."
+
+#: frontend/src/metabase/lib/schema_metadata.js:515
+msgid "Maximum value of a column"
+msgstr "Valor máximo de uma coluna"
+
+#: frontend/src/metabase/lib/schema_metadata.js:523
+msgid "Break out by dimension"
+msgstr "Divida por dimensão"
+
+#: frontend/src/metabase/lib/settings.js:93
+msgid "lower case letter"
+msgstr "letra minúscula"
+
+#: frontend/src/metabase/lib/settings.js:95
+msgid "upper case letter"
+msgstr "letra maiúscula"
+
+#: frontend/src/metabase/lib/settings.js:97
+#: src/metabase/automagic_dashboards/core.clj
+msgid "number"
+msgstr "número"
+
+#: frontend/src/metabase/lib/settings.js:99
+msgid "special character"
+msgstr "caractere especial"
+
+#: frontend/src/metabase/lib/settings.js:105
+msgid "must be"
+msgstr "deve ser"
+
+#: frontend/src/metabase/lib/settings.js:105
+#: frontend/src/metabase/lib/settings.js:106
+msgid "characters long"
+msgstr "quantidade de caractere"
+
+#: frontend/src/metabase/lib/settings.js:106
+msgid "Must be"
+msgstr "Deve ser"
+
+#: frontend/src/metabase/lib/settings.js:122
+msgid "and include"
+msgstr "e incluir"
+
+#: frontend/src/metabase/lib/utils.js:92
+msgid "zero"
+msgstr "zero"
+
+#: frontend/src/metabase/lib/utils.js:93
+msgid "one"
+msgstr "um"
+
+#: frontend/src/metabase/lib/utils.js:94
+msgid "two"
+msgstr "dois"
+
+#: frontend/src/metabase/lib/utils.js:95
+msgid "three"
+msgstr "três"
+
+#: frontend/src/metabase/lib/utils.js:96
+msgid "four"
+msgstr "quatro"
+
+#: frontend/src/metabase/lib/utils.js:97
+msgid "five"
+msgstr "cinco"
+
+#: frontend/src/metabase/lib/utils.js:98
+msgid "six"
+msgstr "seis"
+
+#: frontend/src/metabase/lib/utils.js:99
+msgid "seven"
+msgstr "sete"
+
+#: frontend/src/metabase/lib/utils.js:100
+msgid "eight"
+msgstr "oito"
+
+#: frontend/src/metabase/lib/utils.js:101
+msgid "nine"
+msgstr "nove"
+
+#: frontend/src/metabase/meta/Dashboard.js:31
+msgid "Month and Year"
+msgstr "Mês e Ano"
+
+#: frontend/src/metabase/meta/Dashboard.js:32
+msgid "Like January, 2016"
+msgstr "Como janeiro de 2016"
+
+#: frontend/src/metabase/meta/Dashboard.js:36
+msgid "Quarter and Year"
+msgstr "Trimestre e ano"
+
+#: frontend/src/metabase/meta/Dashboard.js:37
+msgid "Like Q1, 2016"
+msgstr "Como T1, 2016"
+
+#: frontend/src/metabase/meta/Dashboard.js:41
+msgid "Single Date"
+msgstr "Data Única"
+
+#: frontend/src/metabase/meta/Dashboard.js:42
+msgid "Like January 31, 2016"
+msgstr "Em 31 de janeiro de 2016"
+
+#: frontend/src/metabase/meta/Dashboard.js:46
+msgid "Date Range"
+msgstr "Faixa de datas"
+
+#: frontend/src/metabase/meta/Dashboard.js:47
+msgid "Like December 25, 2015 - February 14, 2016"
+msgstr "De 25 de dezembro de 2015 a 14 de fevereiro de 2016"
+
+#: frontend/src/metabase/meta/Dashboard.js:51
+msgid "Relative Date"
+msgstr "Data Relativa"
+
+#: frontend/src/metabase/meta/Dashboard.js:52
+msgid "Like \"the last 7 days\" or \"this month\""
+msgstr "Como \"os últimos 7 dias\" ou \"este mês\""
+
+#: frontend/src/metabase/meta/Dashboard.js:56
+msgid "Date Filter"
+msgstr "Filtro de data"
+
+#: frontend/src/metabase/meta/Dashboard.js:57
+msgid "All Options"
+msgstr "Todas as opções"
+
+#: frontend/src/metabase/meta/Dashboard.js:58
+msgid "Contains all of the above"
+msgstr "Ele contém todos os itens acima"
+
+#: frontend/src/metabase/meta/Dashboard.js:70
+msgid "ZIP or Postal Code"
+msgstr "Código postal"
+
+#: frontend/src/metabase/meta/Dashboard.js:78
+#: frontend/src/metabase/meta/Dashboard.js:108
+msgid "ID"
+msgstr "ID"
+
+#: frontend/src/metabase/meta/Dashboard.js:96
+#: frontend/src/metabase/qb/components/actions/PivotByTimeAction.jsx:8
+msgid "Time"
+msgstr "Tempo"
+
+#: frontend/src/metabase/meta/Dashboard.js:97
+msgid "Date range, relative date, time of day, etc."
+msgstr "Intervalo de datas, data relativa, hora do dia, etc."
+
+#: frontend/src/metabase/meta/Dashboard.js:102
+#: frontend/src/metabase/qb/components/actions/PivotByLocationAction.jsx:8
+msgid "Location"
+msgstr "Localização"
+
+#: frontend/src/metabase/meta/Dashboard.js:103
+msgid "City, State, Country, ZIP code."
+msgstr "Cidade, província, país, código postal."
+
+#: frontend/src/metabase/meta/Dashboard.js:109
+msgid "User ID, product ID, event ID, etc."
+msgstr "ID do usuário, ID do produto, ID do evento, etc."
+
+#: frontend/src/metabase/meta/Dashboard.js:114
+msgid "Other Categories"
+msgstr "Outras categorias"
+
+#: frontend/src/metabase/meta/Dashboard.js:115
+msgid "Category, Type, Model, Rating, etc."
+msgstr "Categoria, Tipo, Modelo, Classificação, etc."
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:43
+#: frontend/src/metabase/user/components/UserSettings.jsx:54
+msgid "Account settings"
+msgstr "Configuração da conta"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:50
+msgid "Exit admin"
+msgstr "Sair do admin"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:60
+msgid "Logs"
+msgstr "Logs"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:67
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorSidebar.jsx:105
+msgid "Help"
+msgstr "Ajuda"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:76
+msgid "About Metabase"
+msgstr "Sobre o Metabase"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:82
+msgid "Sign out"
+msgstr "Sair"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:107
+msgid "Thanks for using"
+msgstr "Obrigado por usar"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:111
+msgid "You're on version"
+msgstr "Você está na versão"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:114
+msgid "Built on"
+msgstr "Construído em"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:133
+msgid "is a Trademark of"
+msgstr "é uma marca registrada da"
+
+#: frontend/src/metabase/nav/components/ProfileLink.jsx:135
+msgid "and is built with care in San Francisco, CA"
+msgstr "e é construído com amor em São Francisco, CA"
+
+#: frontend/src/metabase/nav/containers/Navbar.jsx:195
+msgid "Metabase Admin"
+msgstr "Administrador do Metabase"
+
+#: frontend/src/metabase/nav/containers/Navbar.jsx:296
+#: frontend/src/metabase/reference/databases/TableQuestions.jsx:36
+#: frontend/src/metabase/reference/metrics/MetricQuestions.jsx:37
+#: frontend/src/metabase/reference/segments/SegmentQuestions.jsx:37
+msgid "Ask a question"
+msgstr "Fazer uma pergunta"
+
+#: frontend/src/metabase/nav/containers/Navbar.jsx:305
+msgid "New dashboard"
+msgstr "Novo painel"
+
+#: frontend/src/metabase/nav/containers/Navbar.jsx:311
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:127
+msgid "New pulse"
+msgstr "Nova notificação"
+
+#: frontend/src/metabase/nav/containers/Navbar.jsx:319
+msgid "Reference"
+msgstr "Referência"
+
+#: frontend/src/metabase/new_query/containers/MetricSearch.jsx:83
+msgid "Which metric?"
+msgstr "Qual métrica?"
+
+#: frontend/src/metabase/new_query/containers/MetricSearch.jsx:110
+#: frontend/src/metabase/reference/metrics/MetricList.jsx:24
+msgid "Defining common metrics for your team makes it even easier to ask questions"
+msgstr "Definir métricas comuns para sua equipe facilita ainda mais fazer perguntas"
+
+#: frontend/src/metabase/new_query/containers/MetricSearch.jsx:113
+msgid "How to create metrics"
+msgstr "Como criar métricas"
+
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:138
+msgid "See data over time, as a map, or pivoted to help you understand trends or changes."
+msgstr "Verifique os dados ao longo do tempo, como um mapa, ou gire-os para ajudar você a entender tendências ou mudanças."
+
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:149
+msgid "Custom"
+msgstr "Personalizado"
+
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:150
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:517
+msgid "New question"
+msgstr "Nova pergunta"
+
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:152
+msgid "Use the simple question builder to see trends, lists of things, or to create your own metrics."
+msgstr "Use o gerador de perguntas para ver tendências, listas de coisas ou para criar suas próprias métricas."
+
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:161
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Native query"
+msgstr "Consulta nativa"
+
+#: frontend/src/metabase/new_query/containers/NewQueryOptions.jsx:162
+msgid "For more complicated questions, you can write your own SQL or native query."
+msgstr "Para perguntas mais complicadas, você pode escrever sua própria consulta SQL ou nativo"
+
+#: frontend/src/metabase/parameters/components/ParameterValueWidget.jsx:240
+msgid "Select a default value…"
+msgstr "Selecione um valor padrão..."
+
+#: frontend/src/metabase/parameters/components/widgets/DateAllOptionsWidget.jsx:149
+#: frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx:390
+msgid "Update filter"
+msgstr "Atualizar filtro"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:9
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:144
+msgid "Today"
+msgstr "Hoje"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:14
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:148
+msgid "Yesterday"
+msgstr "Ontem"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:18
+msgid "Past 7 days"
+msgstr "Últimos 7 dias"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:19
+msgid "Past 30 days"
+msgstr "Últimos 30 dias"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:24
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:29
+#: src/metabase/api/table.clj
+msgid "Week"
+msgstr "Semana"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:25
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:30
+#: src/metabase/api/table.clj
+msgid "Month"
+msgstr "Mês"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:26
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:31
+#: src/metabase/api/table.clj
+msgid "Year"
+msgstr "Ano"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:152
+msgid "Past 7 Days"
+msgstr "Últimos 7 dias"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:156
+msgid "Past 30 Days"
+msgstr "Últimos 30 dias"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:160
+msgid "Last Week"
+msgstr "Semana passada"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:164
+msgid "Last Month"
+msgstr "Mês passado"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:168
+msgid "Last Year"
+msgstr "Ano passado"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:172
+msgid "This Week"
+msgstr "Esta semana"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:176
+msgid "This Month"
+msgstr "Este mês"
+
+#: frontend/src/metabase/parameters/components/widgets/DateRelativeWidget.jsx:180
+msgid "This Year"
+msgstr "Este ano"
+
+#: frontend/src/metabase/parameters/components/widgets/ParameterFieldWidget.jsx:88
+#: frontend/src/metabase/parameters/components/widgets/TextWidget.jsx:54
+msgid "Enter a value..."
+msgstr "Digite um valor..."
+
+#: frontend/src/metabase/parameters/components/widgets/TextWidget.jsx:90
+msgid "Enter a default value..."
+msgstr "Digite um valor padrão..."
+
+#: frontend/src/metabase/public/components/PublicError.jsx:18
+msgid "An error occurred"
+msgstr "Ocorreu um erro"
+
+#: frontend/src/metabase/public/components/PublicNotFound.jsx:11
+msgid "Not found"
+msgstr "Não encontrado"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:82
+msgid "You’ve made changes that need to be published before they will be reflected in your application embed."
+msgstr "Você fez alterações que devem ser publicadas antes de serem refletidas a inserção do aplicativo."
+
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:83
+msgid "You will need to publish this {0} before you can embed it in another application."
+msgstr "Você terá que publicar este {0} antes de poder incorporá-lo em outro aplicativo."
+
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:92
+msgid "Discard Changes"
+msgstr "Descartar alterações"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:99
+msgid "Updating..."
+msgstr "Atualizando..."
+
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:100
+msgid "Updated"
+msgstr "Atualizado"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:101
+msgid "Failed!"
+msgstr "Ele falhou!"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:102
+msgid "Publish"
+msgstr "Publicar"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedEmbedPane.jsx:111
+msgid "Code"
+msgstr "Código"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:70
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:157
+msgid "Style"
+msgstr "Estilo"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:80
+msgid "Which parameters can users of this embed use?"
+msgstr "Quais parâmetros os usuários desta incorporação podem usar?"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:83
+msgid "This {0} doesn't have any parameters to configure yet."
+msgstr "Este {0} ainda não tem parâmetros para configurar."
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:104
+msgid "Editable"
+msgstr "Editável"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:105
+msgid "Locked"
+msgstr "Bloqueado"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:113
+msgid "Preview Locked Parameters"
+msgstr "Visualização de parâmetros bloqueados"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:115
+msgid "Try passing some values to your locked parameters here. Your server will have to provide the actual values in the signed token when using this for real."
+msgstr "Tente passar alguns valores para seus parâmetros bloqueados aqui. Seu servidor terá que fornecer os valores reais no token assinado ao usar este de uma maneira real."
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:126
+msgid "Danger zone"
+msgstr "Área perigosa"
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:127
+msgid "This will disable embedding for this {0}."
+msgstr "Isso desativará a incorporação para este {0}."
+
+#: frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx:128
+msgid "Unpublish"
+msgstr "Despublicar"
+
+#: frontend/src/metabase/public/components/widgets/DisplayOptionsPane.jsx:17
+msgid "Light"
+msgstr "Claro"
+
+#: frontend/src/metabase/public/components/widgets/DisplayOptionsPane.jsx:18
+msgid "Dark"
+msgstr "Escuro"
+
+#: frontend/src/metabase/public/components/widgets/DisplayOptionsPane.jsx:37
+msgid "Border"
+msgstr "Borda"
+
+#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:62
+msgid "To embed this {0} in your application:"
+msgstr "Para inserir este {0} na sua aplicação"
+
+#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:64
+msgid "Insert this code snippet in your server code to generate the signed embedding URL "
+msgstr "Insira este trecho de código no código do seu servidor para gerar a URL de inserção "
+
+#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:87
+msgid "Then insert this code snippet in your HTML template or single page app."
+msgstr "Em seguida, insira esse código em seu modelo HTML ou aplicação de página única."
+
+#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:94
+msgid "Embed code snippet for your HTML or Frontend Application"
+msgstr "Incorporar este trecho de código em seu aplicativo"
+
+#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:101
+msgid "More {0}"
+msgstr "Mais {0}"
+
+#: frontend/src/metabase/public/components/widgets/EmbedCodePane.jsx:103
+msgid "examples on GitHub"
+msgstr "exemplos no GitHub"
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:72
+msgid "Enable sharing"
+msgstr "Ativar compartilhamento"
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:76
+msgid "Disable this public link?"
+msgstr "Desativar este link público?"
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:77
+msgid "This will cause the existing link to stop working. You can re-enable it, but when you do it will be a different link."
+msgstr "Isso fará com que o link existente pare de funcionar. Você pode voltar para habilitá-lo, mas quando você fizer isso será um link diferente."
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:117
+msgid "Public link"
+msgstr "Link público"
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:118
+msgid "Share this {0} with people who don't have a Metabase account using the URL below:"
+msgstr "Compartilhe isto {0} com pessoas que não tenham uma conta Metabase usando a seguinte URL:"
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:158
+msgid "Public embed"
+msgstr "Incorporar publicamente"
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:159
+msgid "Embed this {0} in blog posts or web pages by copying and pasting this snippet:"
+msgstr "Incorporar este {0} em posts ou páginas da web ao copiar e colar este fragmento:"
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:176
+msgid "Embed this {0} in an application"
+msgstr "Incorporar este {0} em um aplicativo"
+
+#: frontend/src/metabase/public/components/widgets/SharingPane.jsx:177
+msgid "By integrating with your application server code, you can provide a secure stats {0} limited to a specific user, customer, organization, etc."
+msgstr "Ao integrar-se com seu servidor de aplicativos, você pode fornecer um {0} seguro limitado a um usuário, cliente, organização, etc. específicos"
+
+#: frontend/src/metabase/pulse/components/PulseCardPreview.jsx:94
+msgid "Remove attachment"
+msgstr "Remover o anexo"
+
+#: frontend/src/metabase/pulse/components/PulseCardPreview.jsx:95
+msgid "Attach file with results"
+msgstr "Anexar arquivo com resultados"
+
+#: frontend/src/metabase/pulse/components/PulseCardPreview.jsx:127
+msgid "This question will be added as a file attachment"
+msgstr "Esta questão será adicionada como um anexo"
+
+#: frontend/src/metabase/pulse/components/PulseCardPreview.jsx:128
+msgid "This question won't be included in your Pulse"
+msgstr "Esta questão não será incluída em sua notificação"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:92
+msgid "This pulse will no longer be emailed to {0} {1}"
+msgstr "Esta notificação não será mais enviada por e-mail para {0} {1}"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:94
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:374
+msgid "{0} address"
+msgid_plural "{0} addresses"
+msgstr[0] "{0} endereço"
+msgstr[1] "{0} endereços"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:102
+msgid "Slack channel {0} will no longer get this pulse {1}"
+msgstr "O canal Slack {0} não receberá mais essa notificação {1}"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:110
+msgid "Channel {0} will no longer receive this pulse {1}"
+msgstr "O canal {0} não receberá mais essa notificação {1}"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:127
+msgid "Edit pulse"
+msgstr "Editar uma notificação"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:131
+msgid "What's a Pulse?"
+msgstr "O que é uma notificação?"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:141
+msgid "Got it"
+msgstr "Entendi"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:157
+msgid "Where should this data go?"
+msgstr "Para onde esses dados devem ir?"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:173
+msgid "Unarchiving…"
+msgstr "Desarquivando..."
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:174
+msgid "Unarchive failed"
+msgstr "Falha ao desarquivar"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:175
+msgid "Unarchived"
+msgstr "Desarquivado"
+
+#: frontend/src/metabase/pulse/components/PulseEdit.jsx:182
+msgid "Create pulse"
+msgstr "Crie um notificação"
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:90
+msgid "Attachment"
+msgstr "Anexo"
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:104
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:111
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:671
+msgid "Heads up"
+msgstr "Atenção"
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:105
+msgid "We'll show the first 10 columns and 20 rows of this table in your Pulse. If you email this, we'll add a file attachment with all columns and up to 2,000 rows."
+msgstr "Mostraremos as 10 primeiras colunas e 20 linhas desta tabela em sua notificação. Se você enviar por e-mail, adicionaremos um anexo com todas as colunas e até 2.000 linhas."
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:112
+msgid "Raw data questions can only be included as email attachments"
+msgstr "As perguntas de dados brutos só podem ser incluídas como anexos de e-mail"
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:119
+msgid "Looks like this pulse is getting big"
+msgstr "Parece que esse notificação está ficando muito grande"
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:120
+msgid "We recommend keeping pulses small and focused to help keep them digestible and useful to the whole team."
+msgstr "Recomendamos manter as notificações pequenas e simples para mantê-las fáceis e úteis para toda a equipe."
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:160
+msgid "Pick your data"
+msgstr "Escolha seus dados"
+
+#: frontend/src/metabase/pulse/components/PulseEditCards.jsx:162
+msgid "Choose questions you'd like to send in this pulse"
+msgstr "Escolha as perguntas que você gostaria de enviar nesta notificação"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:29
+msgid "Emails"
+msgstr "E-mails"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:30
+msgid "Slack messages"
+msgstr "Mensagens Slack"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:218
+msgid "Sent"
+msgstr "Enviado"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:219
+msgid "{0} will be sent at"
+msgstr "{0} será enviado para"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:221
+msgid "Messages"
+msgstr "Mensagens"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:232
+msgid "Send email now"
+msgstr "Enviar e-mail agora"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:233
+msgid "Send to {0} now"
+msgstr "Envie para {0} agora"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:235
+msgid "Sending…"
+msgstr "Enviando..."
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:236
+msgid "Sending failed"
+msgstr "Envio falhou"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:239
+msgid "Didn’t send because the pulse has no results."
+msgstr "Não foi enviado porque a notificação não tem resultados."
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:240
+msgid "Pulse sent"
+msgstr "Notificação enviada"
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:279
+msgid "{0} needs to be set up by an administrator."
+msgstr "{0} deve ser configurado por um administrador."
+
+#: frontend/src/metabase/pulse/components/PulseEditChannels.jsx:294
+msgid "Slack"
+msgstr "Slack"
+
+#: frontend/src/metabase/pulse/components/PulseEditCollection.jsx:12
+msgid "Which collection should this pulse live in?"
+msgstr "Em que coleção esta notificação deveria ficar?"
+
+#: frontend/src/metabase/pulse/components/PulseEditName.jsx:35
+msgid "Name your pulse"
+msgstr "Nomeie sua notificação"
+
+#: frontend/src/metabase/pulse/components/PulseEditName.jsx:37
+msgid "Give your pulse a name to help others understand what it's about"
+msgstr "Dê um nome a sua notificação para ajudar os outros a entenderem sobre o que ela é"
+
+#: frontend/src/metabase/pulse/components/PulseEditName.jsx:49
+msgid "Important metrics"
+msgstr "Métricas importantes"
+
+#: frontend/src/metabase/pulse/components/PulseEditSkip.jsx:22
+msgid "Skip if no results"
+msgstr "Pular se não existirem resultados"
+
+#: frontend/src/metabase/pulse/components/PulseEditSkip.jsx:24
+msgid "Skip a scheduled Pulse if none of its questions have any results"
+msgstr "Pule uma notificação programada se nenhuma de suas perguntas tiver resultado"
+
+#: frontend/src/metabase/pulse/components/RecipientPicker.jsx:65
+msgid "Enter email addresses you'd like this data to go to"
+msgstr "Digite os endereços de e-mail para os quais você deseja enviar esses dados"
+
+#: frontend/src/metabase/pulse/components/WhatsAPulse.jsx:16
+msgid "Help everyone on your team stay in sync with your data."
+msgstr "Ajude toda a sua equipe a permanecer em sincronia com seus dados."
+
+#: frontend/src/metabase/pulse/components/WhatsAPulse.jsx:30
+msgid "Pulses let you send data from Metabase to email or Slack on the schedule of your choice."
+msgstr "As notificações permitem que você envie dados do Metabase para o e-mail ou Slack na hora que você quiser."
+
+#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:100
+msgid "After {0}"
+msgstr "Depois de {0}"
+
+#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:102
+msgid "Before {0}"
+msgstr "Antes de {0}"
+
+#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:104
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:299
+msgid "Is Empty"
+msgstr "Vazio"
+
+#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:106
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:305
+msgid "Not Empty"
+msgstr "Não vazio"
+
+#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:109
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:216
+msgid "All Time"
+msgstr "Todo o tempo"
+
+#: frontend/src/metabase/qb/components/TimeseriesFilterWidget.jsx:154
+msgid "Apply"
+msgstr "Aplicar"
+
+#: frontend/src/metabase/qb/components/actions/CommonMetricsAction.jsx:21
+msgid "View {0}"
+msgstr "Veja {0}"
+
+#: frontend/src/metabase/qb/components/actions/CompareWithTable.jsx:29
+msgid "Compare this with all rows in the table"
+msgstr "Comparar este com todas as linhas na tabela"
+
+#: frontend/src/metabase/qb/components/actions/CompoundQueryAction.jsx:14
+msgid "Analyze the results of this Query"
+msgstr "Analise os resultados dessa consulta"
+
+#: frontend/src/metabase/qb/components/actions/CountByTimeAction.jsx:29
+msgid "Count of rows by time"
+msgstr "Número de linhas por hora"
+
+#: frontend/src/metabase/qb/components/actions/PivotByAction.jsx:55
+msgid "Break out by {0}"
+msgstr "Distribuir por {0}"
+
+#: frontend/src/metabase/qb/components/actions/SummarizeBySegmentMetricAction.jsx:34
+msgid "Summarize this segment"
+msgstr "Resumir este segmento"
+
+#: frontend/src/metabase/qb/components/actions/UnderlyingDataAction.jsx:14
+msgid "View this as a table"
+msgstr "Veja isso como uma tabela"
+
+#: frontend/src/metabase/qb/components/actions/UnderlyingRecordsAction.jsx:22
+msgid "View the underlying {0} records"
+msgstr "Veja os registros subjacentes de {0}"
+
+#: frontend/src/metabase/qb/components/actions/XRayCard.jsx:15
+msgid "X-Ray this question"
+msgstr "Aplique raios-X a esta questão"
+
+#: frontend/src/metabase/qb/components/drill/AutomaticDashboardDrill.jsx:32
+msgid "X-ray {0} {1}"
+msgstr "Raio-X {0} {1}"
+
+#: frontend/src/metabase/qb/components/drill/AutomaticDashboardDrill.jsx:32
+#: frontend/src/metabase/qb/components/drill/CompareToRestDrill.js:32
+msgid "these"
+msgstr "estes"
+
+#: frontend/src/metabase/qb/components/drill/AutomaticDashboardDrill.jsx:32
+#: frontend/src/metabase/qb/components/drill/CompareToRestDrill.js:32
+msgid "this"
+msgstr "este"
+
+#: frontend/src/metabase/qb/components/drill/CompareToRestDrill.js:32
+msgid "Compare {0} {1} to the rest"
+msgstr "Comparar {0} {1} com o restante"
+
+#: frontend/src/metabase/qb/components/drill/DistributionDrill.jsx:35
+msgid "Distribution"
+msgstr "Distribuição"
+
+#: frontend/src/metabase/qb/components/drill/ObjectDetailDrill.jsx:38
+msgid "View details"
+msgstr "Ver detalhes"
+
+#: frontend/src/metabase/qb/components/drill/QuickFilterDrill.jsx:54
+msgid "View this {0}'s {1}"
+msgstr "Veja {1} de {0}"
+
+#: frontend/src/metabase/qb/components/drill/SortAction.jsx:45
+msgid "Ascending"
+msgstr "Ascendente"
+
+#: frontend/src/metabase/qb/components/drill/SortAction.jsx:57
+msgid "Descending"
+msgstr "Descendente"
+
+#: frontend/src/metabase/qb/components/drill/SummarizeColumnByTimeDrill.js:47
+msgid "over time"
+msgstr "através do tempo"
+
+#: frontend/src/metabase/qb/components/drill/SummarizeColumnDrill.js:21
+msgid "Avg"
+msgstr "Média"
+
+#: frontend/src/metabase/qb/components/drill/SummarizeColumnDrill.js:33
+msgid "Distincts"
+msgstr "Distintos"
+
+#: frontend/src/metabase/qb/components/drill/UnderlyingRecordsDrill.jsx:32
+msgid "View this {0}"
+msgid_plural "View these {0}"
+msgstr[0] "Veja este {0}"
+msgstr[1] "Veja estes {0}"
+
+#. não seria ampliar aqui?
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:220
+#: frontend/src/metabase/qb/components/drill/ZoomDrill.jsx:26
+msgid "Zoom in"
+msgstr "Aumentar Zoom"
+
+#: frontend/src/metabase/query_builder/components/AggregationPopover.jsx:19
+msgid "Custom Expression"
+msgstr "Expressão personalizada"
+
+#: frontend/src/metabase/query_builder/components/AggregationPopover.jsx:20
+msgid "Common Metrics"
+msgstr "Métricas Comuns"
+
+#: frontend/src/metabase/query_builder/components/AggregationPopover.jsx:182
+msgid "Metabasics"
+msgstr "Metabase"
+
+#: frontend/src/metabase/query_builder/components/AggregationPopover.jsx:290
+msgid "Name (optional)"
+msgstr "Nome (opcional)"
+
+#: frontend/src/metabase/query_builder/components/AggregationWidget.jsx:153
+msgid "Choose an aggregation"
+msgstr "Escolha uma agregação"
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:115
+msgid "Set up your own alert"
+msgstr "Configure seu próprio alerta"
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:155
+msgid "Unsubscribing..."
+msgstr "Cancelando inscrição..."
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:160
+msgid "Failed to unsubscribe"
+msgstr "Falha ao cancelar a inscrição"
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:216
+msgid "Unsubscribe"
+msgstr "Retirar-se"
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:247
+msgid "No channel"
+msgstr "Sem canal"
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:274
+msgid "Okay, you're unsubscribed"
+msgstr "Ok, sua inscrição foi cancelada"
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:346
+msgid "You're receiving {0}'s alerts"
+msgstr "Você está recebendo alertas de {0}"
+
+#: frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx:347
+msgid "{0} set up an alert"
+msgstr "{0} configurou um alerta"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:161
+msgid "alerts"
+msgstr "alertas"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:184
+msgid "Let's set up your alert"
+msgstr "Vamos configurar seu alerta"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:215
+msgid "The wide world of alerts"
+msgstr "O vasto mundo de alertas"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:216
+msgid "There are a few different kinds of alerts you can get"
+msgstr "Existem alguns tipos diferentes de alertas que você pode obter"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:229
+msgid "When a raw data question {0}"
+msgstr "Quando uma questão de dados brutos {0}"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:230
+msgid "returns any results"
+msgstr "retorna alguns resultados"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:240
+msgid "When a line or bar {0}"
+msgstr "Quando uma linha ou barra {0}"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:241
+msgid "crosses a goal line"
+msgstr "excede a meta"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:251
+msgid "When a progress bar {0}"
+msgstr "Quando uma barra de progresso {0}"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:252
+msgid "reaches its goal"
+msgstr "alcançar sua meta"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:260
+msgid "Set up an alert"
+msgstr "Crie um alerta"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:329
+msgid "Edit your alert"
+msgstr "Edite seu alerta"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:329
+msgid "Edit alert"
+msgstr "Editar alerta"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:372
+msgid "This alert will no longer be emailed to {0}."
+msgstr "Este alerta não será mais enviado por email para {0}."
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:380
+msgid "Slack channel {0} will no longer get this alert."
+msgstr "O canal Slack {0} não receberá mais este alerta."
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:384
+msgid "Channel {0} will no longer receive this alert."
+msgstr "O canal {0} não receberá mais este alerta."
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:401
+msgid "Delete this alert"
+msgstr "Remover este alerta"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:403
+msgid "Stop delivery and delete this alert. There's no undo, so be careful."
+msgstr "Pare de enviar e remova este alerta. Não pode ser desfeito, então tenha tenha cuidado"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:411
+msgid "Delete this alert?"
+msgstr "Remover este alerta?"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:495
+msgid "Alert me when the line…"
+msgstr "Deixe-me saber quando a linha..."
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:496
+msgid "Alert me when the progress bar…"
+msgstr "Deixe-me saber quando a barra de progresso..."
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:499
+msgid "Goes above the goal line"
+msgstr "Vai acima da linha da meta"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:499
+msgid "Reaches the goal"
+msgstr "Alcançar a meta"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:502
+msgid "Goes below the goal line"
+msgstr "Vai abaixo da linha de meta"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:502
+msgid "Goes below the goal"
+msgstr "Vai abaixo da meta"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:510
+msgid "The first time it crosses, or every time?"
+msgstr "A primeira vez que você cruza ou a cada vez?"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:511
+msgid "The first time it reaches the goal, or every time?"
+msgstr "A primeira vez que você alcança a meta ou a cada vez?"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:513
+msgid "The first time"
+msgstr "A primeira vez"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:514
+msgid "Every time"
+msgstr "Toda vez"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:617
+msgid "Where do you want to send these alerts?"
+msgstr "Para onde você deseja enviar esses alertas?"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:628
+msgid "Email alerts to:"
+msgstr "Envie e-mails de alerta para:"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:670
+msgid "{0} Goal-based alerts aren't yet supported for charts with more than one line, so this alert will be sent whenever the chart has {1}."
+msgstr "{0} Alertas baseados em metas ainda não são compatíveis com gráficos com mais de uma linha, portanto, esse alerta será enviado desde que o gráfico tenha {1}."
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:673
+msgid "results"
+msgstr "resultados"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:677
+msgid "{0} This kind of alert is most useful when your saved question doesn’t {1} return any results, but you want to know when it does."
+msgstr "{0} Este tipo de alerta é mais útil quando sua pergunta {1} não lança nenhum resultado, mas você quer saber quando isso acontece."
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:678
+msgid "Tip"
+msgstr "Dica:"
+
+#: frontend/src/metabase/query_builder/components/AlertModals.jsx:680
+msgid "usually"
+msgstr "geralmente"
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:57
+msgid "Pick a segment or table"
+msgstr "Escolha um segmento ou tabela"
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:73
+msgid "Select a database"
+msgstr "Selecione um banco de dados"
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:88
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:87
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:187
+#: frontend/src/metabase/reference/components/MetricImportantFieldsDetail.jsx:35
+msgid "Select..."
+msgstr "Selecione..."
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:128
+msgid "Select a table"
+msgstr "Selecione uma tabela"
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:785
+msgid "No tables found in this database."
+msgstr "Nenhuma tabela foi encontrada neste banco de dados."
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:822
+msgid "Is a question missing?"
+msgstr "Uma pergunta está faltando?"
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:826
+msgid "Learn more about nested queries"
+msgstr "Saiba mais sobre consultas aninhadas"
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:860
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:30
+msgid "Fields"
+msgstr "Campos"
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:938
+msgid "No segments were found."
+msgstr "Nenhum segmento foi encontrado"
+
+#: frontend/src/metabase/query_builder/components/DataSelector.jsx:961
+msgid "Find a segment"
+msgstr "Encontre um segmento"
+
+#: frontend/src/metabase/query_builder/components/ExpandableString.jsx:46
+msgid "View less"
+msgstr "Ver menos"
+
+#: frontend/src/metabase/query_builder/components/ExpandableString.jsx:56
+msgid "View more"
+msgstr "Ver mais"
+
+#: frontend/src/metabase/query_builder/components/ExtendedOptions.jsx:111
+msgid "Pick a field to sort by"
+msgstr "Escolha um campo para ordenar por"
+
+#: frontend/src/metabase/query_builder/components/ExtendedOptions.jsx:124
+msgid "Sort"
+msgstr "Ordenar"
+
+#: frontend/src/metabase/query_builder/components/ExtendedOptions.jsx:193
+msgid "Row limit"
+msgstr "Limite de linha"
+
+#: frontend/src/metabase/query_builder/components/FieldName.jsx:76
+msgid "Unknown Field"
+msgstr "Campo Desconhecido"
+
+#: frontend/src/metabase/query_builder/components/FieldName.jsx:79
+msgid "field"
+msgstr "campo"
+
+#: frontend/src/metabase/query_builder/components/Filter.jsx:113
+msgid "Matches"
+msgstr "Combinações"
+
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:152
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:160
+msgid "Add filters to narrow your answer"
+msgstr "Adicione filtros para limitar sua resposta"
+
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:284
+msgid "Add a grouping"
+msgstr "Adicione um grupo"
+
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:322
+#: frontend/src/metabase/visualizations/components/LineAreaBarChart.jsx:102
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:55
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:92
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:131
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:58
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:66
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:73
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:54
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:61
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:66
+#: frontend/src/metabase/visualizations/visualizations/Table.jsx:60
+#: frontend/src/metabase/visualizations/visualizations/Table.jsx:72
+msgid "Data"
+msgstr "Dados"
+
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:352
+msgid "Filtered by"
+msgstr "Filtrado por"
+
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:369
+msgid "View"
+msgstr "Ver"
+
+#: frontend/src/metabase/query_builder/components/GuiQueryEditor.jsx:386
+msgid "Grouped By"
+msgstr "Agrupado por"
+
+#: frontend/src/metabase/query_builder/components/LimitWidget.jsx:27
+msgid "None"
+msgstr "Nenhum"
+
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:338
+msgid "This question is written in {0}."
+msgstr "Esta questão está escrita em {0}."
+
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:346
+msgid "Hide Editor"
+msgstr "Ocultar editor"
+
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:347
+msgid "Hide Query"
+msgstr "Esconder consulta"
+
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:352
+msgid "Open Editor"
+msgstr "Abrir editor"
+
+#: frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx:353
+msgid "Show Query"
+msgstr "Mostrar consulta"
+
+#: frontend/src/metabase/query_builder/components/QueryDefinitionTooltip.jsx:25
+msgid "This metric has been retired.  It's no longer available for use."
+msgstr "Esta métrica foi removida. Não está mais disponível para uso."
+
+#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:34
+#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:46
+msgid "Download full results"
+msgstr "Baixe os resultados completos"
+
+#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:35
+msgid "Download this data"
+msgstr "Baixe esta informação"
+
+#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:46
+msgid "Warning"
+msgstr "Aviso"
+
+#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:52
+msgid "Your answer has a large number of rows so it could take a while to download."
+msgstr "Sua resposta tem muitas linhas, então pode demorar um pouco para baixar."
+
+#: frontend/src/metabase/query_builder/components/QueryDownloadWidget.jsx:53
+msgid "The maximum download size is 1 million rows."
+msgstr "O tamanho máximo para baixar é de 1 milhão de linhas."
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:232
+msgid "Edit question"
+msgstr "Editar pergunta"
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:249
+msgid "SAVE CHANGES"
+msgstr "SALVAR ALTERAÇÕES"
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:263
+msgid "CANCEL"
+msgstr "CANCELAR"
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:276
+msgid "Move question"
+msgstr "Mover pergunta"
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:283
+msgid "Which collection should this be in?"
+msgstr "Em que coleção deveria ser?"
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:313
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:110
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorSidebar.jsx:83
+msgid "Variables"
+msgstr "Variáveis"
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:432
+msgid "Learn about your data"
+msgstr "Conheça seus dados"
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:460
+msgid "Alerts are on"
+msgstr "Os alertas são ativados"
+
+#: frontend/src/metabase/query_builder/components/QueryHeader.jsx:522
+msgid "started from"
+msgstr "iniciado a partir de"
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:48
+msgid "SQL"
+msgstr "SQL"
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:48
+msgid "native query"
+msgstr "consulta nativa"
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:52
+msgid "Not Supported"
+msgstr "Não suportado"
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:58
+msgid "View the {0}"
+msgstr "Veja {0}"
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:59
+msgid "Switch to {0}"
+msgstr "Mude para {0}"
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:62
+msgid "Switch to Builder"
+msgstr "Mude para o editor"
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:87
+msgid "{0} for this question"
+msgstr "{0} desta questão"
+
+#: frontend/src/metabase/query_builder/components/QueryModeButton.jsx:111
+msgid "Convert this question to {0}"
+msgstr "Converta esta questão para {0}"
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:122
+msgid "This question will take approximately {0} to refresh"
+msgstr "Esta questão será atualizada em aproximadamente {0}"
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:131
+msgid "Updated {0}"
+msgstr "Atualizado {0} atrás"
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:141
+msgid "row"
+msgid_plural "rows"
+msgstr[0] "linha"
+msgstr[1] "linhas"
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:148
+msgid "Showing first {0} {1}"
+msgstr "Mostrando primeiro {0} {1}"
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:151
+msgid "Showing {0} {1}"
+msgstr "Mostrando {0} {1}"
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:281
+msgid "Doing science"
+msgstr "Fazendo ciência"
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:294
+msgid "If you give me some data I can show you something cool. Run a Query!"
+msgstr "Se você me der alguma informação, eu posso te mostrar uma coisa ótima. Execute uma consulta!"
+
+#: frontend/src/metabase/query_builder/components/QueryVisualization.jsx:299
+msgid "How do I use this thing?"
+msgstr "Como eu uso essa coisa?"
+
+#: frontend/src/metabase/query_builder/components/RunButton.jsx:28
+msgid "Get Answer"
+msgstr "Obter uma resposta"
+
+#: frontend/src/metabase/query_builder/components/SavedQuestionIntroModal.jsx:12
+msgid "It's okay to play around with saved questions"
+msgstr "Você pode brincar com perguntas salvas"
+
+#: frontend/src/metabase/query_builder/components/SavedQuestionIntroModal.jsx:14
+msgid "You won't make any permanent changes to a saved question unless you click the edit icon in the top-right."
+msgstr "Você não fará alterações permanentes em uma pergunta salva, a menos que você clique no ícone de edição no canto superior direito."
+
+#: frontend/src/metabase/query_builder/components/SearchBar.jsx:28
+msgid "Search for"
+msgstr "Pesquisar por"
+
+#: frontend/src/metabase/query_builder/components/SelectionModule.jsx:158
+msgid "Advanced..."
+msgstr "Avançado..."
+
+#: frontend/src/metabase/query_builder/components/SelectionModule.jsx:167
+msgid "Sorry. Something went wrong."
+msgstr "Sinto muito. Algo deu errado."
+
+#: frontend/src/metabase/query_builder/components/TimeGroupingPopover.jsx:40
+msgid "Group time by"
+msgstr "Horário do grupo por"
+
+#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:46
+msgid "Your question took too long"
+msgstr "Sua pergunta demorou muito"
+
+#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:47
+msgid "We didn't get an answer back from your database in time, so we had to stop. You can try again in a minute, or if the problem persists, you can email an admin to let them know."
+msgstr "Nós não recebemos uma resposta do seu banco de dados a tempo, então tivemos que Você pode tentar novamente em um minuto, ou se o problema persistir, Você pode enviar um email para um administrador para notificá-lo."
+
+#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:55
+msgid "We're experiencing server issues"
+msgstr "Estamos com problemas com o servidor"
+
+#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:56
+msgid "Try refreshing the page after waiting a minute or two. If the problem persists we'd recommend you contact an admin."
+msgstr "Tente atualizar a página depois de esperar um ou dois minutos. Se ele problema persistir, recomendamos que você entre em contato com um administrador."
+
+#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:88
+msgid "There was a problem with your question"
+msgstr "Houve um problema com sua pergunta"
+
+#: frontend/src/metabase/query_builder/components/VisualizationError.jsx:89
+msgid "Most of the time this is caused by an invalid selection or bad input value. Double check your inputs and retry your query."
+msgstr "Na maioria das vezes isso é causado por uma seleção inválida ou por um Valor incorreto de entrada. Verifique suas entradas e tente novamente."
+
+#: frontend/src/metabase/query_builder/components/VisualizationResult.jsx:60
+msgid "This may be the answer you’re looking for. If not, try removing or changing your filters to make them less specific."
+msgstr "Esta pode ser a resposta que você está procurando. Caso contrário, tente Remova ou altere seus filtros para torná-los menos específicos."
+
+#: frontend/src/metabase/query_builder/components/VisualizationResult.jsx:66
+msgid "You can also {0} when there are some results."
+msgstr "Você também pode {0} quando há alguns resultados."
+
+#: frontend/src/metabase/query_builder/components/VisualizationResult.jsx:68
+msgid "get an alert"
+msgstr "receber um alerta"
+
+#: frontend/src/metabase/query_builder/components/VisualizationResult.jsx:77
+msgid "Back to last run"
+msgstr "Retornar para a última execução"
+
+#: frontend/src/metabase/query_builder/components/VisualizationSettings.jsx:53
+msgid "Visualization"
+msgstr "Display"
+
+#: frontend/src/metabase/query_builder/components/dataref/DetailPane.jsx:17
+#: frontend/src/metabase/query_builder/components/dataref/TablePane.jsx:151
+msgid "No description set."
+msgstr "Sem descrição"
+
+#: frontend/src/metabase/query_builder/components/dataref/DetailPane.jsx:21
+msgid "Use for current question"
+msgstr "Use para a pergunta atual"
+
+#: frontend/src/metabase/query_builder/components/dataref/DetailPane.jsx:33
+#: frontend/src/metabase/reference/components/UsefulQuestions.jsx:16
+msgid "Potentially useful questions"
+msgstr "Perguntas potencialmente úteis"
+
+#: frontend/src/metabase/query_builder/components/dataref/FieldPane.jsx:156
+msgid "Group by {0}"
+msgstr "Agrupar por {0}"
+
+#: frontend/src/metabase/query_builder/components/dataref/FieldPane.jsx:165
+msgid "Sum of all values of {0}"
+msgstr "Soma de todos os valores de {0}"
+
+#: frontend/src/metabase/query_builder/components/dataref/FieldPane.jsx:173
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:63
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:51
+msgid "All distinct values of {0}"
+msgstr "Todos os valores diferentes de {0}"
+
+#: frontend/src/metabase/query_builder/components/dataref/FieldPane.jsx:177
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:39
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:51
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:39
+msgid "Number of {0} grouped by {1}"
+msgstr "Número de {0} agrupados por {1}"
+
+#: frontend/src/metabase/query_builder/components/dataref/MainPane.jsx:11
+#: frontend/src/metabase/reference/databases/DatabaseSidebar.jsx:20
+#: frontend/src/metabase/reference/databases/FieldSidebar.jsx:27
+#: frontend/src/metabase/reference/databases/TableSidebar.jsx:23
+#: frontend/src/metabase/reference/guide/BaseSidebar.jsx:17
+#: frontend/src/metabase/reference/guide/BaseSidebar.jsx:19
+#: frontend/src/metabase/reference/metrics/MetricSidebar.jsx:20
+#: frontend/src/metabase/reference/segments/SegmentFieldSidebar.jsx:24
+#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:20
+msgid "Data Reference"
+msgstr "Referência de dados"
+
+#: frontend/src/metabase/query_builder/components/dataref/MainPane.jsx:13
+msgid "Learn more about your data structure to ask more useful questions"
+msgstr "Saiba mais sobre sua estrutura de dados para fazer perguntas mais úteis"
+
+#: frontend/src/metabase/query_builder/components/dataref/MetricPane.jsx:58
+#: frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx:84
+msgid "Could not find the table metadata prior to creating a new question"
+msgstr "Os metadados na tabela não foram encontrados antes de criar um novo questão"
+
+#: frontend/src/metabase/query_builder/components/dataref/MetricPane.jsx:80
+msgid "See {0}"
+msgstr "Veja {0}"
+
+#: frontend/src/metabase/query_builder/components/dataref/MetricPane.jsx:94
+msgid "Metric Definition"
+msgstr "Definição de Métrica"
+
+#: frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx:118
+msgid "Filter by {0}"
+msgstr "Filtrar por {0}"
+
+#: frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx:127
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:36
+msgid "Number of {0}"
+msgstr "Número de {0}"
+
+#: frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx:134
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:46
+msgid "See all {0}"
+msgstr "Ver todos {0}"
+
+#: frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx:148
+msgid "Segment Definition"
+msgstr "Definição de Segmento"
+
+#: frontend/src/metabase/query_builder/components/dataref/TablePane.jsx:50
+msgid "An error occurred loading the table"
+msgstr "Ocorreu um erro ao carregar a tabela"
+
+#: frontend/src/metabase/query_builder/components/dataref/TablePane.jsx:74
+msgid "See the raw data for {0}"
+msgstr "Veja os dados brutos de {0}"
+
+#: frontend/src/metabase/query_builder/components/dataref/TablePane.jsx:205
+msgid "More"
+msgstr "Mais"
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:200
+msgid "Invalid expression"
+msgstr "Expressão inválida"
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:275
+msgid "unknown error"
+msgstr "erro desconhecido"
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:46
+msgid "Field formula"
+msgstr "Campo fórmula"
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:57
+msgid "Think of this as being kind of like writing a formula in a spreadsheet program: you can use numbers, fields in this table, mathematical symbols like +, and some functions. So you could type something like Subtotal - Cost."
+msgstr "Pense nisso como algo como escrever uma fórmula em um programa de planilhas: você pode usar números, campos nesta tabela, símbolos matemáticos como +, e algumas funções. Então você poderia escrever algo como Subtotal &minus; Custo."
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:62
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:126
+msgid "Learn more"
+msgstr "Saiba mais"
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:66
+msgid "Give it a name"
+msgstr "Dê um nome a ele"
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx:72
+msgid "Something nice and descriptive"
+msgstr "Algo legal e descritivo"
+
+#: frontend/src/metabase/query_builder/components/expressions/Expressions.jsx:60
+msgid "Add a custom field"
+msgstr "Adicione um campo personalizado"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:17
+msgid "Include {0}"
+msgstr "Inclua {0}"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:19
+msgid "Case sensitive"
+msgstr "É sensível a maiúsculas e minúsculas"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:23
+msgid "today"
+msgstr "hoje"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:24
+msgid "this week"
+msgstr "esta semana"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:25
+msgid "this month"
+msgstr "este mês"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:26
+msgid "this year"
+msgstr "este ano"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:27
+msgid "this minute"
+msgstr "esse minuto"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterOptions.jsx:28
+msgid "this hour"
+msgstr "esta hora"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx:285
+msgid "not implemented {0}"
+msgstr "{0} não implementado"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx:286
+msgid "true"
+msgstr "verdadeiro"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx:286
+msgid "false"
+msgstr "falso"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx:390
+msgid "Add filter"
+msgstr "Adicionar filtro"
+
+#: frontend/src/metabase/query_builder/components/filters/FilterWidgetList.jsx:64
+msgid "Item"
+msgstr "Element"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:224
+msgid "Previous"
+msgstr "Anterior"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:255
+msgid "Current"
+msgstr "Atual"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/DatePicker.jsx:282
+msgid "On"
+msgstr "Ativado"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/NumberPicker.jsx:47
+msgid "Enter desired number"
+msgstr "Digite o número desejado"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/SelectPicker.jsx:83
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:100
+msgid "Empty"
+msgstr "Vazio"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/SelectPicker.jsx:116
+msgid "Find a value"
+msgstr "Encontre um valor"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/SpecificDatePicker.jsx:113
+msgid "Hide calendar"
+msgstr "Ocultar calendário"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/SpecificDatePicker.jsx:113
+msgid "Show calendar"
+msgstr "Mostrar calendário"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/TextPicker.jsx:97
+msgid "You can enter multiple values separated by commas"
+msgstr "Você pode inserir vários valores separados por vírgulas"
+
+#: frontend/src/metabase/query_builder/components/filters/pickers/TextPicker.jsx:38
+msgid "Enter desired text"
+msgstr "Digite o texto desejado"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:83
+msgid "Try it"
+msgstr "Experimente"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:105
+msgid "What's this for?"
+msgstr "Para que serve isto?"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:107
+msgid "Variables in native queries let you dynamically replace values in your queries using filter widgets or through the URL."
+msgstr "Variáveis ​​em consultas nativas permitem que você substitua dinamicamente os valores em suas consultas usando elementos de filtro ou através do URL"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:112
+msgid "{0} creates a variable in this SQL template called \"variable_name\". Variables can be given types in the side panel, which changes their behavior. All variable types other than \"Field Filter\" will automatically cause a filter widget to be placed on this question; with Field Filters, this is optional. When this filter widget is filled in, that value replaces the variable in the SQL template."
+msgstr "{0} cria uma variável neste modelo SQL chamado \"nome_da_variável\". Você pode atribuir tipos às variáveis ​​no painel lateral, o que muda seu comportamento. Todos os tipos de variáveis que não sejam \"Filtro de Campo\" farão com que o filtro seja colocado nesta questão; com filtros de campo, isso é opcional. Quando este filtro isso é preenchido, esse valor substitui a variável no modelo SQL."
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:121
+msgid "Field Filters"
+msgstr "Filtros de campo"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:123
+msgid "Giving a variable the \"Field Filter\" type allows you to link SQL cards to dashboard filter widgets or use more types of filter widgets on your SQL question. A Field Filter variable inserts SQL similar to that generated by the GUI query builder when adding filters on existing columns."
+msgstr "Dar a uma variavel o tipo \"Filtro de campo\" te permite vincular as placas SQL com os elementos de filtro nos painéis, ou usar mais tipos de elementos de filtro na sua pergunta SQL. Uma variável de Filtro de Campo insere SQL semelhante ao editor de consultas GUI quando os filtros são adicionados às colunas existentes."
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:126
+msgid "When adding a Field Filter variable, you'll need to map it to a specific field. You can then choose to display a filter widget on your question, but even if you don't, you can now map your Field Filter variable to a dashboard filter when adding this question to a dashboard. Field Filters should be used inside of a \"WHERE\" clause."
+msgstr "Ao adicionar uma variável de Filtro de Campo, você deve atribuí-la a um campo específico. Em seguida, você pode optar por mostrar um elemento de filtro na sua pergunta, mas mesmo se você não fizer isso, você pode agora atribuir a sua variável de Filtro de Campo para um Filtro de Painel ao adicionar esta pergunta ao painel. Filtros de Campo devem ser usados ​​dentro de uma cláusula \"WHERE\"."
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:130
+msgid "Optional Clauses"
+msgstr "Cláusulas opcionais"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:132
+msgid "brackets around a {0} create an optional clause in the template. If \"variable\" is set, then the entire clause is placed into the template. If not, then the entire clause is ignored."
+msgstr "os parênteses em torno de um {0} criam uma cláusula opcional no modelo. Se a \"variável\" está posta, então a cláusula inteira é posta no  modelo. Caso contrário, a cláusula inteira será ignorada."
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:142
+msgid "To use multiple optional clauses you can include at least one non-optional WHERE clause followed by optional clauses starting with \"AND\"."
+msgstr "Para usar várias cláusulas opcionais, você pode incluir pelo menos uma cláusula WHERE não opcional seguida de cláusulas opcionais que começam com \"AND\"."
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp.jsx:154
+msgid "Read the full documentation"
+msgstr "Leia a documentação completa"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:124
+msgid "Filter label"
+msgstr "Filtro de Etiqueta"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:136
+msgid "Variable type"
+msgstr "Tipo de Variável"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:145
+msgid "Text"
+msgstr "Texto"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:147
+msgid "Date"
+msgstr "Data"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:148
+msgid "Field Filter"
+msgstr "Filtro de campo"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:154
+msgid "Field to map to"
+msgstr "Campo para mapear para"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:176
+msgid "Filter widget type"
+msgstr "Tipo de filtro"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:199
+msgid "Required?"
+msgstr "Obrigatório?"
+
+#: frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx:210
+msgid "Default filter widget value"
+msgstr "Valor padrão do filtro"
+
+#: frontend/src/metabase/query_builder/containers/ArchiveQuestionModal.jsx:46
+msgid "Archive this question?"
+msgstr "Arquivar esta pergunta?"
+
+#: frontend/src/metabase/query_builder/containers/ArchiveQuestionModal.jsx:57
+msgid "This question will be removed from any dashboards or pulses using it."
+msgstr "Esta questão será removida de qualquer painel ou notificação que a utilize."
+
+#: frontend/src/metabase/query_builder/containers/QueryBuilder.jsx:136
+msgid "Question"
+msgstr "Pergunta"
+
+#: frontend/src/metabase/questions/containers/AddToDashboard.jsx:11
+msgid "Pick a question to add"
+msgstr "Escolha uma pergunta para adicionar"
+
+#: frontend/src/metabase/reference/components/EditHeader.jsx:19
+msgid "You are editing this page"
+msgstr "Você está editando esta página"
+
+#: frontend/src/metabase/reference/components/EditableReferenceHeader.jsx:101
+#: frontend/src/metabase/reference/components/ReferenceHeader.jsx:63
+msgid "See this {0}"
+msgstr "Veja isto {0}"
+
+#: frontend/src/metabase/reference/components/EditableReferenceHeader.jsx:120
+msgid "A subset of"
+msgstr "Um subconjunto de"
+
+#: frontend/src/metabase/reference/components/Field.jsx:47
+#: frontend/src/metabase/reference/components/Field.jsx:86
+#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:32
+#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:68
+msgid "Select a field type"
+msgstr "Selecione um tipo de campo"
+
+#: frontend/src/metabase/reference/components/Field.jsx:56
+#: frontend/src/metabase/reference/components/Field.jsx:71
+#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:41
+#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:57
+msgid "No field type"
+msgstr "Sem tipo de campo"
+
+#: frontend/src/metabase/reference/components/FieldToGroupBy.jsx:22
+msgid "by"
+msgstr "por"
+
+#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:25
+#: frontend/src/metabase/reference/databases/FieldList.jsx:152
+#: frontend/src/metabase/reference/segments/SegmentFieldList.jsx:153
+msgid "Field type"
+msgstr "Tipo do campo"
+
+#: frontend/src/metabase/reference/components/FieldTypeDetail.jsx:72
+msgid "Select a Foreign Key"
+msgstr "Selecione uma chave estrangeira"
+
+#: frontend/src/metabase/reference/components/Formula.jsx:53
+msgid "View the {0} formula"
+msgstr "Veja a fórmula para {0}"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:80
+msgid "Why this {0} is important"
+msgstr "Por que isso {0} é importante"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:81
+msgid "Why this {0} is interesting"
+msgstr "Por que isso {0} é interessante"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:87
+msgid "Nothing important yet"
+msgstr "Nada importante ainda"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:88
+#: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:168
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:233
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:211
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:215
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:219
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:229
+msgid "Nothing interesting yet"
+msgstr "Nada de interessante ainda"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:93
+msgid "Things to be aware of about this {0}"
+msgstr "Coisas para ter em mente sobre isso {0}"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:97
+#: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:178
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:243
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:221
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:225
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:229
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:239
+msgid "Nothing to be aware of yet"
+msgstr "Nada para estar ciente ainda"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:103
+msgid "Explore this metric"
+msgstr "Explore esta métrica"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:105
+msgid "View this metric"
+msgstr "Veja esta métrica"
+
+#: frontend/src/metabase/reference/components/GuideDetail.jsx:112
+msgid "By {0}"
+msgstr "Por {0}"
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:146
+msgid "Remove item"
+msgstr "Remover item"
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:155
+msgid "Why is this dashboard the most important?"
+msgstr "Por que esse painel é o mais importante?"
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:156
+msgid "What is useful or interesting about this {0}?"
+msgstr "O que é útil ou interessante sobre isso {0}?"
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:160
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:174
+msgid "Write something helpful here"
+msgstr "Escreva algo útil aqui"
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:169
+msgid "Is there anything users of this dashboard should be aware of?"
+msgstr "Existe alguma coisa que os usuários deste painel devam considerar?"
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:170
+msgid "Anything users should be aware of about this {0}?"
+msgstr "Existe alguma coisa que os usuários devem saber sobre isso {0}?"
+
+#: frontend/src/metabase/reference/components/GuideDetailEditor.jsx:182
+#: frontend/src/metabase/reference/components/MetricImportantFieldsDetail.jsx:26
+msgid "Which 2-3 fields do you usually group this metric by?"
+msgstr "Com quais 2-3 campos esta métrica é geralmente agrupada?"
+
+#: frontend/src/metabase/reference/components/GuideHeader.jsx:23
+msgid "This is the perfect place to start if you’re new to your company’s data, or if you just want to check in on what’s going on."
+msgstr "Este é o lugar perfeito para começar se você é novo em seus dados empresa, ou se você quiser apenas verificar o que está acontecendo."
+
+#: frontend/src/metabase/reference/components/MetricImportantFieldsDetail.jsx:65
+msgid "Most useful fields to group this metric by"
+msgstr "Campos mais úteis para agrupar essa métrica"
+
+#: frontend/src/metabase/reference/components/RevisionMessageModal.jsx:32
+msgid "Reason for changes"
+msgstr "Razão da mudança"
+
+#: frontend/src/metabase/reference/components/RevisionMessageModal.jsx:36
+msgid "Leave a note to explain what changes you made and why they were required"
+msgstr "Deixe uma nota para explicar que mudanças você fez e por que elas foram obrigatório"
+
+#: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:166
+msgid "Why this database is interesting"
+msgstr "Por que esse banco de dados é interessante"
+
+#: frontend/src/metabase/reference/databases/DatabaseDetail.jsx:176
+msgid "Things to be aware of about this database"
+msgstr "Coisas para manter em mente sobre este banco de dados"
+
+#: frontend/src/metabase/reference/databases/DatabaseList.jsx:46
+#: frontend/src/metabase/reference/guide/BaseSidebar.jsx:39
+msgid "Databases and tables"
+msgstr "Bancos de dados e tabelas"
+
+#: frontend/src/metabase/reference/databases/DatabaseSidebar.jsx:27
+#: frontend/src/metabase/reference/databases/FieldSidebar.jsx:38
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:170
+#: frontend/src/metabase/reference/databases/TableSidebar.jsx:31
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:184
+#: frontend/src/metabase/reference/metrics/MetricSidebar.jsx:27
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:188
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:187
+#: frontend/src/metabase/reference/segments/SegmentFieldSidebar.jsx:31
+#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:27
+msgid "Details"
+msgstr "Detalhes"
+
+#: frontend/src/metabase/reference/databases/DatabaseSidebar.jsx:33
+#: frontend/src/metabase/reference/databases/TableList.jsx:111
+msgid "Tables in {0}"
+msgstr "Tabelas em {0}"
+
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:222
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:200
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:218
+msgid "Actual name in database"
+msgstr "Nome real no banco de dados"
+
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:231
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:227
+msgid "Why this field is interesting"
+msgstr "Por que esse campo é interessante"
+
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:241
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:237
+msgid "Things to be aware of about this field"
+msgstr "Coisas a ter em mente sobre este campo"
+
+#: frontend/src/metabase/reference/databases/FieldDetail.jsx:253
+#: frontend/src/metabase/reference/databases/FieldList.jsx:155
+#: frontend/src/metabase/reference/segments/SegmentFieldDetail.jsx:249
+#: frontend/src/metabase/reference/segments/SegmentFieldList.jsx:156
+msgid "Data type"
+msgstr "Tipo de dados"
+
+#: frontend/src/metabase/reference/databases/FieldList.jsx:39
+#: frontend/src/metabase/reference/segments/SegmentFieldList.jsx:39
+msgid "Fields in this table will appear here as they're added"
+msgstr "Os campos nesta tabela aparecerão aqui à medida que forem adicionados"
+
+#: frontend/src/metabase/reference/databases/FieldList.jsx:134
+#: frontend/src/metabase/reference/segments/SegmentFieldList.jsx:135
+msgid "Fields in {0}"
+msgstr "Campos em {0}"
+
+#: frontend/src/metabase/reference/databases/FieldList.jsx:149
+#: frontend/src/metabase/reference/segments/SegmentFieldList.jsx:150
+msgid "Field name"
+msgstr "Nome do campo"
+
+#: frontend/src/metabase/reference/databases/FieldSidebar.jsx:46
+msgid "X-ray this field"
+msgstr "Aplique raios-X a este campo"
+
+#: frontend/src/metabase/reference/databases/NoDatabasesEmptyState.jsx:8
+msgid "Metabase is no fun without any data"
+msgstr "Metabase não é divertido sem dados"
+
+#: frontend/src/metabase/reference/databases/NoDatabasesEmptyState.jsx:9
+msgid "Your databases will appear here once you connect one"
+msgstr "Seus bancos de dados aparecerão aqui quando você conectar um"
+
+#: frontend/src/metabase/reference/databases/NoDatabasesEmptyState.jsx:10
+msgid "Databases will appear here once your admins have added some"
+msgstr "Os bancos de dados aparecerão aqui quando os administradores tiverem adicionou alguns"
+
+#: frontend/src/metabase/reference/databases/NoDatabasesEmptyState.jsx:12
+msgid "Connect a database"
+msgstr "Conecte um banco de dados"
+
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:38
+msgid "Count of {0}"
+msgstr "Número de {0}"
+
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:47
+msgid "See raw data for {0}"
+msgstr "Ver dados brutos de {0}"
+
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:209
+msgid "Why this table is interesting"
+msgstr "Por que esta tabela é interessante"
+
+#: frontend/src/metabase/reference/databases/TableDetail.jsx:219
+msgid "Things to be aware of about this table"
+msgstr "Coisas para manter em mente sobre esta tabela"
+
+#: frontend/src/metabase/reference/databases/TableList.jsx:30
+msgid "Tables in this database will appear here as they're added"
+msgstr "As tabelas neste banco de dados aparecerão aqui quando forem adicionadas"
+
+#: frontend/src/metabase/reference/databases/TableQuestions.jsx:34
+msgid "Questions about this table will appear here as they're added"
+msgstr "Perguntas sobre esta tabela aparecerão aqui quando forem adicionadas"
+
+#: frontend/src/metabase/reference/databases/TableQuestions.jsx:71
+#: frontend/src/metabase/reference/metrics/MetricQuestions.jsx:75
+#: frontend/src/metabase/reference/metrics/MetricSidebar.jsx:33
+#: frontend/src/metabase/reference/segments/SegmentQuestions.jsx:74
+msgid "Questions about {0}"
+msgstr "Perguntas sobre {0}"
+
+#: frontend/src/metabase/reference/databases/TableQuestions.jsx:95
+#: frontend/src/metabase/reference/metrics/MetricQuestions.jsx:99
+#: frontend/src/metabase/reference/segments/SegmentQuestions.jsx:98
+msgid "Created {0} by {1}"
+msgstr "Criado {0} por {1}"
+
+#: frontend/src/metabase/reference/databases/TableSidebar.jsx:37
+msgid "Fields in this table"
+msgstr "Campos nesta tabela"
+
+#: frontend/src/metabase/reference/databases/TableSidebar.jsx:45
+msgid "Questions about this table"
+msgstr "Perguntas sobre esta tabela"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:157
+msgid "Help your team get started with your data."
+msgstr "Ajude sua equipe a começar com seus dados."
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:159
+msgid "Show your team what’s most important by choosing your top dashboard, metrics, and segments."
+msgstr "Mostre à sua equipe o que é mais importante ao escolher seu melhor painel, suas métricas e seus principais segmentos."
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:165
+msgid "Get started"
+msgstr "Começar"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:173
+msgid "Our most important dashboard"
+msgstr "Nosso painel mais importante"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:188
+msgid "Numbers that we pay attention to"
+msgstr "Números aos quais prestamos atenção"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:213
+msgid "Metrics are important numbers your company cares about. They often represent a core indicator of how the business is performing."
+msgstr "As métricas são números importantes que são importantes para sua empresa. Frequentemente eles representam um indicador central de como o negócio está funcionando."
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:221
+msgid "See all metrics"
+msgstr "Veja todas as métricas"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:235
+msgid "Segments and tables"
+msgstr "Segmentos e Tabelas"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:236
+msgid "Tables"
+msgstr "Tabelas"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:262
+msgid "Segments and tables are the building blocks of your company's data. Tables are collections of the raw information while segments are specific slices with specific meanings, like {0}"
+msgstr "Segmentos e tabelas são os componentes básicos dos seus dados companhia Tabelas são coleções de informações sem formatação enquanto os segmentos são partes limitadas com significados específicos, como {0}"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:267
+msgid "Tables are the building blocks of your company's data."
+msgstr "Tabelas são os componentes básicos dos dados da sua empresa."
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:277
+msgid "See all segments"
+msgstr "Veja todos os segmentos"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:293
+msgid "See all tables"
+msgstr "Veja todas as tabelas"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:301
+msgid "Other things to know about our data"
+msgstr "Outras coisas que você deve saber sobre nossos dados"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:302
+msgid "Find out more"
+msgstr "Descubra mais"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:307
+msgid "A good way to get to know your data is by spending a bit of time exploring the different tables and other info available to you. It may take a while, but you'll start to recognize names and meanings over time."
+msgstr "Uma boa maneira de conhecer seus dados é dedicar um pouco de tempo para Explore as diferentes tabelas e outras informações disponíveis. Você pode levar um pouco, mas você começará a reconhecer nomes e significados ao longo do tempo."
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:313
+msgid "Explore our data"
+msgstr "Explore nossos dados"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:321
+msgid "Have questions?"
+msgstr "Você tem alguma pergunta?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuide.jsx:326
+msgid "Contact {0}"
+msgstr "Fale com {0}"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:248
+msgid "Help new Metabase users find their way around."
+msgstr "Ajude os novos usuários do Metabase a encontrar o caminho."
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:251
+msgid "The Getting Started guide highlights the dashboard, metrics, segments, and tables that matter most, and informs your users of important things they should know before digging into the data."
+msgstr "O guia de introdução destaca o painel, as métricas, o segmentos e tabelas mais importantes, e informa os usuários sobre coisas importantes que você deve saber antes de investigar os dados."
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:258
+msgid "Is there an important dashboard for your team?"
+msgstr "Existe um painel importante para sua equipe?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:260
+msgid "Create a dashboard now"
+msgstr "Crie um painel agora"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:266
+msgid "What is your most important dashboard?"
+msgstr "Qual é o seu painel mais importante?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:285
+msgid "Do you have any commonly referenced metrics?"
+msgstr "Você tem alguma métrica comumente referenciada?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:287
+msgid "Learn how to define a metric"
+msgstr "Aprenda a definir uma métrica"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:300
+msgid "What are your 3-5 most commonly referenced metrics?"
+msgstr "Quais são as 3-5 métricas mais referenciadas?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:344
+msgid "Add another metric"
+msgstr "Adicione outra métrica"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:357
+msgid "Do you have any commonly referenced segments or tables?"
+msgstr "Você tem um segmento ou tabela comumente referenciado?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:359
+msgid "Learn how to create a segment"
+msgstr "Aprenda a criar um segmento"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:372
+msgid "What are 3-5 commonly referenced segments or tables that would be useful for this audience?"
+msgstr "Quais são os 3-5 segmentos ou tabelas comumente referenciados que seriam útil para esse público?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:418
+msgid "Add another segment or table"
+msgstr "Adicione outro segmento ou tabela"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:427
+msgid "Is there anything your users should understand or know before they start accessing the data?"
+msgstr "Existe alguma coisa que seus usuários devem entender ou saber antes de começar para acessar os dados?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:433
+msgid "What should a user of this data know before they start accessing it?"
+msgstr "O que um usuário deve saber sobre esses dados antes de começarem a acessar eles?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:437
+msgid "E.g., expectations around data privacy and use, common pitfalls or misunderstandings, information about data warehouse performance, legal notices, etc."
+msgstr "Por exemplo, expectativas sobre privacidade e uso de dados, perigos comuns ou mal-entendidos, informações sobre o desempenho do data warehouse, avisos legais, etc."
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:448
+msgid "Is there someone your users could contact for help if they're confused about this guide?"
+msgstr "Existe alguém com quem seus usuários podem entrar em contato se precisarem de ajuda se Você está confuso sobre este guia?"
+
+#: frontend/src/metabase/reference/guide/GettingStartedGuideEditForm.jsx:457
+msgid "Who should users contact for help if they're confused about this data?"
+msgstr "Com quem os usuários devem entrar em contato para obter ajuda se estiverem confuso sobre esta informação?"
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:75
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:95
+msgid "Please enter a revision message"
+msgstr "Por favor, adicione uma mensagem de revisão"
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:213
+msgid "Why this Metric is interesting"
+msgstr "Por que essa métrica é interessante"
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:223
+msgid "Things to be aware of about this Metric"
+msgstr "Coisas a ter em conta acerca desta métrica"
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:233
+msgid "How this Metric is calculated"
+msgstr "Como essa métrica é calculada"
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:235
+msgid "Nothing on how it's calculated yet"
+msgstr "Nada sobre como foi calculado ainda"
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:293
+msgid "Other fields you can group this metric by"
+msgstr "Outros campos pelos quais você pode agrupar essa métrica"
+
+#: frontend/src/metabase/reference/metrics/MetricDetail.jsx:294
+msgid "Fields you can group this metric by"
+msgstr "Campos pelos quais você pode agrupar essa métrica"
+
+#: frontend/src/metabase/reference/metrics/MetricList.jsx:23
+msgid "Metrics are the official numbers that your team cares about"
+msgstr "As métricas são os números oficiais com os quais sua equipe se preocupa"
+
+#: frontend/src/metabase/reference/metrics/MetricList.jsx:25
+msgid "Metrics will appear here once your admins have created some"
+msgstr "As métricas aparecerão aqui quando seus administradores tiverem criado alguns"
+
+#: frontend/src/metabase/reference/metrics/MetricList.jsx:27
+msgid "Learn how to create metrics"
+msgstr "Aprenda como criar métricas"
+
+#: frontend/src/metabase/reference/metrics/MetricQuestions.jsx:35
+msgid "Questions about this metric will appear here as they're added"
+msgstr "Perguntas sobre essa métrica aparecerão aqui quando forem adicionadas"
+
+#: frontend/src/metabase/reference/metrics/MetricRevisions.jsx:29
+msgid "There are no revisions for this metric"
+msgstr "Não há comentários para esta métrica"
+
+#: frontend/src/metabase/reference/metrics/MetricRevisions.jsx:88
+#: frontend/src/metabase/reference/metrics/MetricSidebar.jsx:47
+#: frontend/src/metabase/reference/segments/SegmentRevisions.jsx:88
+msgid "Revision history for {0}"
+msgstr "Histórico de revisão de {0}"
+
+#: frontend/src/metabase/reference/metrics/MetricSidebar.jsx:39
+msgid "X-ray this metric"
+msgstr "Aplique raios-X a esta métrica"
+
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:217
+msgid "Why this Segment is interesting"
+msgstr "Por que esse segmento é interessante"
+
+#: frontend/src/metabase/reference/segments/SegmentDetail.jsx:227
+msgid "Things to be aware of about this Segment"
+msgstr "Coisas a ter em mente sobre este segmento"
+
+#: frontend/src/metabase/reference/segments/SegmentList.jsx:23
+msgid "Segments are interesting subsets of tables"
+msgstr "Segmentos são subconjuntos interessantes de tabelas"
+
+#: frontend/src/metabase/reference/segments/SegmentList.jsx:24
+msgid "Defining common segments for your team makes it even easier to ask questions"
+msgstr "Definir segmentos comuns para sua equipe facilita ainda mais fazer perguntas"
+
+#: frontend/src/metabase/reference/segments/SegmentList.jsx:25
+msgid "Segments will appear here once your admins have created some"
+msgstr "Os segmentos aparecerão aqui quando os administradores tiverem criado alguns"
+
+#: frontend/src/metabase/reference/segments/SegmentList.jsx:27
+msgid "Learn how to create segments"
+msgstr "Aprenda a criar segmentos"
+
+#: frontend/src/metabase/reference/segments/SegmentQuestions.jsx:35
+msgid "Questions about this segment will appear here as they're added"
+msgstr "Perguntas sobre este segmento aparecerão aqui quando forem adicionadas"
+
+#: frontend/src/metabase/reference/segments/SegmentRevisions.jsx:29
+msgid "There are no revisions for this segment"
+msgstr "Não há comentários para este segmento"
+
+#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:33
+msgid "Fields in this segment"
+msgstr "Campos neste segmento"
+
+#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:39
+msgid "Questions about this segment"
+msgstr "Perguntas sobre este segmento"
+
+#: frontend/src/metabase/reference/segments/SegmentSidebar.jsx:45
+msgid "X-ray this segment"
+msgstr "Aplique raios-X a este segmento"
+
+#: frontend/src/metabase/routes.jsx:182
+msgid "Login"
+msgstr "Iniciar sessão"
+
+#: frontend/src/metabase/nav/containers/Navbar.jsx:130
+#: frontend/src/metabase/routes.jsx:198
+msgid "Search"
+msgstr "Pesquisar"
+
+#: frontend/src/metabase/routes.jsx:217
+msgid "Dashboard"
+msgstr "Painel"
+
+#: frontend/src/metabase/routes.jsx:227
+msgid "New Question"
+msgstr "Nova pergunta"
+
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:125
+msgid "Select the type of Database you use"
+msgstr "Selecione o tipo de banco de dados que você usa"
+
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:141
+msgid "Add your data"
+msgstr "Adicione seus dados"
+
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:145
+msgid "I'll add my own data later"
+msgstr "Eu adicionarei meus próprios dados mais tarde"
+
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:146
+msgid "Connecting to {0}"
+msgstr "Conectando com {0}"
+
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:165
+msgid "You’ll need some info about your database, like the username and password. If you don’t have that right now, Metabase also comes with a sample dataset you can get started with."
+msgstr "Você precisará de informações sobre seu banco de dados, como seu nome de usuário e a senha. Se você não tem isso agora, o Metabase também vem com um conjunto de dados de amostra com os quais você pode começar."
+
+#: frontend/src/metabase/setup/components/DatabaseConnectionStep.jsx:196
+msgid "I'll add my data later"
+msgstr "Eu adicionarei meus dados mais tarde"
+
+#: frontend/src/metabase/setup/components/DatabaseSchedulingStep.jsx:41
+msgid "Control automatic scans"
+msgstr "Controle automático de digitalização"
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:53
+msgid "Usage data preferences"
+msgstr "Preferências de uso de dados"
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:56
+msgid "Thanks for helping us improve"
+msgstr "Obrigado por nos ajudar a melhorar"
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:57
+msgid "We won't collect any usage events"
+msgstr "Nós não coletaremos nenhum evento de uso"
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:76
+msgid "In order to help us improve Metabase, we'd like to collect certain data about usage through Google Analytics."
+msgstr "Para nos ajudar a melhorar o Metabase, gostaríamos de coletar certas informações sobre o uso através do Google Analytics."
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:85
+msgid "Here's a full list of everything we track and why."
+msgstr "Aqui está uma lista completa de tudo que acompanhamos e por quê."
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:98
+msgid "Allow Metabase to anonymously collect usage events"
+msgstr "Permitir que o Metabase colete eventos de uso anonimamente"
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:105
+msgid "Metabase {0} collects anything about your data or question results."
+msgstr "Metabase {0} coleta informações sobre seus dados ou resultados de perguntas."
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:106
+msgid "never"
+msgstr "nunca"
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:108
+msgid "All collection is completely anonymous."
+msgstr "Toda a informação obtida é completamente anônima."
+
+#: frontend/src/metabase/setup/components/PreferencesStep.jsx:110
+msgid "Collection can be turned off at any point in your admin settings."
+msgstr "A coleção pode ser desativada a qualquer momento na seção de configuração"
+
+#: frontend/src/metabase/setup/components/Setup.jsx:45
+msgid "If you feel stuck"
+msgstr "Se você se sentir sobrecarregado"
+
+#: frontend/src/metabase/setup/components/Setup.jsx:52
+msgid "our getting started guide"
+msgstr "nosso guia inicial"
+
+#: frontend/src/metabase/setup/components/Setup.jsx:53
+msgid "is just a click away."
+msgstr "É um único clique."
+
+#: frontend/src/metabase/setup/components/Setup.jsx:95
+msgid "Welcome to Metabase"
+msgstr "Bem vindo ao Metabase"
+
+#: frontend/src/metabase/setup/components/Setup.jsx:96
+msgid "Looks like everything is working. Now let’s get to know you, connect to your data, and start finding you some answers!"
+msgstr "Parece que tudo está funcionando. Agora vamos conhecê-lo, conecte-se com o seu dados e comece a encontrar algumas respostas!"
+
+#: frontend/src/metabase/setup/components/Setup.jsx:100
+msgid "Let's get started"
+msgstr "Vamos começar"
+
+#: frontend/src/metabase/setup/components/Setup.jsx:145
+msgid "You're all set up!"
+msgstr "Estás pronto!"
+
+#: frontend/src/metabase/setup/components/Setup.jsx:156
+msgid "Take me to Metabase"
+msgstr "Leve-me para o Metabase"
+
+#: frontend/src/metabase/setup/components/UserStep.jsx:155
+msgid "What should we call you?"
+msgstr "Como devemos ligar para você?"
+
+#: frontend/src/metabase/setup/components/UserStep.jsx:156
+msgid "Hi, {0}. nice to meet you!"
+msgstr "Olá, {0}. Prazer em conhecê-lo!"
+
+#: frontend/src/metabase/setup/components/UserStep.jsx:243
+msgid "Create a password"
+msgstr "Crie uma senha"
+
+#: frontend/src/metabase/setup/components/UserStep.jsx:259
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:116
+msgid "Shhh..."
+msgstr "Shhh..."
+
+#: frontend/src/metabase/setup/components/UserStep.jsx:269
+msgid "Confirm password"
+msgstr "Confirme a senha"
+
+#: frontend/src/metabase/setup/components/UserStep.jsx:278
+msgid "Shhh... but one more time so we get it right"
+msgstr "Shhh... novamente, para ter certeza de que fazemos certo"
+
+#: frontend/src/metabase/setup/components/UserStep.jsx:287
+msgid "Your company or team name"
+msgstr "O nome da sua empresa ou equipe"
+
+#: frontend/src/metabase/setup/components/UserStep.jsx:296
+msgid "Department of awesome"
+msgstr "Departamento impressionante"
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:26
+msgid "Metabot is admiring your integers…"
+msgstr "O Metabot está admirando seus números inteiros..."
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:27
+msgid "Metabot is performing billions of differential equations…"
+msgstr "O Metabot está fazendo bilhões de equações diferenciais..."
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:28
+msgid "Metabot is doing science…"
+msgstr "O Metabot está fazendo ciência..."
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:29
+msgid "Metabot is checking out your metrics…"
+msgstr "O Metabot está revendo suas métricas..."
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:30
+msgid "Metabot is looking for trends and outliers…"
+msgstr "O Metabot está procurando por tendências e outliers..."
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:31
+msgid "Metabot is consulting the quantum abacus…"
+msgstr "O Metabot está consultando o ábaco quântico..."
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:32
+msgid "Metabot is feeling pretty good about all this…"
+msgstr "O Metabot está se sentindo bem com o que está acontecendo…"
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:52
+msgid "We’ll show you some interesting explorations of your data in\n"
+"just a few minutes."
+msgstr "Vamos mostrar algumas explorações de seus dados em \n"
+"alguns minuros."
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:72
+msgid "This seems to be taking a while. In the meantime, you can check out one of these example explorations to see what Metabase can do for you."
+msgstr "Isso parece estar demorando um pouco. Enquanto isso, você pode ver um destes exemplos de explorações para ver o que o Metabase pode fazer por você."
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:86
+msgid "I took a look at the data you just connected, and I have some explorations of interesting things I found. Hope you like them!"
+msgstr "Analisei os dados que você acabou de conectar e tenho algumas explorações de interessantes. Espero que você goste!"
+
+#: frontend/src/metabase/setup/containers/PostSetupApp.jsx:98
+msgid "I'm done exploring for now"
+msgstr "Acabei de explorar por agora"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:20
+msgid "Welcome to the Query Builder!"
+msgstr "Bem vindo ao editor de consultas!"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:22
+msgid "The Query Builder lets you assemble questions (or \"queries\") to ask about your data."
+msgstr "O editor de consultas te permite construir perguntas (ou \"consultas\") para questionar seus dados."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:26
+msgid "Tell me more"
+msgstr "Me conte mais"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:43
+msgid "Start by picking the table with the data that you have a question about."
+msgstr "Comece escolhendo a tabela com os dados sobre os quais você tem uma pergunta."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:45
+msgid "Go ahead and select the \"Orders\" table from the dropdown menu."
+msgstr "Vá em frente e selecione a tabela \"Pedidos\" no menu dropdown."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:78
+msgid "Filter your data to get just what you want."
+msgstr "Filtre seus dados para obter exatamente o que você deseja."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:79
+msgid "Click the plus button and select the \"Created At\" field."
+msgstr "Clique no sinal de mais e selecione o campo \"Criado em\"."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:93
+msgid "Here we can pick how many days we want to see data for, try 10"
+msgstr "Aqui podemos escolher quantos dias queremos ver os dados, tente 10"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:116
+msgid "Here's where you can choose to add or average your data, count the number of rows in the table, or just view the raw data."
+msgstr "Aqui é onde você pode optar por adicionar ou calcular a média dos seus dados, contar o número de linhas na tabela, ou apenas ver os dados brutos."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:118
+msgid "Try it: click on <strong>Raw Data</strong> to change it to <strong>Count of rows</strong> so we can count how many orders there are in this table."
+msgstr "Experimente: clique em <strong>Dados brutos</strong> para alterá-lo para <strong>Número de linhas</strong> para que possamos contar quantos pedidos existem nesta tabela."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:142
+msgid "Add a grouping to break out your results by category, day, month, and more."
+msgstr "Adicione um agrupamento para dividir seus resultados por categoria, dia, mês e mais"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:144
+msgid "Let's do it: click on <strong>Add a grouping</strong>, and choose <strong>Created At: by Week</strong>."
+msgstr "Vamos fazer isso: clique em <strong>Adicionar um grupo</strong> e escolha <strong>Criado em> por Semana</strong>."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:152
+msgid "Click on \"by day\" to change it to \"Week.\""
+msgstr "Clique em \"por dia\" para mudar para \"Semana\"."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:173
+msgid "Run Your Query."
+msgstr "Execute sua consulta"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:175
+msgid "You're doing so well! Click <strong>Run query</strong> to get your results!"
+msgstr "Você está indo muito bem! Clique em <strong>Executar consulta</strong> para obter seus resultados."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:192
+msgid "You can view your results as a chart instead of a table."
+msgstr "Você pode ver seus resultados como um gráfico em vez de uma tabela."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:194
+msgid "Everbody likes charts! Click the <strong>Visualization</strong> dropdown and select <strong>Line</strong>."
+msgstr "Nós todos gostamos de gráficos! Clique no menu suspenso <strong>Exibir</strong> e selecione <strong>Linha</strong>."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:216
+msgid "Well done!"
+msgstr "Muito bem!"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:218
+msgid "That's all! If you still have questions, check out our"
+msgstr "Isso é tudo! Se você ainda tiver dúvidas, confira nosso"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:223
+msgid "User's Guide"
+msgstr "Guia do usuário"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:223
+msgid "Have fun exploring your data!"
+msgstr "Divirta-se explorando seus dados!"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:226
+msgid "Thanks"
+msgstr "Obrigado"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:235
+msgid "Save Your Questions"
+msgstr "Salve suas dúvidas"
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:237
+msgid "By the way, you can save your questions so you can refer to them later. Saved Questions can also be put into dashboards or Pulses."
+msgstr "A propósito, você pode salvar suas perguntas para que você possa consultá-las mais tarde. Perguntas salvas também podem ser colocadas em painéis ou notificações."
+
+#: frontend/src/metabase/tutorial/QueryBuilderTutorial.jsx:241
+msgid "Sounds good"
+msgstr "Soa bem"
+
+#: frontend/src/metabase/tutorial/Tutorial.jsx:248
+msgid "Whoops!"
+msgstr "Oopps!!"
+
+#: frontend/src/metabase/tutorial/Tutorial.jsx:249
+msgid "Sorry, it looks like something went wrong. Please try restarting the tutorial in a minute."
+msgstr "Desculpe, parece que algo deu errado. Tente reiniciar o tutorial em um minuto."
+
+#: frontend/src/metabase/user/actions.js:34
+msgid "Password updated successfully!"
+msgstr "Senha atualizada corretamente!"
+
+#: frontend/src/metabase/user/actions.js:53
+msgid "Account updated successfully!"
+msgstr "Conta atualizada com sucesso!"
+
+#: frontend/src/metabase/user/components/SetUserPassword.jsx:107
+msgid "Current password"
+msgstr "Senha atual"
+
+#: frontend/src/metabase/user/components/UpdateUserDetails.jsx:137
+msgid "Sign in with Google Email address"
+msgstr "Faça login com o endereço de e-mail do Google"
+
+#: frontend/src/metabase/user/components/UserSettings.jsx:65
+msgid "User Details"
+msgstr "Detalhes do usuário"
+
+#: frontend/src/metabase/visualizations/components/ChartSettings.jsx:225
+msgid "Reset to defaults"
+msgstr "Redefinir para o padrão"
+
+#: frontend/src/metabase/visualizations/components/ChoroplethMap.jsx:123
+msgid "unknown map"
+msgstr "mapa desconhecido"
+
+#: frontend/src/metabase/visualizations/components/LeafletGridHeatMap.jsx:26
+msgid "Grid map requires binned longitude/latitude."
+msgstr "O mapa da grade requer longitude / latitude agrupada."
+
+#: frontend/src/metabase/visualizations/components/LegendVertical.jsx:112
+msgid "more"
+msgstr "mais"
+
+#: frontend/src/metabase/visualizations/components/LineAreaBarChart.jsx:101
+msgid "Which fields do you want to use for the X and Y axes?"
+msgstr "Quais campos você deseja usar para os eixos X e Y?"
+
+#: frontend/src/metabase/visualizations/components/LineAreaBarChart.jsx:103
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:59
+msgid "Choose fields"
+msgstr "Escolha campos"
+
+#: frontend/src/metabase/visualizations/components/PinMap.jsx:204
+msgid "Save as default view"
+msgstr "Salvar como visualização padrão"
+
+#: frontend/src/metabase/visualizations/components/PinMap.jsx:226
+msgid "Draw box to filter"
+msgstr "Desenhar caixa para filtrar"
+
+#: frontend/src/metabase/visualizations/components/PinMap.jsx:226
+msgid "Cancel filter"
+msgstr "Cancelar filtro"
+
+#: frontend/src/metabase/visualizations/components/PinMap.jsx:47
+msgid "Pin Map"
+msgstr "Fixar mapa"
+
+#: frontend/src/metabase/visualizations/components/TableInteractive.jsx:423
+msgid "Unset"
+msgstr "Desmarcar"
+
+#: frontend/src/metabase/visualizations/components/TableSimple.jsx:227
+msgid "Rows {0}-{1} of {2}"
+msgstr "Linhas {0}-{1} de {2}"
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:184
+msgid "Data truncated to {0} rows."
+msgstr "Dados truncados para {0} linhas."
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:349
+msgid "Could not find visualization"
+msgstr "A visualização não pôde ser encontrada"
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:356
+msgid "Could not display this chart with this data."
+msgstr "Não foi possível mostrar este gráfico com essas informações."
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:454
+msgid "No results!"
+msgstr "Nenhum resultado!"
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:475
+msgid "Still Waiting..."
+msgstr "Ainda esperando..."
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:478
+msgid "This usually takes an average of {0}."
+msgstr "Isso geralmente leva cerca de {0}."
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:484
+msgid "(This is a bit long for a dashboard)"
+msgstr "(Isso é um pouco demorado para um painel)"
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:488
+msgid "This is usually pretty fast but seems to be taking awhile right now."
+msgstr "Normalmente isso é bastante rápido, mas parece estar demorando neste momento."
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingFieldPicker.jsx:14
+msgid "Select a field"
+msgstr "Selecione um campo"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingFieldsPicker.jsx:42
+msgid "error"
+msgstr "erro"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingOrderedColumns.jsx:107
+msgid "Click and drag to change their order"
+msgstr "Clique e arraste para mudar a ordenação"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingOrderedColumns.jsx:119
+msgid "Add fields from the list below"
+msgstr "Adicionar campos da lista abaixo"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:24
+msgid "less than"
+msgstr "menos que"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:25
+msgid "greater than"
+msgstr "maior do que"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:26
+msgid "less than or equal to"
+msgstr "menor que ou igual a"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:27
+msgid "greater than or equal to"
+msgstr "maior que ou igual a"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:28
+msgid "equal to"
+msgstr "igual a"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:29
+msgid "not equal to"
+msgstr "diferente de"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:169
+msgid "Conditional formatting"
+msgstr "Formatação condicional"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:171
+msgid "You can add rules to make the cells in this table change color if\n"
+"they meet certain conditions."
+msgstr "Você pode adicionar regrar para fazer as células desta tabela\n"
+"mudarem de cor se atenderem a certas condições."
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:181
+msgid "Add a rule"
+msgstr "Adicionar uma regra"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:186
+msgid "Rules will be applied in this order"
+msgstr "As regras serão aplicadas nesta ordem"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:187
+msgid "Click and drag to reorder."
+msgstr "Clique e arraste para reordenar."
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:220
+msgid "No columns selected"
+msgstr "Não há colunas selecionadas"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:277
+msgid "Cells in this column will be tinted based on their values."
+msgstr "Células nesta coluna serão coloridas de acordo com seus valores."
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:279
+msgid "When a cell in these columns is {0} it will be tinted this color."
+msgstr "Quando uma célula nessas colunas é igual a {0} ela será colorida desta cor."
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:290
+msgid "Which columns should be affected?"
+msgstr "Quais colunas deveriam ser afetadas?"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:300
+msgid "Formatting style"
+msgstr "Estilo de formatação"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:304
+msgid "Single color"
+msgstr "Cor Única"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:305
+msgid "Color range"
+msgstr "Intervalo de Cores"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:312
+msgid "When a cell in this column is…"
+msgstr "Quando a célula nesta coluna é..."
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:325
+msgid "…turn its background this color:"
+msgstr "…ela muda a cor de fundo para esta cor:"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:331
+msgid "Highlight the whole row"
+msgstr "Realçar"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:339
+msgid "Colors"
+msgstr "Cores"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:344
+msgid "Start the range at"
+msgstr "Começar o intervalo em"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:349
+msgid "Smallest value in this column"
+msgstr "Menor valor nesta coluna."
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:351
+msgid "Smallest value in each column"
+msgstr "Menor valor em cada coluna."
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:353
+msgid "Smallest value in all of these columns"
+msgstr "Menor valor em todas as colunas."
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:357
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:379
+msgid "Custom value"
+msgstr "Valor personalizado"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:366
+msgid "End the range at"
+msgstr "Termine o intervalo em"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:371
+msgid "Largest value in this column"
+msgstr "Maior valor nesta coluna"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:373
+msgid "Largest value in each column"
+msgstr "Maior valor em cada coluna"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:375
+msgid "Largest value in all of these columns"
+msgstr "Maior valor em todas essas colunas"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:407
+msgid "Add rule"
+msgstr "Adicionar regra"
+
+#: frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx:407
+msgid "Update rule"
+msgstr "Atualizar regra"
+
+#: frontend/src/metabase/visualizations/index.js:30
+msgid "Visualization is null"
+msgstr "Visualização nula"
+
+#: frontend/src/metabase/visualizations/index.js:35
+msgid "Visualization must define an 'identifier' static variable: "
+msgstr "A visualização deve definir uma variável 'identificadora' estática: "
+
+#: frontend/src/metabase/visualizations/index.js:41
+msgid "Visualization with that identifier is already registered: "
+msgstr "A visualização com este identificador já está registrada: "
+
+#: frontend/src/metabase/visualizations/index.js:69
+msgid "No visualization for {0}"
+msgstr "Nenhuma exibição para {0}"
+
+#: frontend/src/metabase/visualizations/lib/LineAreaBarRenderer.js:71
+msgid "\"{0}\" is an unaggregated field: if it has more than one value at a point on the x-axis, the values will be summed."
+msgstr "\"{0}\" é um campo não agregado: se ele têm mais de um valor em um único ponto no  eixo X, os valores serão somados."
+
+#: frontend/src/metabase/visualizations/lib/LineAreaBarRenderer.js:87
+msgid "This chart type requires at least 2 columns."
+msgstr "Este tipo de gráfico requer pelo menos 2 colunas."
+
+#: frontend/src/metabase/visualizations/lib/LineAreaBarRenderer.js:92
+msgid "This chart type doesn't support more than {0} series of data."
+msgstr "Este tipo de gráfico não suporta mais de {0} séries de dados."
+
+#: frontend/src/metabase/visualizations/lib/LineAreaBarRenderer.js:509
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:42
+msgid "Goal"
+msgstr "Meta"
+
+#: frontend/src/metabase/visualizations/lib/errors.js:9
+msgid "Doh! The data from your query doesn't fit the chosen display choice. This visualization requires at least {0} {1} of data."
+msgstr "Epa! Os dados em sua consulta não correspondem à opção de exibição escolhida. Esta visualização requer pelo menos {0} {1} de dados."
+
+#: frontend/src/metabase/visualizations/lib/errors.js:9
+msgid "column"
+msgid_plural "columns"
+msgstr[0] "coluna"
+msgstr[1] "colunas"
+
+#: frontend/src/metabase/visualizations/lib/errors.js:21
+msgid "No dice. We have {0} data {1} to show and that's not enough for this visualization."
+msgstr "Sem sorte. Temos {0} dados {1} para mostrar, mas não é suficiente esta visualização."
+
+#: frontend/src/metabase/visualizations/lib/errors.js:21
+msgid "point"
+msgid_plural "points"
+msgstr[0] "ponto"
+msgstr[1] "pontos"
+
+#: frontend/src/metabase/visualizations/lib/errors.js:33
+msgid "Bummer. We can't actually do a pin map for this data because we require both a latitude and longitude column."
+msgstr "Putz. Não podemos fazer um mapa de pinos para esses dados porque precisamos de uma coluna de latitude e longitude."
+
+#: frontend/src/metabase/visualizations/lib/errors.js:42
+msgid "Please configure this chart in the chart settings"
+msgstr "Por favor, configure este gráfico na configuração gráfica"
+
+#: frontend/src/metabase/visualizations/lib/errors.js:44
+msgid "Edit Settings"
+msgstr "Editar configuração"
+
+#: frontend/src/metabase/visualizations/lib/fill_data.js:38
+msgid "xValues missing!"
+msgstr "xValores ausentes!"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:56
+#: frontend/src/metabase/visualizations/visualizations/RowChart.jsx:31
+msgid "X-axis"
+msgstr "Eixo X"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:82
+msgid "Add a series breakout..."
+msgstr "Adicione um detalhamento da série..."
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:93
+#: frontend/src/metabase/visualizations/visualizations/RowChart.jsx:35
+msgid "Y-axis"
+msgstr "Eixo Y"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:118
+msgid "Add another series..."
+msgstr "Adicione outra série..."
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:132
+msgid "Bubble size"
+msgstr "Tamanho da bolha"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:161
+#: frontend/src/metabase/visualizations/visualizations/LineChart.jsx:17
+msgid "Line"
+msgstr "Line"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:162
+msgid "Curve"
+msgstr "Curva"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:163
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:67
+msgid "Step"
+msgstr "Passo"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:170
+msgid "Show point markers on lines"
+msgstr "Mostrar marcadores de ponto em linhas"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:178
+msgid "Stacking"
+msgstr "Empilhado"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:182
+msgid "Don't stack"
+msgstr "Não empilhados"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:183
+msgid "Stack"
+msgstr "Pilha"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:184
+msgid "Stack - 100%"
+msgstr "Empilhamento - 100%"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:201
+msgid "Show goal"
+msgstr "Mostrar meta"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:207
+msgid "Goal value"
+msgstr "Valor da meta"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:218
+msgid "Replace missing values with"
+msgstr "Substitua os valores ausentes por"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:223
+msgid "Zero"
+msgstr "Zero"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:224
+msgid "Nothing"
+msgstr "Nada"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:225
+msgid "Linear Interpolated"
+msgstr "Linha interpolada"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:284
+msgid "X-axis scale"
+msgstr "Escala do Eixo X"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:301
+msgid "Timeseries"
+msgstr "Séries cronológica"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:304
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:322
+msgid "Linear"
+msgstr "Linear"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:306
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:323
+msgid "Power"
+msgstr "Expoente"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:307
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:324
+msgid "Log"
+msgstr "Logarítmico"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:309
+msgid "Histogram"
+msgstr "Histograma"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:311
+msgid "Ordinal"
+msgstr "Ordinal"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:317
+msgid "Y-axis scale"
+msgstr "Escala do Eixo Y"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:330
+msgid "Show x-axis line and marks"
+msgstr "Mostrar marcas de linha e eixo X"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:336
+msgid "Compact"
+msgstr "Compacto"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:337
+msgid "Rotate 45°"
+msgstr "Rodar 45°"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:338
+msgid "Rotate 90°"
+msgstr "Rodar 90°"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:345
+msgid "Show y-axis line and marks"
+msgstr "Mostrar linha e marcas do eixo Y"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:357
+msgid "Auto y-axis range"
+msgstr "Eixo Y automático"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:401
+msgid "Use a split y-axis when necessary"
+msgstr "Use um eixo Y dividido quando necessário"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:408
+msgid "Show label on x-axis"
+msgstr "Mostrar rótulo no eixo X"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:414
+msgid "X-axis label"
+msgstr "Etiqueta do eixo X"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:423
+msgid "Show label on y-axis"
+msgstr "Mostrar rótulo no eixo Y"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:429
+msgid "Y-axis label"
+msgstr "Etiqueta do eixo Y"
+
+#: frontend/src/metabase/visualizations/lib/utils.js:116
+msgid "Standard Deviation"
+msgstr "Desvio padrão"
+
+#: frontend/src/metabase/visualizations/visualizations/AreaChart.jsx:18
+msgid "Area"
+msgstr "Área"
+
+#: frontend/src/metabase/visualizations/visualizations/AreaChart.jsx:21
+msgid "area chart"
+msgstr "gráfico de área"
+
+#: frontend/src/metabase/visualizations/visualizations/BarChart.jsx:16
+msgid "Bar"
+msgstr "Barra"
+
+#: frontend/src/metabase/visualizations/visualizations/BarChart.jsx:19
+msgid "bar chart"
+msgstr "gráfico de barras"
+
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:57
+msgid "Which fields do you want to use?"
+msgstr "Quais campos você deseja usar?"
+
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:31
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:85
+msgid "Funnel"
+msgstr "Funil"
+
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:74
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:67
+msgid "Measure"
+msgstr "Medida"
+
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:80
+msgid "Funnel type"
+msgstr "Tipo de Funil"
+
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:86
+msgid "Bar chart"
+msgstr "Gráfico de barras"
+
+#: frontend/src/metabase/visualizations/visualizations/LineChart.jsx:20
+msgid "line chart"
+msgstr "gráfico de linhas"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:211
+msgid "Please select longitude and latitude columns in the chart settings."
+msgstr "Selecione as colunas de longitude e latitude na configuração do gráfico"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:217
+msgid "Please select a region map."
+msgstr "Por favor, selecione um mapa da região."
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:221
+msgid "Please select region and metric columns in the chart settings."
+msgstr "Selecione as colunas e métricas da região nas configurações do gráfico."
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:31
+msgid "Map"
+msgstr "Mapa"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:45
+msgid "Map type"
+msgstr "Tipo de mapa"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:49
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:146
+msgid "Region map"
+msgstr "Mapa da região"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:50
+msgid "Pin map"
+msgstr "Fixar mapa"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:96
+msgid "Pin type"
+msgstr "Tipo de pino"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:101
+msgid "Tiles"
+msgstr "Azulejos"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:102
+msgid "Markers"
+msgstr "Marcadores"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:118
+msgid "Latitude field"
+msgstr "Campo de latitude"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:128
+msgid "Longitude field"
+msgstr "Campo de longitude"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:138
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:165
+msgid "Metric field"
+msgstr "Campo métrico"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:170
+msgid "Region field"
+msgstr "Campo de região"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:179
+msgid "Radius"
+msgstr "Rádio"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:185
+msgid "Blur"
+msgstr "Borrão"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:191
+msgid "Min Opacity"
+msgstr "Opacidade mínima"
+
+#: frontend/src/metabase/visualizations/visualizations/Map.jsx:197
+msgid "Max Zoom"
+msgstr "Zoom máximo"
+
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:175
+msgid "No relationships found."
+msgstr "Nenhum relacionamento foi encontrado."
+
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:213
+msgid "via {0}"
+msgstr "via {0}"
+
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:290
+msgid "This {0} is connected to:"
+msgstr "Este {0} está conectado a:"
+
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:47
+msgid "Object Detail"
+msgstr "Detalhe do Objeto"
+
+#: frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx:50
+msgid "object"
+msgstr "objeto"
+
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:254
+msgid "Total"
+msgstr "Total"
+
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:53
+msgid "Which columns do you want to use?"
+msgstr "Quais colunas você deseja usar?"
+
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:40
+msgid "Pie"
+msgstr "Pizza"
+
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:62
+msgid "Dimension"
+msgstr "Dimensão"
+
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:72
+msgid "Show legend"
+msgstr "Mostrar legenda"
+
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:77
+msgid "Show percentages in legend"
+msgstr "Mostrar percentagens na legenda"
+
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:83
+msgid "Minimum slice percentage"
+msgstr "Percentagem mínima de porção"
+
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:136
+msgid "Goal met"
+msgstr "Meta atingida"
+
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:138
+msgid "Goal exceeded"
+msgstr "Meta excedida"
+
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:208
+msgid "Goal {0}"
+msgstr "Meta {0}"
+
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:35
+msgid "Progress visualization requires a number."
+msgstr "A exibição de progresso requer um número."
+
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:23
+msgid "Progress"
+msgstr "Progresso"
+
+#: frontend/src/metabase/entities/collections.js:101
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:48
+msgid "Color"
+msgstr "Cor"
+
+#: frontend/src/metabase/visualizations/visualizations/RowChart.jsx:13
+msgid "Row Chart"
+msgstr "Gráfico de linhas"
+
+#: frontend/src/metabase/visualizations/visualizations/RowChart.jsx:16
+msgid "row chart"
+msgstr "gráfico de linha"
+
+#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:75
+msgid "Separator style"
+msgstr "Estilo separador"
+
+#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:88
+msgid "Number of decimal places"
+msgstr "Número de decimais"
+
+#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:92
+msgid "Add a prefix"
+msgstr "Adicione um prefixo"
+
+#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:96
+msgid "Add a suffix"
+msgstr "Adicione um sufixo"
+
+#: frontend/src/metabase/visualizations/visualizations/Scalar.jsx:100
+msgid "Multiply by a number"
+msgstr "Multiplique por um número"
+
+#: frontend/src/metabase/visualizations/visualizations/ScatterPlot.jsx:16
+msgid "Scatter"
+msgstr "Dispersão"
+
+#: frontend/src/metabase/visualizations/visualizations/ScatterPlot.jsx:19
+msgid "scatter plot"
+msgstr "gráfico de dispersão"
+
+#: frontend/src/metabase/visualizations/visualizations/Table.jsx:61
+msgid "Pivot the table"
+msgstr "Converter em tabela dinâmica"
+
+#: frontend/src/metabase/visualizations/visualizations/Table.jsx:73
+msgid "Visible fields"
+msgstr "Campos visíveis"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:167
+msgid "Write here, and use Markdown if you''d like"
+msgstr "Escreva aqui, e use Markdown se você quiser"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:73
+msgid "Vertical Alignment"
+msgstr "Alinhamento Vertical"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:77
+msgid "Top"
+msgstr "Topo"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:78
+msgid "Middle"
+msgstr "Meio"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:79
+msgid "Bottom"
+msgstr "Inferior"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:86
+msgid "Horizontal Alignment"
+msgstr "Alinhamento Horizontal"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:90
+msgid "Left"
+msgstr "Esquerda"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:91
+msgid "Center"
+msgstr "Centro"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:92
+msgid "Right"
+msgstr "Direita"
+
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:99
+msgid "Show background"
+msgstr "Mostrar fundo"
+
+#: frontend/src/metabase-lib/lib/Dimension.js:497
+msgid "{0} bin"
+msgid_plural "{0} bins"
+msgstr[0] "{0} agrupamento"
+msgstr[1] "{0} agrupamentos"
+
+#: frontend/src/metabase-lib/lib/Dimension.js:503
+msgid "Auto binned"
+msgstr "Auto agrupado"
+
+#: src/metabase/api/alert.clj
+msgid "DELETE /api/alert/:id is deprecated. Instead, change its `archived` value via PUT /api/alert/:id."
+msgstr "DELETE /api/alert/:id está desatualizado. Ao invés, mude o valor `archived` via PUT /api/alert/:id."
+
+#: src/metabase/api/automagic_dashboards.clj
+msgid "invalid show value"
+msgstr "valor de exibição inválido"
+
+#: src/metabase/api/automagic_dashboards.clj
+msgid "invalid value for prefix"
+msgstr "valor inválido para prefixo"
+
+#: src/metabase/api/automagic_dashboards.clj
+msgid "invalid value for rule name"
+msgstr "valor inválido para nome de regra"
+
+#: src/metabase/api/automagic_dashboards.clj
+msgid "value couldn''t be parsed as base64 encoded JSON"
+msgstr "valor não pode ser convertido como JSON codificado em base64"
+
+#: src/metabase/api/automagic_dashboards.clj
+msgid "Invalid entity type"
+msgstr "Tipo de entidade inválido"
+
+#: src/metabase/api/automagic_dashboards.clj
+msgid "Invalid comparison entity type. Can only be one of \"table\", \"segment\", or \"adhoc\""
+msgstr "Tipo de entidade de comparação inválida. Pode ser somente um de: \"tabela\", \"segmento\", ou \"adhoc\""
+
+#: src/metabase/api/card.clj
+msgid "Error running query to determine Card result metadata:"
+msgstr "Erro ao executar a query para determinar o resultado de metadados do cartão:"
+
+#. Ajustado, o enter estava travando a compilação
+#: src/metabase/api/card.clj
+msgid "DELETE /api/card/:id is deprecated. Instead, change its `archived` value via PUT /api/card/:id."
+msgstr "DELETE /api/card/:id está desatualizado. Ao invés, mude o valor `archived` via PUT /api/card/:id."
+
+#: src/metabase/api/common.clj src/metabase/api/common/internal.clj
+msgid "Invalid field: {0}"
+msgstr "Campo inválido: {0}"
+
+#: src/metabase/api/common.clj
+msgid "Invalid value ''{0}'' for ''{1}'': {2}"
+msgstr "Valor inválido ''{0}'' para ''{1}'': {2}"
+
+#: src/metabase/api/common.clj
+msgid "must be one of: {0}"
+msgstr "deve ser um de: {0}."
+
+#: src/metabase/api/common.clj
+msgid "Invalid Request."
+msgstr "Requisição inválida"
+
+#: src/metabase/api/common.clj
+msgid "Not found."
+msgstr "Não encontrado"
+
+#: src/metabase/api/common.clj
+msgid "You don''t have permissions to do that."
+msgstr "Desculpe, você não tem permissão para fazer isso."
+
+#: src/metabase/api/common.clj
+msgid "Internal server error."
+msgstr "Erro interno do servidor"
+
+#: src/metabase/api/common.clj
+msgid "Warning: endpoint {0}/{1} does not have a docstring."
+msgstr "Aviso: o endpoint {0}/{1} não tem uma docstring"
+
+#: src/metabase/api/common.clj
+msgid "starting streaming request"
+msgstr "iniciando a requisição de fluxo"
+
+#: src/metabase/api/common.clj
+msgid "connection closed, canceling request"
+msgstr "conexão fechada, cancelando requisição"
+
+#. a newline padding character as it's harmless and will allow us to check if the client is connected. If
+#. sending this character fails because the connection is closed, the chan will then close.  Newlines are
+#. no-ops when reading JSON which this depends upon.
+#: src/metabase/api/common.clj
+msgid "Response not ready, writing one byte & sleeping..."
+msgstr "Resposta não está pronta, escrevendo um byte e pausando..."
+
+#: src/metabase/api/common.clj
+msgid "Public sharing is not enabled."
+msgstr "Compartilhamento público não está habilitado"
+
+#: src/metabase/api/common.clj
+msgid "Embedding is not enabled."
+msgstr "Inserção não está habilitada"
+
+#: src/metabase/api/common.clj
+msgid "The object has been archived."
+msgstr "Este objeto foi arquivado"
+
+#: src/metabase/api/common/internal.clj
+msgid "Attempted to return a boolean as an API response. This is not allowed!"
+msgstr "Tentativa de retornar booleano como resposta de API. Isso não é permitido!"
+
+#: src/metabase/api/dataset.clj
+msgid "Source query for this query is Card {0}"
+msgstr "Consulta original para esta consulta é o Cartão {0}"
+
+#: src/metabase/api/dataset.clj
+msgid "Invalid export format: {0}"
+msgstr "Formato de exportação inválido: {0}"
+
+#: src/metabase/api/geojson.clj
+msgid "Invalid JSON URL or resource: {0}"
+msgstr "URL JSON inválida ou recurso: {0}"
+
+#: src/metabase/api/geojson.clj
+msgid "JSON containing information about custom GeoJSON files for use in map visualizations instead of the default US State or World GeoJSON."
+msgstr "Arquivos JSON contendo informações sobre arquivos GeoJSON customizados para uso em visualização de mapas ao invés dos arquivos padrões dos EUA e Mundo."
+
+#: src/metabase/api/geojson.clj
+msgid "Invalid custom GeoJSON key: {0}"
+msgstr "Chave de GeoJSON customizado inválido: {0}"
+
+#. ...but if we *still* couldn't find a match, throw an Exception, because we don't want people
+#. trying to inject new params
+#: src/metabase/api/public.clj
+msgid "Invalid param: {0}"
+msgstr "Parâmetros inválidos: {0}"
+
+#. Ajustado, o enter estava travando a compilação
+#: src/metabase/api/pulse.clj
+msgid "DELETE /api/pulse/:id is deprecated. Instead, change its `archived` value via PUT /api/pulse/:id."
+msgstr "DELETE /api/pulse/:id está desatualizado. Ao invés mude o valor `archived` via PUT /api/pulse/:id."
+
+#: src/metabase/api/routes.clj
+msgid "API endpoint does not exist."
+msgstr "Endpoint da API não existe."
+
+#: src/metabase/api/session.clj
+msgid "Password did not match stored password."
+msgstr "A senha não corresponde com a senha gravada."
+
+#: src/metabase/api/session.clj
+msgid "did not match stored password"
+msgstr "não correspondeu com a senha gravada"
+
+#: src/metabase/api/session.clj
+msgid "Problem connecting to LDAP server, will fallback to local authentication {0}"
+msgstr "Problema ao conectar ao servidor LDAP, irá voltar à autenticação local {0}"
+
+#: src/metabase/api/session.clj
+msgid "Invalid reset token"
+msgstr "Token de reset inválido"
+
+#: src/metabase/api/session.clj
+msgid "Client ID for Google Auth SSO. If this is set, Google Auth is considered to be enabled."
+msgstr "ID de usuário para o Google Auth SSO. Se estiver habilitado, o Google Auth seráconsiderado habilitado."
+
+#: src/metabase/api/session.clj
+msgid "When set, allow users to sign up on their own if their Google account email address is from this domain."
+msgstr "Permite que os usuários se registrem sozinhos se o endereço de e-mail Google é deste domínio."
+
+#: src/metabase/api/session.clj
+msgid "Invalid Google Auth token."
+msgstr "Token de autenticação do Google inválido."
+
+#: src/metabase/api/session.clj
+msgid "Email is not verified."
+msgstr "E-mail não verificado"
+
+#: src/metabase/api/session.clj
+msgid "You''ll need an administrator to create a Metabase account before you can use Google to log in."
+msgstr "Você precisará de um administrador para criar uma conta do Metabase antes de poder usar o Google para fazer login."
+
+#: src/metabase/api/session.clj
+msgid "Successfully authenticated Google Auth token for: {0} {1}"
+msgstr "Autenticado com sucesso usando o token Google para: {0} {1}"
+
+#: src/metabase/api/setup.clj
+msgid "Add a database"
+msgstr "Adicionar um banco de dados"
+
+#: src/metabase/api/setup.clj
+msgid "Get connected"
+msgstr "Conectar"
+
+#: src/metabase/api/setup.clj
+msgid "Connect to your data so your whole team can start to explore."
+msgstr "Conecte seus dados para que toda a equipe possa começar a explorar."
+
+#: src/metabase/api/setup.clj
+msgid "Set up email"
+msgstr "Configurar o e-mail"
+
+#: src/metabase/api/setup.clj
+msgid "Add email credentials so you can more easily invite team members and get updates via Pulses."
+msgstr "Adicione credenciais de e-mail para que você possa convidar mais facilmente para os membros da equipe e obter atualizações através de Notificações"
+
+#: src/metabase/api/setup.clj
+msgid "Set Slack credentials"
+msgstr "Definir credenciais do Slack"
+
+#: src/metabase/api/setup.clj
+msgid "Does your team use Slack? If so, you can send automated updates via pulses and ask questions with MetaBot."
+msgstr "Sua equipe usa o Slack? Em caso afirmativo, você pode enviar atualizações automáticas por notificações e fazer perguntas com MedaBot."
+
+#: src/metabase/api/setup.clj
+msgid "Invite team members"
+msgstr "Convidar membros da equipe"
+
+#: src/metabase/api/setup.clj
+msgid "Share answers and data with the rest of your team."
+msgstr "Compartilhe respostas e dados com o restante de sua equipe."
+
+#: src/metabase/api/setup.clj
+msgid "Hide irrelevant tables"
+msgstr "Ocultar tabelas irrelevantes"
+
+#: src/metabase/api/setup.clj
+msgid "Curate your data"
+msgstr "Mime seus dados"
+
+#: src/metabase/api/setup.clj
+msgid "If your data contains technical or irrelevant info you can hide it."
+msgstr "Se os seus dados contiverem informações técnicas ou irrelevantes, você poderá ocultá-los."
+
+#: src/metabase/api/setup.clj
+msgid "Organize questions"
+msgstr "Organize perguntas"
+
+#: src/metabase/api/setup.clj
+msgid "Have a lot of saved questions in {0}? Create collections to help manage them and add context."
+msgstr "Você tem muitas perguntas salvas em {0}? Crie coleções para ajudar Gerencie-os e adicione contexto."
+
+#. 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
+#: src/metabase/api/setup.clj
+msgid "Metabase"
+msgstr "Metabase"
+
+#: src/metabase/api/setup.clj
+msgid "Create metrics"
+msgstr "Crie métricas"
+
+#: src/metabase/api/setup.clj
+msgid "Define canonical metrics to make it easier for the rest of your team to get the right answers."
+msgstr "Defina métricas canônicas para que o restante de sua equipe receba o respostas corretas"
+
+#: src/metabase/api/setup.clj
+msgid "Create segments"
+msgstr "Criar segmantos"
+
+#: src/metabase/api/setup.clj
+msgid "Keep everyone on the same page by creating canonical sets of filters anyone can use while asking questions."
+msgstr "Mantém todos na mesma página, criando conjuntos filtros canônicos que qualquer um pode usar ao fazer perguntas."
+
+#: src/metabase/api/table.clj
+msgid "Table ''{0}'' is now visible. Resyncing."
+msgstr "Tabela ''{0}'' está visível. Resincronizando."
+
+#: src/metabase/api/table.clj
+msgid "Auto bin"
+msgstr "Agrupamento automático"
+
+#: src/metabase/api/table.clj
+msgid "Don''t bin"
+msgstr "Não Agrupar"
+
+#: src/metabase/api/table.clj
+msgid "Day"
+msgstr "Dia"
+
+#. note the order of these options corresponds to the order they will be shown to the user in the UI
+#: src/metabase/api/table.clj
+msgid "Minute"
+msgstr "Minuto"
+
+#: src/metabase/api/table.clj
+msgid "Hour"
+msgstr "Hora"
+
+#: src/metabase/api/table.clj
+msgid "Quarter"
+msgstr "Trimestre"
+
+#: src/metabase/api/table.clj
+msgid "Minute of Hour"
+msgstr "Minuto da Hora"
+
+#: src/metabase/api/table.clj
+msgid "Hour of Day"
+msgstr "Hora do Dia"
+
+#: src/metabase/api/table.clj
+msgid "Day of Week"
+msgstr "Dia da Semana"
+
+#: src/metabase/api/table.clj
+msgid "Day of Month"
+msgstr "Dia do mês"
+
+#: src/metabase/api/table.clj
+msgid "Day of Year"
+msgstr "Dia do Ano"
+
+#: src/metabase/api/table.clj
+msgid "Week of Year"
+msgstr "Semana do Ano"
+
+#: src/metabase/api/table.clj
+msgid "Month of Year"
+msgstr "Mês do Ano"
+
+#: src/metabase/api/table.clj
+msgid "Quarter of Year"
+msgstr "Trimestre do ano"
+
+#: src/metabase/api/table.clj
+msgid "10 bins"
+msgstr "10 grupos"
+
+#: src/metabase/api/table.clj
+msgid "50 bins"
+msgstr "50 grupos"
+
+#: src/metabase/api/table.clj
+msgid "100 bins"
+msgstr "100 grupos"
+
+#: src/metabase/api/table.clj
+msgid "Bin every 0.1 degrees"
+msgstr "Agrupe a cada 0.1 grau"
+
+#: src/metabase/api/table.clj
+msgid "Bin every 1 degree"
+msgstr "Agrupe a cada 1 grau"
+
+#: src/metabase/api/table.clj
+msgid "Bin every 10 degrees"
+msgstr "Agrupe a cada 10 graus"
+
+#: src/metabase/api/table.clj
+msgid "Bin every 20 degrees"
+msgstr "Agrupe a cada 20 graus"
+
+#. returns `true` if successful -- see JavaDoc
+#: src/metabase/api/tiles.clj src/metabase/pulse/render.clj
+msgid "No appropriate image writer found!"
+msgstr "Escritor de imagens apropriado não foi encontrado!"
+
+#: src/metabase/api/user.clj
+msgid "Email address already in use."
+msgstr "E-mail em uso"
+
+#: src/metabase/api/user.clj
+msgid "Email address already associated to another user."
+msgstr "E-mail está vinculado a outro usuário."
+
+#: src/metabase/api/user.clj
+msgid "Not able to reactivate an active user"
+msgstr "Não foi possível reativar um usuário ativo"
+
+#: src/metabase/api/user.clj
+msgid "Invalid password"
+msgstr "Senha inválida"
+
+#: src/metabase/automagic_dashboards/comparison.clj
+msgid "All {0}"
+msgstr "Todos {0}"
+
+#: src/metabase/automagic_dashboards/comparison.clj
+msgid "{0}, all {1}"
+msgstr "{0}, todos {1}"
+
+#: src/metabase/automagic_dashboards/comparison.clj
+msgid "Comparison of {0} and {1}"
+msgstr "Comparação de {0} e {1}"
+
+#: src/metabase/automagic_dashboards/comparison.clj
+msgid "Automatically generated comparison dashboard comparing {0} and {1}"
+msgstr "Painel de comparação gerado automaticamente, comparando {0} e {1}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "sum"
+msgstr "soma"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "average"
+msgstr "média"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "minumum"
+msgstr "minimo"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "maximum"
+msgstr "máximo"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "distinct count"
+msgstr "contagem distinta"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "standard deviation"
+msgstr "desvio padrão"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "cumulative count"
+msgstr "contagem acumulativa"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "cumulative sum"
+msgstr "incremento"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} and {1}"
+msgstr "{0} e {1}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} of {1}"
+msgstr "{0} de {1}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} by {1}"
+msgstr "{0} por {1}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} in the {1} segment"
+msgstr "{0] no segmento {1}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} segment"
+msgstr "{0} segmento"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} metric"
+msgstr "{0} métrica"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} field"
+msgstr "{0} campo"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "\"{0}\" question"
+msgstr "\"{0}\"questão"
+
+#: src/metabase/automagic_dashboards/comparison.clj
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Compare with {0}"
+msgstr "Comparação com {0}"
+
+#: src/metabase/automagic_dashboards/comparison.clj
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Compare with entire dataset"
+msgstr "Compare com todo o conjunto de dados"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Applying heuristic %s to %s."
+msgstr "Aplicando heurística %s para %s."
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Dimensions bindings:n%s"
+msgstr "Vinculações de dimensões:n%s"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Using definitions:nMetrics:n%snFilters:n%s"
+msgstr "Usando definições:nMétricas:n%snFiltros:n%s"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Can''t create dashboard for {0}"
+msgstr "Não foi possível criar um painel para {0}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0}st"
+msgstr "{0} primeiro"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0}nd"
+msgstr "{0}º"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0}rd"
+msgstr "{0}º"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0}th"
+msgstr "{0}º"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "at {0}"
+msgstr "em {0}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "on {0}"
+msgstr "no {0}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "in {0} week - {1}"
+msgstr "em {0} semana -{1}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "in {0}"
+msgstr "em {0}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "in Q{0} - {1}"
+msgstr "em Q{0} - {1}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "Q{0}"
+msgstr "Q{0}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} is {1}"
+msgstr "{0} é {1}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} is between {1} and {2}"
+msgstr "{0} está entre {1} e {2}"
+
+#. Ajustado, o enter estava travando a compilação
+#: src/metabase/automagic_dashboards/core.clj
+msgid "{0} is between {1} and {2}; and {3} is between {4} and {5}"
+msgstr "{0} está entre {1} e {2}; e {3} está entre {4} e {5}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "where {0}"
+msgstr "onde {0}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "A closer look at {0}"
+msgstr "Verifique melhor em {0}"
+
+#: src/metabase/automagic_dashboards/core.clj
+msgid "A closer look at the {0}"
+msgstr "Um olhar mais atento em {0}"
+
+#: src/metabase/automagic_dashboards/populate.clj
+msgid "Adding %s cards to dashboard %s:n%s"
+msgstr "Adicionando %s cartões ao Painel %s:n%s"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "0 <= score <= {0}"
+msgstr "0 <= pontos <= {0}"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "1 <= width <= {0}"
+msgstr "1 <= largura <= {0}"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid metrics references"
+msgstr "Referência válida de métricas"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid filters references"
+msgstr "Referências válidas de filtros"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid group references"
+msgstr "Referências válidas de grupos"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid order_by references"
+msgstr "Referências order_by válidas"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid dashboard filters references"
+msgstr "Referências a filtros de painéis válidas"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid dimension references"
+msgstr "Referências de dimensões válidas"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Valid card dimension references"
+msgstr "Referências de cartões válidas"
+
+#: src/metabase/automagic_dashboards/rules.clj
+msgid "Error parsing %s:n%s"
+msgstr "Erro ao identificar %s:n%s"
+
+#: src/metabase/cmd/reset_password.clj
+msgid "No user found with email address ''{0}''. "
+msgstr "Nenhum usuário encontrado com email ''{0}''. "
+
+#: src/metabase/cmd/reset_password.clj
+msgid "Please check the spelling and try again."
+msgstr "Por favor verifique a ortografia e tente novamente."
+
+#: src/metabase/cmd/reset_password.clj
+msgid "Resetting password for {0}..."
+msgstr "Resetando senha de {0}..."
+
+#: src/metabase/cmd/reset_password.clj
+msgid "OK [[[{0}]]]"
+msgstr "OK [[[{0}]]]"
+
+#: src/metabase/cmd/reset_password.clj
+msgid "FAIL [[[{0}]]]"
+msgstr "FALHA [[[{0}]]]"
+
+#: src/metabase/core.clj
+msgid "Please use the following URL to setup your Metabase installation:"
+msgstr "Use a seguinte URL para customizar a sua instalação do Metabase"
+
+#: src/metabase/core.clj
+msgid "Metabase Shutting Down ..."
+msgstr "O Metabase está desligando..."
+
+#: src/metabase/core.clj
+msgid "Metabase Shutdown COMPLETE"
+msgstr "Desligamento do Metabase COMPLETO"
+
+#: src/metabase/core.clj
+msgid "Starting Metabase version {0} ..."
+msgstr "Iniciando o Metabase versão {0} ..."
+
+#: src/metabase/core.clj
+msgid "System timezone is ''{0}'' ..."
+msgstr "O fuzo horário do sistema é ''{0}'' ..."
+
+#. startup database.  validates connection & runs any necessary migrations
+#: src/metabase/core.clj
+msgid "Setting up and migrating Metabase DB. Please sit tight, this may take a minute..."
+msgstr "Configurando e migrando o BD do Metabase. Por favor aguarde, isso pode demorar alguns minutos."
+
+#: src/metabase/core.clj
+msgid "Looks like this is a new installation ... preparing setup wizard"
+msgstr "Parece uma instalação nova.... preparando o assistente de instalação"
+
+#: src/metabase/core.clj
+msgid "Metabase Initialization COMPLETE"
+msgstr "Inicialização do Metabase COMPLETA"
+
+#: src/metabase/core.clj
+msgid "Launching Embedded Jetty Webserver with config:"
+msgstr "Iniciando o servidor web Jetty incluído com a configuração:"
+
+#: src/metabase/core.clj
+msgid "Shutting Down Embedded Jetty Webserver"
+msgstr "Desligando o servidor web Jetty"
+
+#: src/metabase/core.clj
+msgid "Starting Metabase in STANDALONE mode"
+msgstr "Iniciando o Metabase no modo INDIVIDUAL"
+
+#: src/metabase/core.clj
+msgid "Metabase Initialization FAILED"
+msgstr "A Inicialização do Metabase FALHOU"
+
+#: src/metabase/db.clj
+msgid "Database has migration lock; cannot run migrations."
+msgstr "Banco de dados com trava de migração; não é possivel rodar migrações."
+
+#: src/metabase/db.clj
+msgid "You can force-release these locks by running `java -jar metabase.jar migrate release-locks`."
+msgstr "Você pode forçar a liberação da trava usando o comando `java -jar metabase.jar migrate release-locks`."
+
+#: src/metabase/db.clj
+msgid "Checking if Database has unrun migrations..."
+msgstr "Checando se o banco de dados tem comandos de migração pendentes..."
+
+#: src/metabase/db.clj
+msgid "Database has unrun migrations. Waiting for migration lock to be cleared..."
+msgstr "Banco de dados está com comandos de migração pendentes. Esperando a liberação da trava..."
+
+#: src/metabase/db.clj
+msgid "Migration lock is cleared. Running migrations..."
+msgstr "Trava de migração liberada. Executando comandos de migração..."
+
+#: src/metabase/db.clj
+msgid "Migration lock cleared, but nothing to do here! Migrations were finished by another instance."
+msgstr "Trava de migração liberada, mas não foram executados comandos! As migrações foram finalizadas por outra instância."
+
+#. Set up liquibase and let it do its thing
+#: src/metabase/db.clj
+msgid "Setting up Liquibase..."
+msgstr "Configurando o Liquibase..."
+
+#: src/metabase/db.clj
+msgid "Liquibase is ready."
+msgstr "Liquibase está pronto."
+
+#: src/metabase/db.clj
+msgid "Verifying {0} Database Connection ..."
+msgstr "Verificando {0} conexão com o banco de dados..."
+
+#: src/metabase/db.clj
+msgid "Verify Database Connection ... "
+msgstr "Verificar conexão com o banco de dados... "
+
+#: src/metabase/db.clj
+msgid "Running Database Migrations..."
+msgstr "Executando migrações do banco de dados..."
+
+#: src/metabase/db.clj
+msgid "Database Migrations Current ... "
+msgstr "Migração do banco de dados atual...␣"
+
+#: src/metabase/driver.clj
+msgid "Hmm, we couldn''t connect to the database."
+msgstr "Hmm, não conseguimos conectar ao banco de dados."
+
+#: src/metabase/driver.clj
+msgid "Make sure your host and port settings are correct"
+msgstr "Confirme que o servidor e porta estão corretos"
+
+#: src/metabase/driver.clj
+msgid "We couldn''t connect to the ssh tunnel host."
+msgstr "Não conseguimos conectar ao tunel ssh para o servidor"
+
+#: src/metabase/driver.clj
+msgid "Check the username, password."
+msgstr "Verifique o nome de usuário, senha"
+
+#: src/metabase/driver.clj
+msgid "Check the hostname and port."
+msgstr "Verifique o nome e a porta."
+
+#: src/metabase/driver.clj
+msgid "Looks like the database name is incorrect."
+msgstr "Parece que o nome do banco de dados está incorreto."
+
+#: src/metabase/driver.clj
+msgid "It looks like your host is invalid."
+msgstr "Parece que o seu servidor é inválido."
+
+#: src/metabase/driver.clj
+msgid "Please double-check it and try again."
+msgstr "Por favor verifique os dados e tente novamente."
+
+#: src/metabase/driver.clj
+msgid "Looks like your password is incorrect."
+msgstr "Parece que sua senha está incorreta"
+
+#: src/metabase/driver.clj
+msgid "Looks like you forgot to enter your password."
+msgstr "Parece que você esqueceu de digitar sua senha."
+
+#: src/metabase/driver.clj
+msgid "Looks like your username is incorrect."
+msgstr "Parece que seu nome de usuário está incorreto."
+
+#: src/metabase/driver.clj
+msgid "Looks like the username or password is incorrect."
+msgstr "Parece que seu nome de usuário ou senha está incorreto."
+
+#. ## CONFIG
+#: src/metabase/driver.clj
+msgid "Connection timezone to use when executing queries. Defaults to system timezone."
+msgstr "Fuso horário a ser utilizado quando executando consultas. O padrão é o fuso do sistema."
+
+#: src/metabase/driver.clj
+msgid "Registered driver {0} {1}"
+msgstr "Driver registrado {0} {1}"
+
+#: src/metabase/driver.clj
+msgid "No -init-driver function found for ''{0}''"
+msgstr "Sem função -init-driver encontrada para ''{0}''"
+
+#: src/metabase/driver.clj
+msgid "Unable to parse date string ''{0}'' for database engine ''{1}''"
+msgstr "Impossível de identificar data no texto ''{0}'' para o banco de dados ''{1}''"
+
+#. all-NULL columns in DBs like Mongo w/o explicit types
+#: src/metabase/driver.clj
+msgid "Don''t know how to map class ''{0}'' to a Field base_type, falling back to :type/*."
+msgstr "Não sei como mapear a classe ''{0}'' para um campo base_type, alternando para :tipo/*."
+
+#: src/metabase/driver.clj
+msgid "Failed to connect to database: {0}"
+msgstr "Falha ao conectar ao banco de dados: {0}"
+
+#: src/metabase/driver/bigquery.clj
+msgid "Invalid BigQuery identifier: ''{0}''"
+msgstr "Identificador BigQuery inválido: ''{0}''"
+
+#: src/metabase/driver/bigquery.clj
+msgid "BigQuery statements can't be parameterized!"
+msgstr "Valores BigQuery não podem ser parametrizados!"
+
+#: src/metabase/driver/generic_sql/query_processor.clj
+msgid "Failed to set timezone:"
+msgstr "Falha ao escolher o fuso horário"
+
+#: src/metabase/driver/googleanalytics.clj
+msgid "You must enable the Google Analytics API. Use this link to go to the Google Developers Console: {0}"
+msgstr "Você deve ativar a API do Google Analytics. Use este link para ir ao Google Development Console: {0}"
+
+#: src/metabase/driver/h2.clj
+msgid "Running SQL queries against H2 databases using the default (admin) database user is forbidden."
+msgstr "É proibido executar consultas SQL em bancos de dados H2 usando o usuário do banco de dados padrão (administrador)."
+
+#: src/metabase/driver/sparksql.clj
+msgid "Error: metabase.driver.FixedHiveDriver is registered, but JDBC does not seem to be using it."
+msgstr "Erro: o metabase.driver.FixedHiveDriver está registrado, mas o JDBC parece não utilizá-lo."
+
+#: src/metabase/driver/sparksql.clj
+msgid "Found metabase.driver.FixedHiveDriver."
+msgstr "Encontrado metabase.driver.FixedHiveDriver."
+
+#: src/metabase/driver/sparksql.clj
+msgid "Successfully registered metabase.driver.FixedHiveDriver with JDBC."
+msgstr "Registrado com sucesso o metabase.driver.FixedHiveDriver com o JDBC."
+
+#. CONFIG
+#. TODO - smtp-port should be switched to type :integer
+#: src/metabase/email.clj
+msgid "Email address you want to use as the sender of Metabase."
+msgstr "Endereço de e-mail que você deseja usar como remetente do Metabase."
+
+#: src/metabase/email.clj
+msgid "The address of the SMTP server that handles your emails."
+msgstr "O endereço do servidor SMTP que lida com seus e-mails."
+
+#: src/metabase/email.clj
+msgid "SMTP username."
+msgstr "Usuário SMTP."
+
+#: src/metabase/email.clj
+msgid "SMTP password."
+msgstr "Senha SMTP."
+
+#: src/metabase/email.clj
+msgid "The port your SMTP server uses for outgoing emails."
+msgstr "A porta que seu servidor SMTP usa para envio de e-mails."
+
+#: src/metabase/email.clj
+msgid "SMTP secure connection protocol. (tls, ssl, starttls, or none)"
+msgstr "Protocolo de conexão SMTP segura. (tls, ssl, starttls ou nenhum)"
+
+#: src/metabase/email.clj
+msgid "none"
+msgstr "nenhum"
+
+#: src/metabase/email.clj
+msgid "SMTP host is not set."
+msgstr "O servidor SMTP não está configurado."
+
+#: src/metabase/email.clj
+msgid "Failed to send email"
+msgstr "Não foi possível enviar o e-mail"
+
+#: src/metabase/email.clj
+msgid "Error testing SMTP connection"
+msgstr "Erro ao testar a conexão SMTP"
+
+#: src/metabase/integrations/ldap.clj
+msgid "Enable LDAP authentication."
+msgstr "Ativa a autenticação LDAP."
+
+#: src/metabase/integrations/ldap.clj
+msgid "Server hostname."
+msgstr "Nome do servidor"
+
+#: src/metabase/integrations/ldap.clj
+msgid "Server port, usually 389 or 636 if SSL is used."
+msgstr "Porta do servidor, geralmente 389 ou 636, se o SSL for usado."
+
+#: src/metabase/integrations/ldap.clj
+msgid "Use SSL, TLS or plain text."
+msgstr "Use SSL, TLS ou texto simples."
+
+#: src/metabase/integrations/ldap.clj
+msgid "The Distinguished Name to bind as (if any), this user will be used to lookup information about other users."
+msgstr "O nome distinto para vincular como (se existir), esse usuário é usará para encontrar informações sobre outros usuários."
+
+#: src/metabase/integrations/ldap.clj
+msgid "The password to bind with for the lookup user."
+msgstr "A senha para vincular ao usuário da pesquisa."
+
+#: src/metabase/integrations/ldap.clj
+msgid "Search base for users. (Will be searched recursively)"
+msgstr "Base de pesquisa para usuários. (Será pesquisado recursivamente)"
+
+#: src/metabase/integrations/ldap.clj
+msgid "User lookup filter, the placeholder '{login}' will be replaced by the user supplied login."
+msgstr "Filtro de pesquisa do usuário, o espaço reservado '{login}' irá substituir com o login fornecido pelo usuário."
+
+#: src/metabase/integrations/ldap.clj
+msgid "Attribute to use for the user's email. (usually ''mail'', ''email'' or ''userPrincipalName'')"
+msgstr "Atributo de email do usuário. (geralmente '' mail '', '' email '' ou '' userPrincipalName '')"
+
+#: src/metabase/integrations/ldap.clj
+msgid "Attribute to use for the user''s first name. (usually ''givenName'')"
+msgstr "Atributo a ser usado para o primeiro nome do usuário. (normalmente '' givenName '')"
+
+#: src/metabase/integrations/ldap.clj
+msgid "Attribute to use for the user''s last name. (usually ''sn'')"
+msgstr "Atributo a ser usado para o sobrenome do usuário. (normalmente ''sn'')"
+
+#: src/metabase/integrations/ldap.clj
+msgid "Enable group membership synchronization with LDAP."
+msgstr "Ative a sincronização de associação ao grupo com o LDAP."
+
+#: src/metabase/integrations/ldap.clj
+msgid "Search base for groups, not required if your LDAP directory provides a ''memberOf'' overlay. (Will be searched recursively)"
+msgstr "Base de pesquisa para grupos, não obrigatória se o diretório LDAP fornecer uma sobreposição '' memberOf ''. (Será pesquisado recursivamente)"
+
+#. Should be in the form: {"cn=Some Group,dc=...": [1, 2, 3]} where keys are LDAP groups and values are lists of MB groups IDs
+#: src/metabase/integrations/ldap.clj
+msgid "JSON containing LDAP to Metabase group mappings."
+msgstr "JSON que contém a correspondência LDAP para grupos Metabase."
+
+#. Define a setting which captures our Slack api token
+#: src/metabase/integrations/slack.clj
+msgid "Slack API bearer token obtained from https://api.slack.com/web#authentication"
+msgstr "Token da API Slack obtido de https://api.slack.com/web#authentication"
+
+#: src/metabase/metabot.clj
+msgid "Enable MetaBot, which lets you search for and view your saved questions directly via Slack."
+msgstr "Ativar o MetaBot, que permite pesquisar e visualizar suas perguntas salvas diretamente através do Slack."
+
+#: src/metabase/metabot.clj
+msgid "Last MetaBot checkin was {0} ago."
+msgstr "Último checkin do MetaBot foi {0} atrás"
+
+#: src/metabase/metabot.clj
+msgid "This instance will now handle MetaBot duties."
+msgstr "Esta instância vai controlar as tarefas do MetaBot."
+
+#: src/metabase/metabot.clj
+msgid "Here''s what I can {0}:"
+msgstr "Isto é o que eu posso {0}:"
+
+#: src/metabase/metabot.clj
+msgid "I don''t know how to {0} `{1}`.n{2}"
+msgstr "Eu não sei como {0} `{1}` .n {2}"
+
+#: src/metabase/metabot.clj
+msgid "Uh oh! :cry:n> {0}"
+msgstr "Oh não! :cry:n> {0}"
+
+#: src/metabase/metabot.clj
+msgid "Here''s your {0} most recent cards:n{1}"
+msgstr "Aqui estão seus {0} cards mais recentes:n{1}"
+
+#: src/metabase/metabot.clj
+msgid "Could you be a little more specific? I found these cards with names that matched:n{0}"
+msgstr "Você poderia ser um pouco mais específico? Eu encontrei esses cartões com Nomes correspondentes:n{0}"
+
+#: src/metabase/metabot.clj
+msgid "I don''t know what Card `{0}` is. Give me a Card ID or name."
+msgstr "Eu não sei o que é o cartão `{0}`. Me dê um ID ou nome do cartão."
+
+#: src/metabase/metabot.clj
+msgid "Show which card? Give me a part of a card name or its ID and I can show it to you. If you don''t know which card you want, try `metabot list`."
+msgstr "Qual cartão mostrar? Dê-me uma parte do nome de um cartão ou seu ID e você Eu posso mostrar Se você não sabe qual placa você quer, tente `metabot list`."
+
+#: src/metabase/metabot.clj
+msgid "Ok, just a second..."
+msgstr "Ok, só um segundo..."
+
+#: src/metabase/metabot.clj
+msgid "Not Found"
+msgstr "Não encontrado"
+
+#: src/metabase/metabot.clj
+msgid "Loading Kanye quotes..."
+msgstr "Carregando citações de Kanye..."
+
+#: src/metabase/metabot.clj
+msgid "Evaluating Metabot command:"
+msgstr "Avaliando o comando Metabot:"
+
+#: src/metabase/metabot.clj
+msgid "Go home websocket, you're drunk."
+msgstr "Vá para casa websocket, você está bêbado."
+
+#: src/metabase/metabot.clj
+msgid "Error launching metabot:"
+msgstr "Erro ao iniciar o metabot:"
+
+#: src/metabase/metabot.clj
+msgid "MetaBot WebSocket is closed. Reconnecting now."
+msgstr "O WebSocket do MetaBot está fechado. Reconectando agora."
+
+#: src/metabase/metabot.clj
+msgid "Error connecting websocket:"
+msgstr "Erro ao conectar o websocket:"
+
+#: src/metabase/metabot.clj
+msgid "This instance is performing MetaBot duties."
+msgstr "Esta instância está executando as tarefas do Metabot."
+
+#: src/metabase/metabot.clj
+msgid "Another instance is already handling MetaBot duties."
+msgstr "Outra instância já está controlando as tarefas do Metabot."
+
+#: src/metabase/metabot.clj
+msgid "Starting MetaBot threads..."
+msgstr "Iniciando as tarefas do Metabot..."
+
+#: src/metabase/metabot.clj
+msgid "Stopping MetaBot...  🤖"
+msgstr "Parando MetaBot...  🤖"
+
+#: src/metabase/metabot.clj
+msgid "MetaBot already running. Killing the previous WebSocket listener first."
+msgstr "O MetaBot já está em execução. Terminando o verificador de WebSocket anterior primeiro."
+
+#: src/metabase/middleware.clj
+msgid "Base-64 encoded public key for this site's SSL certificate."
+msgstr "Chave pública codificada com base-64 do certificado SSL deste site."
+
+#: src/metabase/middleware.clj
+msgid "Specify this to enable HTTP Public Key Pinning."
+msgstr "Especifique esta opção para habilitar a fixação da chave pública HTTP."
+
+#: src/metabase/middleware.clj
+msgid "See {0} for more information."
+msgstr "Veja {0} para maiores informações."
+
+#: src/metabase/models/card.clj
+msgid "Cannot save Question: source query has circular references."
+msgstr "Não foi possível salvar a Pergunta: a consulta original têm uma referência circular."
+
+#: src/metabase/models/card.clj src/metabase/models/query/permissions.clj
+#: src/metabase/query_processor/middleware/permissions.clj
+msgid "Card {0} does not exist."
+msgstr "Cartão {0} não existe."
+
+#: src/metabase/models/card.clj
+msgid "You do not have permissions to run ad-hoc native queries against Database {0}."
+msgstr "Você não têm permissões para rodar consultas ad-hoc nativas no banco de dados {0}."
+
+#: src/metabase/models/collection.clj
+msgid "Invalid color"
+msgstr "Cor inválida"
+
+#: src/metabase/models/collection.clj
+msgid "must be a valid 6-character hex color code"
+msgstr "deve ser um valor de cor hexadecimal válido de 6 dígitos "
+
+#: src/metabase/models/collection.clj
+msgid "Collection name cannot be blank!"
+msgstr "Nome da coleção não pode ser em branco!"
+
+#: src/metabase/models/collection.clj
+msgid "cannot be blank"
+msgstr "não pode ser branco"
+
+#: src/metabase/models/collection.clj
+msgid "Invalid Collection location: path is invalid."
+msgstr "Localização de Coleção inválida: endereço inválido."
+
+#: src/metabase/models/collection.clj
+msgid "You cannot move a Personal Collection."
+msgstr "Você não pode mover uma coleção pessoal."
+
+#: src/metabase/models/collection.clj
+msgid "Invalid Collection location: some or all ancestors do not exist."
+msgstr "Localização de Coleção inválida: um ou mais dos antecessores não existem."
+
+#: src/metabase/models/collection.clj
+msgid "You cannot archive the Root Collection."
+msgstr "Você não pode arquivar a coleção principal."
+
+#: src/metabase/models/collection.clj
+msgid "You cannot archive a Personal Collection."
+msgstr "Você não pode arquivar uma coleção pessoal."
+
+#: src/metabase/models/collection.clj
+msgid "You cannot move the Root Collection."
+msgstr "Você não pode mover a coleção principal."
+
+#: src/metabase/models/collection.clj
+msgid "You cannot move a Collection into itself or into one of its descendants."
+msgstr "Você não pode mover uma coleção dentro de si mesma ou dentro de suas vinculadas."
+
+#. first move this Collection
+#: src/metabase/models/collection.clj
+msgid "Moving Collection {0} and its descendants from {1} to {2}"
+msgstr "Movendo Coleção {0} e suas vinculadas de {1} para {2}"
+
+#: src/metabase/models/collection.clj
+msgid "You're not allowed to change the owner of a Personal Collection."
+msgstr "Você não está autorizado a mudar o proprietário de uma coleção pessoal."
+
+#: src/metabase/models/collection.clj
+msgid "You're not allowed to move a Personal Collection."
+msgstr "Você não pode mover uma coleção pessoal."
+
+#: src/metabase/models/collection.clj
+msgid "You cannot move a Collection and archive it at the same time."
+msgstr "Você não pode mover e arquivar uma coleção ao mesmo tempo."
+
+#: src/metabase/models/collection.clj
+msgid "You cannot delete a Personal Collection!"
+msgstr "Você não pode remover uma coleção pessoal."
+
+#: src/metabase/models/collection.clj
+msgid "{0} {1}''s Personal Collection"
+msgstr "{0} Coleção pessoal de {1}"
+
+#: src/metabase/models/collection_revision.clj
+msgid "You cannot update a CollectionRevision!"
+msgstr "Você não pode atualizar a CollectionRevision!"
+
+#: src/metabase/models/field_values.clj
+msgid "Field {0} was previously automatically set to show a list widget, but now has {1} values."
+msgstr "O campo {0} foi setado para mostrar automaticamente um widget de lista, mas agoraele apresenta {1} valores."
+
+#: src/metabase/models/field_values.clj
+msgid "Switching Field to use a search widget instead."
+msgstr "Alterando campo para usar um widget de pesquisa."
+
+#: src/metabase/models/field_values.clj
+msgid "Storing updated FieldValues for Field {0}..."
+msgstr "Armazenando valores de campo atualizados para o campo {0}..."
+
+#: src/metabase/models/field_values.clj
+msgid "Storing FieldValues for Field {0}..."
+msgstr "Armazenando FieldValues  para o campo {0}..."
+
+#: src/metabase/models/humanization.clj
+msgid "Metabase can attempt to transform your table and field names into more sensible, human-readable versions, e.g. \"somehorriblename\" becomes \"Some Horrible Name\"."
+msgstr "O Metabase pode tentar transformar os nomes de tabelas e campos em versões mais amigáveis, prontas para leitura, ex. \"umnomehorrivel\" torna-se \"Um Nome Horrivel\"."
+
+#: src/metabase/models/humanization.clj
+msgid "This doesn’t work all that well if the names are in a language other than English, however."
+msgstr "Isso não funciona corretamente se os nomes estão em uma língua diferente do inglês."
+
+#: src/metabase/models/humanization.clj
+msgid "Do you want us to take a guess?"
+msgstr "Quer que eu tente adivinhar?"
+
+#: src/metabase/models/permissions.clj
+msgid "You cannot create or revoke permissions for the 'Admin' group."
+msgstr "Você não pode criar ou revogar permissões para o grupo 'Admin'."
+
+#: src/metabase/models/permissions.clj
+msgid "Invalid permissions object path: ''{0}''."
+msgstr "Permissões Inválidas para o endereço de objeto: ''{0}''."
+
+#: src/metabase/models/permissions.clj
+msgid "You cannot update a permissions entry!"
+msgstr "Você não pode atualizar uma entrada de permissões!"
+
+#: src/metabase/models/permissions.clj
+msgid "Delete it and create a new one."
+msgstr "Remova a atual e crie uma nova."
+
+#: src/metabase/models/permissions.clj
+msgid "You cannot edit permissions for a Personal Collection or its descendants."
+msgstr "Você não pode editar permissões para uma coleção pessoal ou suas vinculadas."
+
+#: src/metabase/models/permissions.clj
+msgid "Looks like someone else edited the permissions and your data is out of date."
+msgstr "Parece que alguém editou as permissões e seus dados estão desatualizados."
+
+#: src/metabase/models/permissions.clj
+msgid "Please fetch new data and try again."
+msgstr "Por favor atualize os dados e tente novamente."
+
+#: src/metabase/models/permissions_group.clj
+msgid "Created magic permissions group ''{0}'' (ID = {1})"
+msgstr "Grupo de permissões mágicas criado ''{0}'' (ID = {1})"
+
+#: src/metabase/models/permissions_group.clj
+msgid "A group with that name already exists."
+msgstr "Um grupo com esse nome já existe."
+
+#: src/metabase/models/permissions_group.clj
+msgid "You cannot edit or delete the ''{0}'' permissions group!"
+msgstr "Você não pode editar ou deletar o grupo de permissões ''{0}''!"
+
+#: src/metabase/models/permissions_group_membership.clj
+msgid "You cannot add or remove users to/from the 'MetaBot' group."
+msgstr "Você não pode adicionar ou remover usuários do grupo 'Metabot'."
+
+#: src/metabase/models/permissions_group_membership.clj
+msgid "You cannot add or remove users to/from the 'All Users' group."
+msgstr "Você não pode adicionar ou remover usuários do grupo 'Todos Usuários'."
+
+#: src/metabase/models/permissions_group_membership.clj
+msgid "You cannot remove the last member of the 'Admin' group!"
+msgstr "Você não pode remover o último membro do grupo 'Admin'!"
+
+#: src/metabase/models/permissions_revision.clj
+msgid "You cannot update a PermissionsRevision!"
+msgstr "Você não pode atualizar a PermissionsRevision!"
+
+#. if there's still not a Card, throw an Exception!
+#: src/metabase/models/pulse.clj
+msgid "Invalid Alert: Alert does not have a Card assoicated with it"
+msgstr "Alerta Inválido: O alerta não tem um Cartão associado"
+
+#: src/metabase/models/pulse.clj
+msgid "value must be a map with the keys `{0}`, `{1}`, and `{2}`."
+msgstr "valor deve ser um mapa com as chaves `{0}`, `{1}`, e `{2}`."
+
+#. Ajustado, o enter estava travando a compilação
+#: src/metabase/models/pulse.clj
+msgid "value must be a map with the following keys `({0})`"
+msgstr "valor deve ser um mapa com as seguintes chaves `({0})`"
+
+#: src/metabase/models/query/permissions.clj
+msgid "Error calculating permissions for query: {0}"
+msgstr "Erro calculando permissões para consulta: {0}"
+
+#: src/metabase/models/query/permissions.clj
+msgid "Invalid query type: {0}"
+msgstr "Tipo de consulta inválido: {0}"
+
+#: src/metabase/models/query_execution.clj
+msgid "You cannot update a QueryExecution!"
+msgstr "Você não pode atualizar a QueryExecution!"
+
+#: src/metabase/models/revision.clj
+msgid "You cannot update a Revision!"
+msgstr "Você não pode atualizar uma Revisão!"
+
+#: src/metabase/models/setting.clj
+msgid "Setting {0} does not exist.nFound: {1}"
+msgstr "Configuração {0} não existe.nFound: {1}"
+
+#: src/metabase/models/setting.clj
+msgid "Updating value of settings-last-updated in DB..."
+msgstr "Atualizando o valor das \"últimas configurações atualizadas\" no BD..."
+
+#: src/metabase/models/setting.clj
+msgid "Checking whether settings cache is out of date (requires DB call)..."
+msgstr "Verificando se as configurações em cache estão desatualizadas (requer uma chamada ao BD)..."
+
+#: src/metabase/models/setting.clj
+msgid "Settings have been changed on another instance, and will be reloaded here."
+msgstr "Configurações foram alteradas por outra instância e serão recarregadas aqui."
+
+#: src/metabase/models/setting.clj
+msgid "Refreshing Settings cache..."
+msgstr "Atualizando cache das configurações..."
+
+#: src/metabase/models/setting.clj
+msgid "Invalid value for string: must be either \"true\" or \"false\" (case-insensitive)."
+msgstr "Valor inválido para texto: deve ser \"verdadeiro\" ou \"falso\"(desconsiderando maiúsculas e minúsculas)."
+
+#: src/metabase/models/setting.clj
+msgid "You cannot update `settings-last-updated` yourself! This is done automatically."
+msgstr "Você não pode atualizar as \"últimas configurações atualizadas\" sozinho. Isso é feito automaticamente."
+
+#. go ahead and log the Exception anyway on the off chance that it *wasn't* just a race condition issue
+#: src/metabase/models/setting.clj
+msgid "Error inserting a new Setting:"
+msgstr "Erro ao inserir a nova configuração:"
+
+#: src/metabase/models/setting.clj
+msgid "Assuming Setting already exists in DB and updating existing value."
+msgstr "Assumi que a Configuração já existe no BD e estou atualizando o valor existente."
+
+#: src/metabase/models/user.clj
+msgid "value must be a map with each value either a string or number."
+msgstr "o valor deve ser um mapa com cada valor como texto ou número."
+
+#: src/metabase/plugins.clj
+msgid "Loading plugins in directory {0}..."
+msgstr "Carregando plugins no diretório {0}..."
+
+#: src/metabase/plugins.clj
+msgid "Loading plugin {0}... "
+msgstr "Carregando plugin {0}... "
+
+#: src/metabase/plugins.clj
+msgid "It looks like you have some external dependencies in your Metabase plugins directory."
+msgstr "Parece que você tem dependências externas no diretório de plugins do Metabase."
+
+#: src/metabase/plugins.clj
+msgid "With Java 9 or higher, Metabase cannot automatically add them to your classpath."
+msgstr "Com o Java 9 ou superior, o Metabase não pode adicionar eles automaticamente ao classpath."
+
+#: src/metabase/plugins.clj
+msgid "Instead, you should include them at launch with the -cp option. For example:"
+msgstr "Ao invés, você deveria incluí-los na inicialização com a opção -cp. Por exemplo:"
+
+#: src/metabase/plugins.clj
+msgid "See https://metabase.com/docs/latest/operations-guide/start.html#java-versions for more details."
+msgstr "Veja https://metabase.com/docs/latest/operations-guide/start.html#java-versions para maiores detalhes."
+
+#: src/metabase/plugins.clj
+msgid "(If you're already running Metabase this way, you can ignore this message.)"
+msgstr "(Se você já está rodando o metabase deste jeito, você pode ignorar esta mensagem.)"
+
+#: src/metabase/public_settings.clj
+msgid "Identify when new versions of Metabase are available."
+msgstr "Relate quando há novas versões do Metabase."
+
+#: src/metabase/public_settings.clj
+msgid "Information about available versions of Metabase."
+msgstr "Informações sobre as versões disponíveis do Metabase."
+
+#: src/metabase/public_settings.clj
+msgid "The name used for this instance of Metabase."
+msgstr "O nome usado para esta instância do Metabase."
+
+#: src/metabase/public_settings.clj
+msgid "The base URL of this Metabase instance, e.g. \"http://metabase.my-company.com\"."
+msgstr "A URL desta instância do Metabase, e.x. \"http://metabase.my-company.com\"."
+
+#: src/metabase/public_settings.clj
+msgid "The default language for this Metabase instance."
+msgstr "O idioma padrão para esta instância do Metabase."
+
+#: src/metabase/public_settings.clj
+msgid "This only applies to emails, Pulses, etc. Users'' browsers will specify the language used in the user interface."
+msgstr "Isso se aplica apenas a e-mails, notificações, etc. Os navegadores dos usuários especificarão o idioma usado na interface do usuário."
+
+#: src/metabase/public_settings.clj
+msgid "The email address users should be referred to if they encounter a problem."
+msgstr "O endereço de e-mail para o qual os usuários serão direcionados se eles acharem um problema."
+
+#: src/metabase/public_settings.clj
+msgid "Enable the collection of anonymous usage data in order to help Metabase improve."
+msgstr "Permite a coleta de dados de uso anônimos para ajudar a melhorar Metabase"
+
+#: src/metabase/public_settings.clj
+msgid "The map tile server URL template used in map visualizations, for example from OpenStreetMaps or MapBox."
+msgstr "O modelo de URL do servidor da camada de mapa usado no visualizações de mapas, por exemplo, do OpenStreetMaps ou MapBox."
+
+#: src/metabase/public_settings.clj
+msgid "Enable admins to create publicly viewable links (and embeddable iframes) for Questions and Dashboards?"
+msgstr "Permitir que os administradores criem links visíveis publicamente (e iframes incorporados) para perguntas e painéis?"
+
+#: src/metabase/public_settings.clj
+msgid "Allow admins to securely embed questions and dashboards within other applications?"
+msgstr "Permite aos administradores inserir perguntas e painéis de forma segura dentro de outras aplicações?"
+
+#: src/metabase/public_settings.clj
+msgid "Allow using a saved question as the source for other queries?"
+msgstr "Permitir o uso de uma pergunta salva como fonte para outras consultas?"
+
+#: src/metabase/public_settings.clj
+msgid "Enabling caching will save the results of queries that take a long time to run."
+msgstr "A ativação do armazenamento em cache armazena os resultados do consultas que demoram muito para serem executadas."
+
+#: src/metabase/public_settings.clj
+msgid "The maximum size of the cache, per saved question, in kilobytes:"
+msgstr "O tamanho máximo do cache, por pergunta salva, em kilobytes:"
+
+#: src/metabase/public_settings.clj
+msgid "The absolute maximum time to keep any cached query results, in seconds."
+msgstr "Tempo máximo que devemos manter os resultados das consultas em cache, em segundos."
+
+#: src/metabase/public_settings.clj
+msgid "Metabase will cache all saved questions with an average query execution time longer than this many seconds:"
+msgstr "o Metabase armazenará em cache todas as perguntas salvas com um tempo execução média da consulta maior que esses segundos:"
+
+#: src/metabase/public_settings.clj
+msgid "To determine how long each saved question''s cached result should stick around, we take the query''s average execution time and multiply that by whatever you input here."
+msgstr "Para determinar quanto tempo o resultado armazenado no memória de cada questão salva, tomamos o tempo médio de execução da consulta e multiplique-o pelo que você coloca aqui."
+
+#: src/metabase/public_settings.clj
+msgid "So if a query takes on average 2 minutes to run, and you input 10 for your multiplier, its cache entry will persist for 20 minutes."
+msgstr "Então, se uma consulta leva uma média de 2 minutos para executar, e você digita 10 para o seu multiplicador, sua entrada no cache é vai manter por 20 minutos."
+
+#: src/metabase/public_settings.clj
+msgid "When using the default binning strategy and a number of bins is not provided, this number will be used as the default."
+msgstr "Quando a estratégia de agrupamento padrão é usada e não é fornecida um número de pernas, esse número será usado como padrão."
+
+#: src/metabase/public_settings.clj
+msgid "When using the default binning strategy for a field of type Coordinate (such as Latitude and Longitude), this number will be used as the default bin width (in degrees)."
+msgstr "Quando a estratégia de agrupamento padrão é usada para um campo do tipo Coordinate (como Latitude e Longitude), esse número será usado como a largura predeterminada da seção (em graus)."
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Unable to validate token."
+msgstr "Impossível validar o token."
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Error fetching token status:"
+msgstr "Erro ao carregar o status do token:"
+
+#: src/metabase/public_settings/metastore.clj
+msgid "There was an error checking whether this token was valid."
+msgstr "Ocorreu um erro ao checar a validade do token."
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Token validation timed out."
+msgstr "Validação do token demorou demais."
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Invalid token: token isn't in the right format."
+msgstr "Token inválido: o formato do token não está correto."
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Checking with the MetaStore to see whether {0} is valid..."
+msgstr "Checando com o MetaStore para ver se {0} é válido..."
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Token for premium embedding. Go to the MetaStore to get yours!"
+msgstr "Token para inserção premium. Vá ao MetaStore e compre o seu!"
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Token is valid."
+msgstr "Este token é válido."
+
+#: src/metabase/public_settings/metastore.clj
+msgid "Error setting premium embedding token"
+msgstr "Erro ao habilitar o token de inserção premium"
+
+#: src/metabase/pulse.clj
+msgid "Unable to compare results to goal for alert."
+msgstr "Você não pode comparar os resultados com a meta do alerta."
+
+#: src/metabase/pulse.clj
+msgid "Question ID is ''{0}'' with visualization settings ''{1}''"
+msgstr "O ID da questão é ''{0}'' com configuração de exibição ''{1}''"
+
+#: src/metabase/pulse.clj
+msgid "Unrecognized alert with condition ''{0}''"
+msgstr "Alerta desconhecido com condição ''{0}''"
+
+#: src/metabase/pulse.clj
+msgid "Unrecognized channel type {0}"
+msgstr "O tipo de canal {0} é desconhecido"
+
+#: src/metabase/pulse.clj
+msgid "Error sending notification!"
+msgstr "Erro ao enviar notificação!"
+
+#: src/metabase/pulse/color.clj
+msgid "Can't find JS color selector at ''{0}''"
+msgstr "Não foi possível encontrar o seletor de cores JS em \"{0}\""
+
+#: src/metabase/pulse/render.clj
+msgid "Card has errors: {0}"
+msgstr "O cartão tem erros: {0}"
+
+#: src/metabase/pulse/render.clj
+msgid "Pulse card render error"
+msgstr "Erro de processamento de notificação"
+
+#: src/metabase/query_processor/middleware/fetch_source_query.clj
+msgid "Trimming trailing comment from card with id {0}"
+msgstr "Cortando os comentários finais do card com id {0}"
+
+#: src/metabase/query_processor/middleware/parameters/sql.clj
+msgid "Can't find field with ID: {0}"
+msgstr "Não foi possível localizar o campo com ID: {0}"
+
+#: src/metabase/query_processor/middleware/parameters/sql.clj
+msgid "''{0}'' is a required param."
+msgstr "''{0}''é um parâmetro requerido."
+
+#: src/metabase/query_processor/middleware/parameters/sql.clj
+msgid "Found ''{0}'' with no terminating ''{1}'' in query ''{2}''"
+msgstr "Encontrado ''{0}'' sem um término ''{1}'' na consulta ''{2}''"
+
+#: src/metabase/query_processor/middleware/parameters/sql.clj
+msgid "Unable to substitute ''{0}'': param not specified.nFound: {1}"
+msgstr "Impossível substituir ''{0}'': parâmetro não especificado.nFound: {1}"
+
+#: src/metabase/query_processor/middleware/permissions.clj
+msgid "You do not have permissions to view Card {0}."
+msgstr "Desculpe, você não tem permissão para ver o cartão {0}."
+
+#: src/metabase/query_processor/middleware/permissions.clj
+msgid "You do not have permissions to run this query."
+msgstr "Desculpe, você não tem permissão para executar esta consulta."
+
+#: src/metabase/sync/analyze.clj
+msgid "Fingerprint updates attempted {0}, updated {1}, no data found {2}, failed {3}"
+msgstr "Tentativas de atualização de digital {0}, atualizações {1}, dados não encontrados {2}, falha {3}"
+
+#: src/metabase/sync/analyze.clj
+msgid "Total number of fields classified {0}, {1} failed"
+msgstr "Número total de campos classificados {0}, {1} falhas"
+
+#: src/metabase/sync/analyze.clj
+msgid "Total number of tables classified {0}, {1} updated"
+msgstr "Número total de tabelas classificadas {0}, {1} atualizadas"
+
+#: src/metabase/sync/analyze/fingerprint/fingerprinters.clj
+msgid "Error generating fingerprint for {0}"
+msgstr "Erro ao gerar identificação para {0}"
+
+#: src/metabase/sync/field_values.clj
+msgid "Updated {0} field value sets, created {1}, deleted {2} with {3} errors"
+msgstr "Valores de campos atualizados {0} , criados {1}, removidos {2} com {3} erros"
+
+#: src/metabase/sync/sync_metadata.clj
+msgid "Total number of fields sync''d {0}, number of fields updated {1}"
+msgstr "Número total de campos sincronizados {0}, número de campos atualizados {1}"
+
+#: src/metabase/sync/sync_metadata.clj
+msgid "Total number of tables sync''d {0}, number of tables updated {1}"
+msgstr "Número total de tabelas sincronizadas {0}, número de tabelas atualizados {1}"
+
+#: src/metabase/sync/sync_metadata.clj
+msgid "Found timezone id {0}"
+msgstr "Fuso horário encontrato, id {0}"
+
+#: src/metabase/sync/sync_metadata.clj
+msgid "Total number of foreign keys sync''d {0}, {1} updated and {2} tables failed to update"
+msgstr "Número total de chaves extrangeiras sincronizadas {0}, {1} atualizado e {2} tabelas falharam atualizar."
+
+#: src/metabase/sync/util.clj
+msgid "{0} Database {1} ''{2}''"
+msgstr "{0} Banco de dados {1} ''{2}''"
+
+#: src/metabase/sync/util.clj
+msgid "Table {0} ''{1}''"
+msgstr "Tabela {0} ''{1}''"
+
+#: src/metabase/sync/util.clj
+msgid "Field {0} ''{1}''"
+msgstr "Campo {0} ''{1}''"
+
+#: src/metabase/sync/util.clj
+msgid "Field ''{0}''"
+msgstr "Campo ''{0}''"
+
+#: src/metabase/sync/util.clj
+msgid "step ''{0}'' for {1}"
+msgstr "passo ''{0}'' para {1}"
+
+#: src/metabase/sync/util.clj
+msgid "Completed {0} on {1}"
+msgstr "Completado em {0} por {1}"
+
+#: src/metabase/sync/util.clj
+msgid "Start: {0}"
+msgstr "Início: {0}"
+
+#: src/metabase/sync/util.clj
+msgid "End: {0}"
+msgstr "Fim: {0}"
+
+#: src/metabase/sync/util.clj
+msgid "Duration: {0}"
+msgstr "Duração: {0}"
+
+#: src/metabase/sync/util.clj
+msgid "Completed step ''{0}''"
+msgstr "Passo completado ''{0}''"
+
+#: src/metabase/task.clj
+msgid "Loading tasks namespace:"
+msgstr "Carregando o namespace das tarefas:"
+
+#: src/metabase/task.clj
+msgid "Starting Quartz Scheduler"
+msgstr "Iniciando o agendador Quartz"
+
+#: src/metabase/task.clj
+msgid "Stopping Quartz Scheduler"
+msgstr "Parando o agendador Quartz"
+
+#: src/metabase/task.clj
+msgid "Job already exists:"
+msgstr "Trabalho já existe:"
+
+#. 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
+#: src/metabase/util.clj
+msgid "Loading Metabase..."
+msgstr "Carregando o Metabase..."
+
+#: src/metabase/util/date.clj
+msgid "Possible timezone conflict found on database {0}."
+msgstr "Possível conflito de fuso horário encontrado no banco de dados {0}."
+
+#: src/metabase/util/date.clj
+msgid "JVM timezone is {0} and detected database timezone is {1}."
+msgstr "O fuso horário da JVM é {0} e o fuso do banco de dados é {1}."
+
+#: src/metabase/util/date.clj
+msgid "Configure a report timezone to ensure proper date and time conversions."
+msgstr "Configure um fuso horário para relatório de maneira a garantir conversões de datas e horários corretas."
+
+#: src/metabase/util/embed.clj
+msgid "Secret key used to sign JSON Web Tokens for requests to `/api/embed` endpoints."
+msgstr "Chave secreta usada para assinar os Web Tokens JSON para requisições ao endpoint `/api/embed`."
+
+#: src/metabase/util/encryption.clj
+msgid "MB_ENCRYPTION_SECRET_KEY must be at least 16 characters."
+msgstr "MB_ENCRYPTION_SECRET_KEY deve ter no mínimo 16 caracteres"
+
+#: src/metabase/util/encryption.clj
+msgid "Saved credentials encryption is ENABLED for this Metabase instance."
+msgstr "Encriptação de credenciais salvas está HABILITADO para esta instância do Metabase."
+
+#: src/metabase/util/encryption.clj
+msgid "Saved credentials encryption is DISABLED for this Metabase instance."
+msgstr "A criptografia para as credencias salvas é desativada nessa instância Metabase."
+
+#: src/metabase/util/encryption.clj
+msgid "nFor more information, see"
+msgstr "Para mais informação, veja"
+
+#: src/metabase/util/schema.clj
+msgid "value must be an integer."
+msgstr "o valor deve ser um inteiro."
+
+#: src/metabase/util/schema.clj
+msgid "value must be a string."
+msgstr "o valor deve ser um texto."
+
+#: src/metabase/util/schema.clj
+msgid "value must be a boolean."
+msgstr "o valor deve ser booleano"
+
+#: src/metabase/util/schema.clj
+msgid "value must be a string that matches the regex `{0}`."
+msgstr "o valor deve ser um texto que esteja com a expressão regular `{0}`."
+
+#: src/metabase/util/schema.clj
+msgid "value must satisfy one of the following requirements: "
+msgstr "o valor deve satisfazer um dos seguintes requisitos: "
+
+#: src/metabase/util/schema.clj
+msgid "value may be nil, or if non-nil, {0}"
+msgstr "o valor pode ser nulo ou, se não for nulo, {0}"
+
+#: src/metabase/util/schema.clj
+msgid "value must be one of: {0}."
+msgstr "o valor deve ser um dos seguintes: {0}."
+
+#: src/metabase/util/schema.clj
+msgid "value must be an array."
+msgstr "o valor deve ser um conjunto"
+
+#: src/metabase/util/schema.clj
+msgid "Each {0}"
+msgstr "Cada {0}"
+
+#: src/metabase/util/schema.clj
+msgid "The array cannot be empty."
+msgstr "O conjunto não pode estar vazio."
+
+#: src/metabase/util/schema.clj
+msgid "value must be a non-blank string."
+msgstr "o valor deve ser um texto não vazio"
+
+#: src/metabase/util/schema.clj
+msgid "Integer greater than zero"
+msgstr "Inteiro maior que zero"
+
+#: src/metabase/util/schema.clj
+msgid "value must be an integer greater than zero."
+msgstr "o valor deve ser um inteiro maior que zero."
+
+#: src/metabase/util/schema.clj
+msgid "Number greater than zero"
+msgstr "Número maio que zero"
+
+#: src/metabase/util/schema.clj
+msgid "value must be a number greater than zero."
+msgstr "valor deve ser um número maior que zero"
+
+#: src/metabase/util/schema.clj
+msgid "Keyword or string"
+msgstr "Palavra-chave ou texto"
+
+#: src/metabase/util/schema.clj
+msgid "Valid field type"
+msgstr "Tipo de campo válido"
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid field type."
+msgstr "o valor deve ser um tipo de campo válido"
+
+#: src/metabase/util/schema.clj
+msgid "Valid field type (keyword or string)"
+msgstr "Tipo de campo válido (palavra-chave ou texto)"
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid field type (keyword or string)."
+msgstr "O valor deve ser um tipo de campo válido (palavra-chave ou texto)."
+
+#: src/metabase/util/schema.clj
+msgid "Valid entity type (keyword or string)"
+msgstr "Tipo de entidade válido (palavra-chave ou texto)"
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid entity type (keyword or string)."
+msgstr "O valor deve ser um tipo de entidade válido (palavra-chave ou texto)."
+
+#: src/metabase/util/schema.clj
+msgid "Valid map"
+msgstr "Mapa válido"
+
+#: src/metabase/util/schema.clj
+msgid "value must be a map."
+msgstr "o valor deve ser um mapa"
+
+#: src/metabase/util/schema.clj
+msgid "Valid email address"
+msgstr "Endereço de e-mail Válido"
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid email address."
+msgstr "o valor deve ser um endereço de e-mail válido"
+
+#: src/metabase/util/schema.clj
+msgid "Insufficient password strength"
+msgstr "Complexidade de senha insuficiente"
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid integer."
+msgstr "o valor deve ser um inteiro válido"
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid integer greater than zero."
+msgstr "o valor deve ser um inteiro válido maior que zero."
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid boolean string (''true'' or ''false'')."
+msgstr "o valor deve ser um booleano válido ('' true '' ou '' false '')."
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid JSON string."
+msgstr "O valor deve ser um texto JSON válido."
+
+#: src/metabase/util/schema.clj
+msgid "value must be a valid embedding params map."
+msgstr "o valor deve ser um mapa de parâmetros de integração válidos."
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsTabs.jsx:12
+msgid "Data permissions"
+msgstr "Permissão de dados"
+
+#: frontend/src/metabase/admin/permissions/components/PermissionsTabs.jsx:13
+msgid "Collection permissions"
+msgstr "Permissão de coleções"
+
+#: frontend/src/metabase/admin/permissions/containers/CollectionPermissionsModal.jsx:56
+msgid "See all collection permissions"
+msgstr "Ver todas as permissões de coleções"
+
+#: frontend/src/metabase/admin/permissions/containers/TogglePropagateAction.jsx:25
+msgid "Also change sub-collections"
+msgstr "Mude também as sub-coleções"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:278
+msgid "Can edit this collection and its contents"
+msgstr "Pode editar essa coleção e seu conteúdo"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:285
+msgid "Can view items in this collection"
+msgstr "Pode ver os itens dessa coleção"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:745
+msgid "Collection Access"
+msgstr "Acesso à coleção"
+
+#: frontend/src/metabase/admin/permissions/selectors.js:821
+msgid "This group has permission to view at least one subcollection of this collection."
+msgstr "Este grupo tem permissão para ver ao menos uma sub-coleção desta coleção."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:826
+msgid "This group has permission to edit at least one subcollection of this collection."
+msgstr "Este grupo tem permissão para editar ao menos uma sub-coleção desta coleção."
+
+#: frontend/src/metabase/admin/permissions/selectors.js:839
+msgid "View sub-collections"
+msgstr "Ver sub-coleções"
+
+#: frontend/src/metabase/auth/containers/LoginApp.jsx:211
+msgid "Remember Me"
+msgstr "Lembre me"
+
+#: frontend/src/metabase/components/BrowseApp.jsx:118
+msgid "X-ray this schema"
+msgstr "Raio-X desse esquema"
+
+#: frontend/src/metabase/components/CollectionLanding.jsx:246
+msgid "Edit the permissions for this collection"
+msgstr "Editar as permissões dessa coleção"
+
+#: frontend/src/metabase/containers/AddToDashSelectDashModal.jsx:51
+msgid "Add this question to a dashboard"
+msgstr "Adicionar essa questão para um painel"
+
+#: frontend/src/metabase/containers/AddToDashSelectDashModal.jsx:61
+msgid "Create a new dashboard"
+msgstr "Criar um novo painel"
+
+#: frontend/src/metabase/containers/ErrorPages.jsx:45
+msgid "The page you asked for couldn't be found."
+msgstr "A página que você pediu não pode ser encontrada."
+
+#: frontend/src/metabase/containers/ItemSelect.jsx:30
+msgid "Select a {0}"
+msgstr "Selecione um {0}"
+
+#: frontend/src/metabase/containers/Overworld.jsx:188
+msgid "Save dashboards, questions, and collections in \"{0}\""
+msgstr "Salvar painéis, questões e coleções em \"{0}\""
+
+#: frontend/src/metabase/containers/Overworld.jsx:191
+msgid "Access dashboards, questions, and collections in \"{0}\""
+msgstr "Acessar painéis, questões e coleções em \"{0}\""
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:216
+msgid "Compare"
+msgstr "Comparar"
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:224
+msgid "Zoom out"
+msgstr "Diminuir Zoom"
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:228
+msgid "Related"
+msgstr "Relacionado"
+
+#: frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx:288
+msgid "More X-rays"
+msgstr "Mais Raios-X"
+
+#: frontend/src/metabase/home/containers/SearchApp.jsx:50
+msgid "No results"
+msgstr "Sem resultados"
+
+#: frontend/src/metabase/home/containers/SearchApp.jsx:51
+msgid "Metabase couldn't find any results for your search."
+msgstr "Metabase não pode localizar nenhum resultado da sua pesquisa."
+
+#: frontend/src/metabase/new_query/containers/MetricSearch.jsx:111
+msgid "No metrics"
+msgstr "Sem métricas"
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:31
+msgid "Aggregations"
+msgstr "Agregações"
+
+#: frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx:32
+msgid "Operators"
+msgstr "Operadores"
+
+#: frontend/src/metabase/query_builder/components/expressions/Expressions.jsx:30
+msgid "Custom fields"
+msgstr "Campos personalizados"
+
+#. 2. Create the new collections.
+#: src/metabase/db/migrations.clj
+msgid "Migrated Dashboards"
+msgstr "Painéis migrados"
+
+#: src/metabase/db/migrations.clj
+msgid "Migrated Pulses"
+msgstr "Notificações migradas"
+
+#: src/metabase/db/migrations.clj
+msgid "Migrated Questions"
+msgstr "Perguntas migradas"
+
+#. 4. move everything not in this Collection to a new Collection
+#: src/metabase/db/migrations.clj
+msgid "Moving instances of {0} that aren't in a Collection to {1} Collection {2}"
+msgstr "Movendo instancias de {0} que não estão em uma coleção para {1} Coleção {2}"
+
+#: src/metabase/models/permissions.clj
+msgid "Failed to grant permissions: {0}"
+msgstr "Falha ao garantir permissões: {0}"
+
+#: src/metabase/util/encryption.clj
+msgid "Cannot decrypt encrypted string. Have you changed or forgot to set MB_ENCRYPTION_SECRET_KEY?"
+msgstr "Não foi possivel decriptar o texto. Você mudou ou esqueceu de dar um valor para MB_ENCRYPTION_SECRET_KEY?"
+
+#: frontend/src/metabase/entities/collections.js:157
+msgid "All personal collections"
+msgstr "Todas coleções pessoais"
+
+#: src/metabase/driver.clj
+msgid "Host"
+msgstr "Servidor"
+
+#: src/metabase/driver.clj
+msgid "Port"
+msgstr "Porta"
+
+#: src/metabase/driver.clj
+msgid "Database username"
+msgstr "Usuário do banco de dados"
+
+#: src/metabase/driver.clj
+msgid "What username do you use to login to the database?"
+msgstr "Qual usuário você usa para acessar o banco de dados?"
+
+#: src/metabase/driver.clj
+msgid "Database password"
+msgstr "Senha do banco de dados"
+
+#: src/metabase/driver.clj
+msgid "Database name"
+msgstr "Nome do banco de dados"
+
+#: src/metabase/driver.clj
+msgid "birds_of_the_world"
+msgstr "aves_do_mundo"
+
+#: src/metabase/driver.clj
+msgid "Use a secure connection (SSL)?"
+msgstr "Usar uma conexão segura (SSL)?"
+
+#: src/metabase/driver.clj
+msgid "Additional JDBC connection string options"
+msgstr "Opções adicionais dos parâmetros de conexão JDBC"
+
+#: src/metabase/driver/bigquery.clj
+msgid "Project ID"
+msgstr "ID do Projeto"
+
+#: src/metabase/driver/bigquery.clj
+msgid "praxis-beacon-120871"
+msgstr "praxis-beacon-120871"
+
+#: src/metabase/driver/bigquery.clj
+msgid "Dataset ID"
+msgstr "ID do conjunto de dados"
+
+#: src/metabase/driver/bigquery.clj
+msgid "toucanSightings"
+msgstr "toucanSightings"
+
+#: src/metabase/driver/bigquery.clj src/metabase/driver/googleanalytics.clj
+msgid "Client ID"
+msgstr "ID do Cliente"
+
+#: src/metabase/driver/bigquery.clj src/metabase/driver/googleanalytics.clj
+msgid "Client Secret"
+msgstr "Segredo do cliente"
+
+#: src/metabase/driver/bigquery.clj src/metabase/driver/googleanalytics.clj
+msgid "Auth Code"
+msgstr "Código de autenticação"
+
+#: src/metabase/driver/crate.clj
+msgid "Hosts"
+msgstr "Servidores"
+
+#: src/metabase/driver/druid.clj
+msgid "Broker node port"
+msgstr "Porta do nó do broker"
+
+#: src/metabase/driver/googleanalytics.clj
+msgid "Google Analytics Account ID"
+msgstr "ID da conta do Google Analytics"
+
+#: src/metabase/driver/h2.clj
+msgid "Connection String"
+msgstr "String de conexão"
+
+#: src/metabase/driver/h2.clj
+msgid "Users/camsaul/bird_sightings/toucans"
+msgstr "Users/camsaul/bird_sightings/toucans"
+
+#: src/metabase/driver/mongo.clj
+msgid "carrierPigeonDeliveries"
+msgstr "carrierPigeonDeliveries"
+
+#: src/metabase/driver/mongo.clj
+msgid "Authentication Database"
+msgstr "Banco de dados de Autenticação"
+
+#: src/metabase/driver/mongo.clj
+msgid "Optional database to use when authenticating"
+msgstr "Banco de dados opcional para uso quando estiver autenticando"
+
+#: src/metabase/driver/mongo.clj
+msgid "Additional Mongo connection string options"
+msgstr "Opções adicionais de conexão ao Mongo"
+
+#: src/metabase/driver/oracle.clj
+msgid "Oracle system ID (SID)"
+msgstr "ID do sistema Oracle (SID)"
+
+#: src/metabase/driver/oracle.clj
+msgid "Usually something like ORCL or XE."
+msgstr "Normalmente algo como ORCL ou XE."
+
+#: src/metabase/driver/oracle.clj
+msgid "Optional if using service name"
+msgstr "Opcional se estiver usando o nome do serviço"
+
+#: src/metabase/driver/oracle.clj
+msgid "Oracle service name"
+msgstr "Nome do serviço Oracle"
+
+#: src/metabase/driver/oracle.clj
+msgid "Optional TNS alias"
+msgstr "Apelido opcional TNS"
+
+#: src/metabase/driver/presto.clj
+msgid "hive"
+msgstr "hive"
+
+#: src/metabase/driver/redshift.clj
+msgid "my-cluster-name.abcd1234.us-east-1.redshift.amazonaws.com"
+msgstr "nome-meu-cluster.abcd1234.us-east-1.redshift.amazonaws.com"
+
+#: src/metabase/driver/redshift.clj
+msgid "toucan_sightings"
+msgstr "toucan_sightings"
+
+#: src/metabase/driver/sparksql.clj
+msgid "default"
+msgstr "padrão"
+
+#: src/metabase/driver/sqlite.clj
+msgid "Filename"
+msgstr "Nome do arquivo"
+
+#: src/metabase/driver/sqlite.clj
+msgid "/home/camsaul/toucan_sightings.sqlite 😋"
+msgstr "/home/camsaul/toucan_sightings.sqlite 😋"
+
+#: src/metabase/driver/sqlserver.clj
+msgid "BirdsOfTheWorld"
+msgstr "PássarosDoMundo"
+
+#: src/metabase/driver/sqlserver.clj
+msgid "Database instance name"
+msgstr "Nome da instância do banco de dados"
+
+#: src/metabase/driver/sqlserver.clj
+msgid "N/A"
+msgstr "N/D"
+
+#: src/metabase/driver/sqlserver.clj
+msgid "Windows domain"
+msgstr "Domínio Windows"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:407
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:413
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:422
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:428
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:437
+msgid "Labels"
+msgstr "Rótulos"
+
+#: frontend/src/metabase/admin/people/components/GroupDetail.jsx:329
+msgid "Add members"
+msgstr "Adicionar membros"
+
+#: frontend/src/metabase/entities/collections.js:108
+msgid "Collection it's saved in"
+msgstr "Coleção que está salva"
+
+#: frontend/src/metabase/lib/groups.js:4
+msgid "All Users"
+msgstr "Todos os usuários"
+
+#: frontend/src/metabase/lib/groups.js:5
+msgid "Administrators"
+msgstr "Administradores"
+
+#: frontend/src/metabase/lib/groups.js:6
+msgid "MetaBot"
+msgstr "MetaBot"
+
+#: frontend/src/metabase/public/components/widgets/EmbedModalContent.jsx:290
+msgid "Sharing"
+msgstr "Compartilhamento"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:156
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:169
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:177
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:200
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:206
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:217
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:233
+#: frontend/src/metabase/visualizations/visualizations/Funnel.jsx:81
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:71
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:76
+#: frontend/src/metabase/visualizations/visualizations/PieChart.jsx:82
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:41
+#: frontend/src/metabase/visualizations/visualizations/Progress.jsx:47
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:72
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:85
+#: frontend/src/metabase/visualizations/visualizations/Text.jsx:98
+msgid "Display"
+msgstr "Exibição"
+
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:283
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:316
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:329
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:344
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:356
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:362
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:370
+#: frontend/src/metabase/visualizations/lib/settings/graph.js:400
+msgid "Axes"
+msgstr "Eixos"
+
+#: frontend/src/metabase/visualizations/visualizations/Table.jsx:93
+msgid "Formatting"
+msgstr "Formatação"
+
+#: frontend/src/metabase/containers/Overworld.jsx:105
+msgid "Try these x-rays based on your data."
+msgstr "Experimente esses Raios-X que criei com seus dados."
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:35
+msgid "There was a problem displaying this chart."
+msgstr "Houve um problema ao exibir esse gráfico."
+
+#: frontend/src/metabase/visualizations/components/Visualization.jsx:36
+msgid "Sorry, you don't have permission to see this card."
+msgstr "Desculpe, mas você não tem permissão para visualizar esse cartão."
+
+#. Não tenho certeza se seria a tradução mais precisa. Alguma sugestão?
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:51
+msgid "Just a heads up:"
+msgstr "Só um aviso rápido:"
+
+#: frontend/src/metabase/admin/databases/components/DeleteDatabaseModal.jsx:59
+msgid "{0} without the Sample Dataset, the Query Builder tutorial won't work. You can always restore the Sample Dataset, but any questions you've saved using this data will be lost."
+msgstr "{0} sem o conjunto de dados de exemplo, o tutorial do editor de consultas não irá funcionar. Você pode sempre restaurar o conjunto de dados de exemplo mas qualquer pergunta que você tenha salvado usando esses dados será perdida."
+
+#. Fala Maurício, provavelmente foi detalhe, mas ajustei esse e mais um campo. Se precisar de ajuda, só falar! Parabéns pelo trabalho!!!
+#: frontend/src/metabase/qb/components/drill/AutomaticDashboardDrill.jsx:27
+msgid "X-ray"
+msgstr "Raio-X"
+
+#: frontend/src/metabase/qb/components/drill/CompareToRestDrill.js:27
+msgid "Compare to the rest"
+msgstr "Comparar ao restante"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:247
+msgid "Use the Java Virtual Machine (JVM) timezone"
+msgstr "Usar o fuso horário da Maquina Virtual Java (JVM)"
+
+#: frontend/src/metabase/components/DatabaseDetailsForm.jsx:249
+msgid "We suggest you leave this off unless you're doing manual timezone casting in\n"
+"many or most of your queries with this data."
+msgstr "Nós sugerimos que você deixe desativado exceto quando você estiver fazendo conversões de fuso horário em várias ou na maioria de suas consultas com esses dados."
+
+#: frontend/src/metabase/containers/Overworld.jsx:313
+msgid "Your team's most important dashboards go here"
+msgstr "Os painéis mais importantes do seu time vão aqui"
+
+#: frontend/src/metabase/containers/Overworld.jsx:314
+msgid "Pin dashboards in {0} to have them appear in this space for everyone"
+msgstr "Fixe painéis em {0} para eles aparecerem neste espaço para todos"
+
+#: src/metabase/db.clj
+msgid "Unable to release the Liquibase lock after a migration failure"
+msgstr "Não foi possível liberar a trava no Liquibase depois de uma falha de migração"
+
+#: src/metabase/driver/bigquery.clj
+msgid "Use JVM Time Zone"
+msgstr "Use o fuso horário da JVM"
+
diff --git a/package.json b/package.json
index 4f77edadc538ebe6856e56e2fdcf85de219d1d2f..454928b50a96867c30bb128df60e0d51caea3822 100644
--- a/package.json
+++ b/package.json
@@ -6,25 +6,28 @@
   "repository": "https://github.com/metabase/metabase",
   "license": "private",
   "engines": {
-    "node": ">=6.7.0",
-    "npm": "2.15.9"
+    "node": ">=8.11.4",
+    "npm": "8.11.4"
   },
   "dependencies": {
     "ace-builds": "^1.2.2",
     "babel-polyfill": "^6.6.1",
     "c-3po": "^0.7.3",
     "chevrotain": "0.21.0",
+    "classlist-polyfill": "^1.2.0",
     "classnames": "^2.1.3",
     "color": "^3.0.0",
+    "color-harmony": "^0.3.0",
     "crossfilter": "^1.3.12",
     "cxs": "^5.0.0",
     "d3": "^3.5.17",
+    "d3-scale": "^2.1.0",
     "dc": "^2.0.0",
     "diff": "^3.2.0",
     "grid-styled": "^4.1.0",
     "history": "3",
     "humanize-plus": "^1.8.1",
-    "icepick": "2",
+    "icepick": "2.3.0",
     "iframe-resizer": "^3.5.11",
     "inflection": "^1.7.1",
     "isomorphic-fetch": "^2.2.1",
@@ -33,6 +36,7 @@
     "leaflet": "^1.2.0",
     "leaflet-draw": "^0.4.9",
     "leaflet.heat": "^0.2.0",
+    "lodash.memoize": "^4.1.2",
     "moment": "2.19.3",
     "node-libs-browser": "^2.0.0",
     "normalizr": "^3.0.2",
@@ -100,6 +104,7 @@
     "babel-preset-stage-0": "^6.5.0",
     "babel-register": "^6.11.6",
     "banner-webpack-plugin": "^0.2.3",
+    "color-diff": "^1.1.0",
     "concurrently": "^3.1.0",
     "css-loader": "^0.28.7",
     "documentation": "^4.0.0-rc.1",
@@ -128,6 +133,8 @@
     "jasmine-reporters": "^2.2.0",
     "jasmine-spec-reporter": "^3.0.0",
     "jest": "^19.0.2",
+    "jest-localstorage-mock": "^2.2.0",
+    "jscodeshift": "0.5.0",
     "jsonwebtoken": "^7.2.1",
     "karma": "^1.3.0",
     "karma-chrome-launcher": "^2.0.0",
@@ -158,7 +165,7 @@
   "scripts": {
     "dev": "concurrently --kill-others -p name -n 'backend,frontend' -c 'blue,green' 'lein ring server' 'yarn build-hot'",
     "lint": "yarn lint-eslint && yarn lint-prettier",
-    "lint-eslint": "yarn && eslint --ext .js --ext .jsx --max-warnings 0 frontend/src frontend/test",
+    "lint-eslint": "yarn && eslint --ext .js --ext .jsx --rulesdir frontend/lint/eslint-rules --max-warnings 0 frontend/src frontend/test",
     "lint-prettier": "yarn && prettier -l 'frontend/**/*.{js,jsx,css}' || (echo '\nThese files are not formatted correctly. Did you forget to \"yarn prettier\"?' && false)",
     "flow": "yarn && flow check",
     "test": "yarn test-unit && yarn test-integrated && yarn test-karma",
@@ -174,11 +181,15 @@
     "build-watch": "yarn && webpack --watch",
     "build-hot": "yarn && NODE_ENV=hot webpack-dev-server --progress",
     "build-stats": "yarn && webpack --json > stats.json",
+    "build-shared": "yarn && webpack --config webpack.shared.config.js",
     "start": "yarn build && lein ring server",
     "precommit": "lint-staged",
     "preinstall": "echo $npm_execpath | grep -q yarn || echo '\\033[0;33mSorry, npm is not supported. Please use Yarn (https://yarnpkg.com/).\\033[0m'",
     "prettier": "prettier --write 'frontend/**/*.{js,jsx,css}'",
-    "docs": "documentation build -f html -o frontend/docs frontend/src/metabase-lib/lib/**"
+    "docs": "documentation build -f html -o frontend/docs frontend/src/metabase-lib/lib/**",
+    "ci": "yarn ci-frontend && yarn ci-backend",
+    "ci-frontend": "yarn lint && yarn flow && yarn test",
+    "ci-backend": "lein docstring-checker && lein bikeshed && lein eastwood && lein test"
   },
   "lint-staged": {
     "frontend/**/*.{js,jsx,css}": [
diff --git a/project.clj b/project.clj
index caf99eccebe35508b7bbdc63f4a94607ce11c9a2..be31a81289277cf838477cd131f052d6395ce3ee 100644
--- a/project.clj
+++ b/project.clj
@@ -20,13 +20,14 @@
                  [org.clojure/java.jdbc "0.7.6"]                      ; basic JDBC access from Clojure
                  [org.clojure/math.combinatorics "0.1.4"]             ; combinatorics functions
                  [org.clojure/math.numeric-tower "0.0.4"]             ; math functions like `ceil`
-                 [org.clojure/tools.logging "0.3.1"]                  ; logging framework
+                 [org.clojure/tools.logging "0.4.1"]                  ; logging framework
                  [org.clojure/tools.namespace "0.2.10"]
                  [amalloy/ring-buffer "1.2.1"
                   :exclusions [org.clojure/clojure
                                org.clojure/clojurescript]]            ; fixed length queue implementation, used in log buffering
                  [amalloy/ring-gzip-middleware "0.1.3"]               ; Ring middleware to GZIP responses if client can handle it
-                 [aleph "0.4.5-alpha2"]                               ; Async HTTP library; WebSockets
+                 [aleph "0.4.5-alpha2"                                ; Async HTTP library; WebSockets
+                  :exclusions [org.clojure/tools.logging]]
                  [buddy/buddy-core "1.2.0"]                           ; various cryptograhpic functions
                  [buddy/buddy-sign "1.5.0"]                           ; JSON Web Tokens; High-Level message signing library
                  [cheshire "5.7.0"]                                   ; fast JSON encoding (used by Ring JSON middleware)
@@ -44,6 +45,9 @@
                                org.apache.httpcomponents/httpclient
                                net.sourceforge.nekohtml/nekohtml
                                ring/ring-core]]
+                 [com.clearspring.analytics/stream "2.9.5"            ; Various sketching algorithms
+                   :exclusions [org.slf4j/slf4j-api
+                                it.unimi.dsi/fastutil]]
                  [com.draines/postal "2.0.2"]                         ; SMTP library
                  [com.google.apis/google-api-services-analytics       ; Google Analytics Java Client Library
                   "v3-rev154-1.23.0"]
@@ -67,9 +71,8 @@
                  [io.forward/yaml "1.0.6"                             ; Clojure wrapper for YAML library SnakeYAML (which we already use for liquidbase)
                   :exclusions [org.clojure/clojure
                                org.yaml/snakeyaml]]
-                 [kixi/stats "0.3.10"                                 ; Various statistic measures implemented as transducers
-                  :exclusions [org.clojure/test.check                 ; test.check and AVL trees are used in kixi.stats.random. Remove exlusion if using.
-                               org.clojure/data.avl]]
+                 [kixi/stats "0.4.1"                                  ; Various statistic measures implemented as transducers
+                  :exclusions [org.clojure/data.avl]]
                  [log4j/log4j "1.2.17"                                ; logging framework
                   :exclusions [javax.mail/mail
                                javax.jms/jms
@@ -83,27 +86,27 @@
                  [net.sf.cssbox/cssbox "4.12"                         ; HTML / CSS rendering
                   :exclusions [org.slf4j/slf4j-api]]
                  [org.clojars.pntblnk/clj-ldap "0.0.12"]              ; LDAP client
-                 [org.liquibase/liquibase-core "3.5.3"]               ; migration management (Java lib)
+                 [org.liquibase/liquibase-core "3.6.2"                ; migration management (Java lib)
+                  :exclusions [ch.qos.logback/logback-classic]]
                  [org.postgresql/postgresql "42.2.2"]                 ; Postgres driver
                  [org.slf4j/slf4j-log4j12 "1.7.25"]                   ; abstraction for logging frameworks -- allows end user to plug in desired logging framework at deployment time
                  [org.tcrawley/dynapath "0.2.5"]                      ; Dynamically add Jars (e.g. Oracle or Vertica) to classpath
                  [org.xerial/sqlite-jdbc "3.21.0.1"]                  ; SQLite driver
                  [org.yaml/snakeyaml "1.18"]                          ; YAML parser (required by liquibase)
-                 [prismatic/schema "1.1.5"]                           ; Data schema declaration and validation library
+                 [prismatic/schema "1.1.9"]                           ; Data schema declaration and validation library
                  [puppetlabs/i18n "0.8.0"]                            ; Internationalization library
                  [redux "0.1.4"]                                      ; Utility functions for building and composing transducers
                  [ring/ring-core "1.6.0"]
                  [ring/ring-jetty-adapter "1.6.0"]                    ; Ring adapter using Jetty webserver (used to run a Ring server for unit tests)
                  [ring/ring-json "0.4.0"]                             ; Ring middleware for reading/writing JSON automatically
                  [stencil "0.5.0"]                                    ; Mustache templates for Clojure
-                 [toucan "1.1.7"                                      ; Model layer, hydration, and DB utilities
+                 [toucan "1.1.9"                                      ; Model layer, hydration, and DB utilities
                   :exclusions [honeysql]]]
   :repositories [["bintray" "https://dl.bintray.com/crate/crate"]     ; Repo for Crate JDBC driver
                  ["redshift" "https://s3.amazonaws.com/redshift-driver-downloads"]]
   :plugins [[lein-environ "1.1.0"]                                    ; easy access to environment variables
             [lein-ring "0.12.3"                                       ; start the HTTP server with 'lein ring server'
-             :exclusions [org.clojure/clojure]]                       ; TODO - should this be a dev dependency ?
-            [puppetlabs/i18n "0.8.0"]]                                ; i18n helpers
+             :exclusions [org.clojure/clojure]]]                      ; TODO - should this be a dev dependency ?
   :main ^:skip-aot metabase.core
   :manifest {"Liquibase-Package" "liquibase.change,liquibase.changelog,liquibase.database,liquibase.parser,liquibase.precondition,liquibase.datatype,liquibase.serializer,liquibase.sqlgenerator,liquibase.executor,liquibase.snapshot,liquibase.logging,liquibase.diff,liquibase.structure,liquibase.structurecompare,liquibase.lockservice,liquibase.sdk,liquibase.ext"}
   :target-path "target/%s"
@@ -134,7 +137,7 @@
   :docstring-checker {:include [#"^metabase"]
                       :exclude [#"test"
                                 #"^metabase\.http-client$"]}
-  :profiles {:dev {:dependencies [[expectations "2.2.0-beta2"]              ; unit tests
+  :profiles {:dev {:dependencies [[expectations "2.2.0-beta2"]        ; unit tests
                                   [ring/ring-mock "0.3.0"]]           ; Library to create mock Ring requests for unit tests
                    :plugins [[docstring-checker "1.0.2"]              ; Check that all public vars have docstrings. Run with 'lein docstring-checker'
                              [jonase/eastwood "0.2.6"
@@ -146,8 +149,9 @@
                                            org.clojure/tools.namespace]]]
                    :env {:mb-run-mode "dev"}
                    :jvm-opts ["-Dlogfile.path=target/log"]
-                   ;; Log appender class needs to be compiled for log4j to use it,
-                   :aot [metabase.logger]}
+                   ;; Log appender class needs to be compiled for log4j to use it. Same with the Quartz class load helper
+                   :aot [metabase.logger
+                         metabase.task.DynamicClassLoadHelper]}
              :ci {:jvm-opts ["-Xmx2500m"]}
              :reflection-warnings {:global-vars {*warn-on-reflection* true}} ; run `lein check-reflection-warnings` to check for reflection warnings
              :expectations {:injections [(require 'metabase.test-setup  ; for test setup stuff
diff --git a/resources/automagic_dashboards/comparison/Country.yaml b/resources/automagic_dashboards/comparison/Country.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..3c96ccc362bed19bbf78094dbe5b6ed3dedb1f92
--- /dev/null
+++ b/resources/automagic_dashboards/comparison/Country.yaml
@@ -0,0 +1,15 @@
+title: A look at your [[this]]
+transient_title: Here's an overview of your [[this]]
+description: How [[this]] is distributed and more.
+applies_to: GenericTable.Country
+metrics:
+- Count: [count]
+cards:
+  - Distribution:
+      title: How [[this]] is distributed
+      visualization:
+        map:
+          map.type: region
+          map.region: world_countries
+      metrics: Count
+      dimensions: this
\ No newline at end of file
diff --git a/resources/automagic_dashboards/comparison/FK.yaml b/resources/automagic_dashboards/comparison/FK.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..17273729845332ddc23177050ff62965499ba216
--- /dev/null
+++ b/resources/automagic_dashboards/comparison/FK.yaml
@@ -0,0 +1,12 @@
+title: A look at your [[this]]
+transient_title: Here's an overview of your [[this]]
+description: How [[this]] is distributed and more.
+applies_to: GenericTable.FK
+metrics:
+- Count: [count]
+cards:
+  - Distribution:
+      title: How [[this]] is distributed
+      visualization: bar
+      metrics: Count
+      dimensions: this
diff --git a/resources/automagic_dashboards/comparison/GenericField.yaml b/resources/automagic_dashboards/comparison/GenericField.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..e7552c625b87d4338d5a0f2ee0cc8a0222589e46
--- /dev/null
+++ b/resources/automagic_dashboards/comparison/GenericField.yaml
@@ -0,0 +1,14 @@
+title: A look at your [[this]]
+transient_title: Here's an overview of your [[this]]
+description: How [[this]] is distributed and more.
+applies_to: GenericTable.*
+metrics:
+- Count: [count]
+cards:
+  - Distribution:
+      title: How [[this]] is distributed
+      visualization: bar
+      metrics: Count
+      dimensions:
+        - this:
+            aggregation: default
diff --git a/resources/automagic_dashboards/comparison/State.yaml b/resources/automagic_dashboards/comparison/State.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..999244b997b7da862050756c6eaab2f3a89ea7cd
--- /dev/null
+++ b/resources/automagic_dashboards/comparison/State.yaml
@@ -0,0 +1,15 @@
+title: A look at your [[this]]
+transient_title: Here's an overview of your [[this]]
+description: How [[this]] is distributed and more.
+applies_to: GenericTable.State
+metrics:
+- Count: [count]
+cards:
+  - Distribution:
+      title: How [[this]] is distributed
+      visualization:
+        map:
+          map.type: region
+          map.region: us_states
+      metrics: Count
+      dimensions: this
diff --git a/resources/automagic_dashboards/field/Country.yaml b/resources/automagic_dashboards/field/Country.yaml
index fa493bb6e66d221a2461f005f143cc0bd20b9690..356639eb845d87437a4370d4df94919a41365860 100644
--- a/resources/automagic_dashboards/field/Country.yaml
+++ b/resources/automagic_dashboards/field/Country.yaml
@@ -38,7 +38,8 @@ groups:
   - Overview:
       title: Overview
   - Breakdowns:
-      title: How [[this]] are distributed
+      title: How the [[this]] is distributed
+      comparison_title: How they compare by distribution
 cards:
   - Count:
       title: Count
@@ -60,7 +61,7 @@ cards:
       group: Overview
       width: 6
   - Distribution:
-      title: Distribution of [[this]]
+      title: How the [[this]] is distributed
       visualization:
         map:
           map.type: region
diff --git a/resources/automagic_dashboards/field/DateTime.yaml b/resources/automagic_dashboards/field/DateTime.yaml
index 8af35916858fee9120726dc52d316e1fb9e77ef5..97a48b8072a407722c3df5bbbe5ab0405eca7093 100644
--- a/resources/automagic_dashboards/field/DateTime.yaml
+++ b/resources/automagic_dashboards/field/DateTime.yaml
@@ -32,9 +32,11 @@ groups:
   - Overview:
       title: Overview
   - Breakdowns:
-      title: How [[this]] is distributed
+      title: How the [[this]] is distributed
+      comparison_title: How they compare by distribution
   - Seasonality:
-      title: Seasonal patterns in [[this]]
+      title: Seasonal patterns in the [[this]]
+      comparison_title: How they compare by seasonality
 cards:
   - Count:
       title: Count
diff --git a/resources/automagic_dashboards/field/GenericField.yaml b/resources/automagic_dashboards/field/GenericField.yaml
index 71d7143394f2565736faabb1212144b36bedb305..436cd7ec27ec9614cd0bfea55f17fd4aeecec619 100644
--- a/resources/automagic_dashboards/field/GenericField.yaml
+++ b/resources/automagic_dashboards/field/GenericField.yaml
@@ -38,7 +38,8 @@ groups:
   - Overview:
       title: Overview
   - Breakdowns:
-      title: How [[this]] are distributed
+      title: How the [[this]] is distributed
+      comparison_title: How they compare by distribution
 cards:
   - Count:
       title: Count
@@ -60,7 +61,7 @@ cards:
       group: Overview
       width: 6
   - Distribution:
-      title: How [[this]] is distributed
+      title: How the [[this]] is distributed
       visualization: bar
       metrics: Count
       dimensions: this
@@ -68,12 +69,13 @@ cards:
       width: 12
   - ByNumber:
       title: "[[GenericNumber]] by [[this]]"
-      visualization: line
+      visualization: bar
       metrics:
       - Sum
       - Avg
       dimensions: this
       group: Breakdowns
+      height: 8
   - Crosstab:
       title: "[[this]] by [[GenericCategoryMedium]]"
       visualization: table
diff --git a/resources/automagic_dashboards/field/Number.yaml b/resources/automagic_dashboards/field/Number.yaml
index 937e04ec5c0dfeb17a1e3476c76ea85495433b9a..d9cc6991a751d1101afbbac9d9ca61ff51501f56 100644
--- a/resources/automagic_dashboards/field/Number.yaml
+++ b/resources/automagic_dashboards/field/Number.yaml
@@ -38,11 +38,14 @@ groups:
   - Overview:
       title: Overview
   - Breakdowns:
-      title: How [[this]] is distributed across categories
+      title: How the [[this]] is distributed across categories
+      comparison_title: How they compare by distribution
   - Seasonality:
-      title: How [[this]] changes with time
+      title: How the [[this]] changes with time
+      comparison_title: How they compare across time
   - Geographical:
-      title: How [[this]] is distributed geographically
+      title: How the [[this]] is distributed geographically
+      comparison_title: How they compare acrosss location
 cards:
   - Count:
       title: Count
@@ -75,7 +78,7 @@ cards:
       group: Overview
       width: 9
   - Distribution:
-      title: How [[this]] is distributed
+      title: How the [[this]] is distributed
       visualization: bar
       metrics: Count
       dimensions:
diff --git a/resources/automagic_dashboards/field/State.yaml b/resources/automagic_dashboards/field/State.yaml
index 2df61dca59ed4c93b5e5eb2c57ca5ff746e10690..57ece21b39d7f00d259add617e81c57960df6ff2 100644
--- a/resources/automagic_dashboards/field/State.yaml
+++ b/resources/automagic_dashboards/field/State.yaml
@@ -38,7 +38,8 @@ groups:
   - Overview:
       title: Overview
   - Breakdowns:
-      title: How [[this]] are distributed
+      title: How the [[this]] is distributed
+      comparison_title: How they compare by distribution
 cards:
   - Count:
       title: Count
diff --git a/resources/automagic_dashboards/metric/GenericMetric.yaml b/resources/automagic_dashboards/metric/GenericMetric.yaml
index 46287df7733281d6a26caeae4dc995a39a2901e2..25b09bad3c07f2e87723088c10fcb8a047bc4aa5 100644
--- a/resources/automagic_dashboards/metric/GenericMetric.yaml
+++ b/resources/automagic_dashboards/metric/GenericMetric.yaml
@@ -1,5 +1,5 @@
-title: A look at your [[this]]
-transient_title: Here's a quick look at your [[this]]
+title: A look at the [[this]]
+transient_title: Here's a quick look at the [[this]]
 description: How it's distributed across time and other categories.
 applies_to: GenericTable
 metrics:
@@ -35,15 +35,21 @@ dimensions:
       field_type: GenericTable.ZipCode
 groups:
   - Periodicity:
-      title: "[[this]] over time"
+      title: The [[this]] over time
+      comparison_title: How they compare across time
   - Geographical:
-      title: "[[this]] by location"
+      title: The [[this]] by location
+      comparison_title: How they compare acrosss location
   - Categories:
-      title: How [[this]] is distributed across different categories
+      title: How this metric is distributed across different categories
+      comparison_title: How they compare across different categories
   - Numbers:
-      title: How [[this]] is distributed across different numbers
-  - LargeCategories:
-      title: Top and bottom [[this]]
+      title: How this metric is distributed across different numbers
+      comparison_title: How they compare by across different numbers
+  - LargeCategoriesTop:
+      title: Top 5 per category
+  - LargeCategoriesBottom:
+      title: Bottom 5 per category
 dashboard_filters:
   - Timestamp
   - State
@@ -119,9 +125,11 @@ cards:
           map.region: us_states
   - ByNumber:
       group: Numbers
-      title: How [[this]] is distributed across [[GenericNumber]]
+      title: "[[this]] by [[GenericNumber]]"
       metrics: this
-      dimensions: GenericNumber
+      dimensions:
+        - GenericNumber:
+            aggregation: default
       visualization: bar
   - ByCategoryMedium:
       group: Categories
@@ -151,7 +159,7 @@ cards:
       order_by:
         this: descending
   - ByCategoryLargeTop:
-      group: LargeCategories
+      group: LargeCategoriesTop
       title: "[[this]] per [[GenericCategoryLarge]], top 5"
       metrics: this
       dimensions: GenericCategoryLarge
@@ -160,7 +168,7 @@ cards:
         this: descending
       limit: 5
   - ByCategoryLargeBottom:
-      group: LargeCategories
+      group: LargeCategoriesBottom
       title: "[[this]] per [[GenericCategoryLarge]], bottom 5"
       metrics: this
       dimensions: GenericCategoryLarge
diff --git a/resources/automagic_dashboards/question/GenericQuestion.yaml b/resources/automagic_dashboards/question/GenericQuestion.yaml
index a7d0cfaa713b033f4af334319fbefdb4fbc5fcaa..ad7a7b8b067df81e1decbd24ceb1b06efb700844 100644
--- a/resources/automagic_dashboards/question/GenericQuestion.yaml
+++ b/resources/automagic_dashboards/question/GenericQuestion.yaml
@@ -1,4 +1,4 @@
 title: "A closer look at your [[this]]"
 transient_title: "Here's a closer look at your [[this]]"
-description: Here is breakdown of metrics and dimensions used in [[this]].
-applies_to: GenericTable
\ No newline at end of file
+description: A closer look at the metrics and dimensions used in this saved question.
+applies_to: GenericTable
diff --git a/resources/automagic_dashboards/table/GenericTable.yaml b/resources/automagic_dashboards/table/GenericTable.yaml
index cb9744168ce4201e9fcf07687c4cd9c450e53fae..49160b3017cf88182997e29cd86de3452d614fff 100644
--- a/resources/automagic_dashboards/table/GenericTable.yaml
+++ b/resources/automagic_dashboards/table/GenericTable.yaml
@@ -20,10 +20,6 @@ dimensions:
   - Source:
       field_type: GenericTable.Source
       score: 100
-  - GenericCategorySmall:
-      field_type: GenericTable.Category
-      score: 80
-      max_cardinality: 5
   - GenericCategoryMedium:
       field_type: GenericTable.Category
       score: 75
@@ -91,13 +87,16 @@ groups:
 - Overview:
     title: Summary
 - Singletons:
-    title: These are the same for all your [[this]]
+    title: These are the same for all your [[this.short-name]]
 - ByTime:
-    title: "[[this]] across time"
+    title: "These [[this.short-name]] across time"
+    comparison_title: How they compare across time
 - Geographical:
-    title: Where your [[this]] are
+    title: Where your [[this.short-name]] are
+    comparison_title: How they compare acrosss location
 - General:
-    title: How [[this]] are distributed
+    title: How these [[this.short-name]] are distributed
+    comparison_title: How they compare by distribution
 dashboard_filters:
 - Timestamp
 - Date
@@ -112,13 +111,13 @@ dashboard_filters:
 cards:
 # Overview
   - Rowcount:
-      title: Total [[this]]
+      title: Total [[this.short-name]]
       visualization: scalar
       metrics: Count
       score: 100
       group: Overview
   - RowcountLast30Days:
-      title: New [[this]] in the last 30 days
+      title: "[[this.short-name]] added in the last 30 days"
       visualization: scalar
       metrics: Count
       score: 100
@@ -132,7 +131,7 @@ cards:
       group: Overview
 # General
   - NumberDistribution:
-      title: How [[this]] are distributed across [[GenericNumber]]
+      title: "[[this.short-name]] by [[GenericNumber]]"
       dimensions:
         - GenericNumber:
             aggregation: default
@@ -141,7 +140,7 @@ cards:
       score: 90
       group: General
   - CountByCategoryMedium:
-      title: "[[this]] per [[GenericCategoryMedium]]"
+      title: "[[this.short-name]] per [[GenericCategoryMedium]]"
       dimensions: GenericCategoryMedium
       metrics: Count
       visualization: row
@@ -151,7 +150,7 @@ cards:
       order_by:
         - Count: descending
   - CountByCategoryLarge:
-      title: "[[this]] per [[GenericCategoryLarge]]"
+      title: "[[this.short-name]] per [[GenericCategoryLarge]]"
       dimensions: GenericCategoryLarge
       metrics: Count
       visualization: table
@@ -162,7 +161,7 @@ cards:
         - Count: descending
 # Geographical
   - CountByCountry:
-      title: "[[this]] per country"
+      title: "[[this.short-name]] per country"
       metrics: Count
       dimensions: Country
       score: 90
@@ -173,7 +172,7 @@ cards:
       group: Geographical
       height: 6
   - CountByState:
-      title: "[[this]] per state"
+      title: "[[this.short-name]] per state"
       metrics: Count
       dimensions: State
       score: 90
@@ -184,7 +183,7 @@ cards:
       group: Geographical
       height: 6
   - CountByCoords:
-      title: "[[this]] by coordinates"
+      title: "[[this.short-name]] by coordinates"
       metrics: Count
       dimensions:
         - Long
@@ -195,42 +194,42 @@ cards:
       height: 6
 # By Time
   - CountByJoinDate:
-      title: "[[this]] that have joined over time"
+      title: "[[this.short-name]] that have joined over time"
       visualization: line
       dimensions: JoinTimestamp
       metrics: Count
       score: 90
       group: ByTime
   - CountByJoinDate:
-      title: "[[this]] that have joined over time"
+      title: "[[this.short-name]] that have joined over time"
       visualization: line
       dimensions: JoinDate
       metrics: Count
       score: 90
       group: ByTime
   - CountByCreateDate:
-      title: New [[this]] over time
+      title: New [[this.short-name]] over time
       visualization: line
       dimensions: CreateTimestamp
       metrics: Count
       score: 90
       group: ByTime
   - CountByCreateDate:
-      title: New [[this]] over time
+      title: New [[this.short-name]] over time
       visualization: line
       dimensions: CreateDate
       metrics: Count
       score: 90
       group: ByTime
   - CountByTimestamp:
-      title: "[[this]] by [[Timestamp]]"
+      title: "[[this.short-name]] by [[Timestamp]]"
       visualization: line
       dimensions: Timestamp
       metrics: Count
       score: 20
       group: ByTime
   - CountByTimestamp:
-      title: "[[this]] by [[Timestamp]]"
+      title: "[[this.short-name]] by [[Timestamp]]"
       visualization: line
       dimensions: Date
       metrics: Count
@@ -371,7 +370,7 @@ cards:
       group: ByTime
       x_label: "[[Timestamp]]"
   - DayOfWeekCreateDate:
-      title: Weekdays when new [[this]] were added
+      title: Weekdays when [[this.short-name]] were added
       visualization: bar
       dimensions:
         - CreateTimestamp:
@@ -381,7 +380,7 @@ cards:
       group: ByTime
       x_label: Created At by day of the week
   - DayOfWeekCreateDate:
-      title: Weekdays when new [[this]] were added
+      title: Weekdays when [[this.short-name]] were added
       visualization: bar
       dimensions:
         - CreateDate:
@@ -391,7 +390,7 @@ cards:
       group: ByTime
       x_label: Created At by day of the week
   - HourOfDayCreateDate:
-      title: Hours when new [[this]] were added
+      title: Hours when [[this.short-name]] were added
       visualization: bar
       dimensions:
       - CreateTimestamp:
@@ -401,7 +400,7 @@ cards:
       group: ByTime
       x_label: Created At by hour of the day
   - HourOfDayCreateDate:
-      title: Hours when new [[this]] were added
+      title: Hours when [[this.short-name]] were added
       visualization: bar
       dimensions:
       - CreateTime:
@@ -411,7 +410,7 @@ cards:
       group: ByTime
       x_label: Created At by hour of the day
   - DayOfMonthCreateDate:
-      title: Days when new [[this]] were added
+      title: Days when [[this.short-name]] were added
       visualization: bar
       dimensions:
         - CreateTimestamp:
@@ -421,7 +420,7 @@ cards:
       group: ByTime
       x_label: Created At by day of the month
   - DayOfMonthCreateDate:
-      title: Days when new [[this]] were added
+      title: Days when [[this.short-name]] were added
       visualization: bar
       dimensions:
         - CreateDate:
@@ -431,7 +430,7 @@ cards:
       group: ByTime
       x_label: Created At by day of the month
   - MonthOfYearCreateDate:
-      title: Months when new [[this]] were added
+      title: Months when [[this.short-name]] were added
       visualization: bar
       dimensions:
         - CreateTimestamp:
@@ -441,7 +440,7 @@ cards:
       group: ByTime
       x_label: Created At by month of the year
   - MonthOfYearCreateDate:
-      title: Months when new [[this]] were added
+      title: Months when [[this.short-name]] were added
       visualization: bar
       dimensions:
         - CreateDate:
@@ -451,7 +450,7 @@ cards:
       group: ByTime
       x_label: Created At by month of the year
   - QuerterOfYearCreateDate:
-      title: Quarters when new [[this]] were added
+      title: Quarters when [[this.short-name]] were added
       visualization: bar
       dimensions:
         - CreateTimestamp:
@@ -461,7 +460,7 @@ cards:
       group: ByTime
       x_label: Created At by quarter of the year
   - QuerterOfYearCreateDate:
-      title: Quarters when new [[this]] were added
+      title: Quarters when [[this.short-name]] were added
       visualization: bar
       dimensions:
         - CreateDate:
@@ -471,7 +470,7 @@ cards:
       group: ByTime
       x_label: Created At by quarter of the year
   - DayOfWeekJoinDate:
-      title: Weekdays when [[this]] joined
+      title: Weekdays when [[this.short-name]] joined
       visualization: bar
       dimensions:
         - JoinTimestamp:
@@ -481,7 +480,7 @@ cards:
       group: ByTime
       x_label: Join date by day of the week
   - DayOfWeekJoinDate:
-      title: Weekdays when [[this]] joined
+      title: Weekdays when [[this.short-name]] joined
       visualization: bar
       dimensions:
         - JoinDate:
@@ -491,7 +490,7 @@ cards:
       group: ByTime
       x_label: Join date by day of the week
   - HourOfDayJoinDate:
-      title: Hours when [[this]] joined
+      title: Hours when [[this.short-name]] joined
       visualization: bar
       dimensions:
       - JoinTimestamp:
@@ -501,7 +500,7 @@ cards:
       group: ByTime
       x_label: Join date by hour of the day
   - HourOfDayJoinDate:
-      title: Hours when [[this]] joined
+      title: Hours when [[this.short-name]] joined
       visualization: bar
       dimensions:
       - JoinTime:
@@ -511,7 +510,7 @@ cards:
       group: ByTime
       x_label: Join date by hour of the day
   - DayOfMonthJoinDate:
-      title: Days of the month when [[this]] joined
+      title: Days of the month when [[this.short-name]] joined
       visualization: bar
       dimensions:
         - JoinTimestamp:
@@ -521,7 +520,7 @@ cards:
       group: ByTime
       x_label: Join date by day of the month
   - DayOfMonthJoinDate:
-      title: Days of the month when [[this]] joined
+      title: Days of the month when [[this.short-name]] joined
       visualization: bar
       dimensions:
         - JoinDate:
@@ -531,7 +530,7 @@ cards:
       group: ByTime
       x_label: Join date by day of the month
   - MonthOfYearJoinDate:
-      title: Months when [[this]] joined
+      title: Months when [[this.short-name]] joined
       visualization: bar
       dimensions:
         - JoinTimestamp:
@@ -541,7 +540,7 @@ cards:
       group: ByTime
       x_label: Join date by month of the year
   - MonthOfYearJoinDate:
-      title: Months when [[this]] joined
+      title: Months when [[this.short-name]] joined
       visualization: bar
       dimensions:
         - JoinDate:
@@ -551,7 +550,7 @@ cards:
       group: ByTime
       x_label: Join date by month of the year
   - QuerterOfYearJoinDate:
-      title: Quarters when [[this]] joined
+      title: Quarters when [[this.short-name]] joined
       visualization: bar
       dimensions:
         - JoinTimestamp:
@@ -561,7 +560,7 @@ cards:
       group: ByTime
       x_label: Join date by quarter of the year
   - QuerterOfYearJoinDate:
-      title: Quarters when [[this]] joined
+      title: Quarters when [[this.short-name]] joined
       visualization: bar
       dimensions:
         - JoinDate:
diff --git a/resources/automagic_dashboards/table/UserTable.yaml b/resources/automagic_dashboards/table/UserTable.yaml
index 1844a20527cb0c73019e8a347b9a960085c61c08..b6297d02f83611b6ea7cdda861c6dbfd49c57668 100644
--- a/resources/automagic_dashboards/table/UserTable.yaml
+++ b/resources/automagic_dashboards/table/UserTable.yaml
@@ -23,14 +23,11 @@ dimensions:
 - JoinDate:
     field_type: Date
     score: 30
-- Source:
-    field_type: Source
 - GenericNumber:
     field_type: GenericTable.Number
     score: 80
 - Source:
     field_type: GenericTable.Source
-    score: 100
 - GenericCategoryMedium:
     field_type: GenericTable.Category
     score: 75
@@ -54,9 +51,9 @@ groups:
 - Overview:
     title: Overview
 - Geographical:
-    title: Where these [[this]] are
+    title: Where these [[this.short-name]] are
 - General:
-    title: How these [[this]] are distributed
+    title: How these [[this.short-name]] are distributed
 dashboard_filters:
 - JoinDate
 - GenericCategoryMedium
@@ -66,7 +63,7 @@ dashboard_filters:
 cards:
 # Overview
   - Rowcount:
-      title: Total [[this]]
+      title: Total [[this.short-name]]
       visualization: scalar
       metrics: Count
       score: 100
@@ -74,7 +71,7 @@ cards:
       width: 5
       height: 3
   - RowcountLast30Days:
-      title: New [[this]] in the last 30 days
+      title: New [[this.short-name]] in the last 30 days
       visualization: scalar
       metrics: Count
       score: 100
@@ -84,8 +81,7 @@ cards:
       height: 3
   - NewUsersByMonth:
       visualization: line
-      title: New [[this]] per month
-      description: The number of new [[this]] each month
+      title: New [[this.short-name]] per month
       dimensions: JoinDate
       metrics: Count
       score: 100
@@ -94,7 +90,7 @@ cards:
       height: 7
 # Geographical
   - CountByCountry:
-      title: Number of [[this]] per country
+      title: Per country
       metrics: Count
       dimensions: Country
       score: 90
@@ -104,7 +100,7 @@ cards:
           map.region: world_countries
       group: Geographical
   - CountByState:
-      title: "[[this]] per state"
+      title: "Per state"
       metrics: Count
       dimensions: State
       score: 90
@@ -115,7 +111,7 @@ cards:
           map.region: us_states
       group: Geographical
   - CountByCoords:
-      title: "[[this]] by coordinates"
+      title: "By coordinates"
       metrics: Count
       dimensions:
         - Long
@@ -135,7 +131,7 @@ cards:
       score: 90
       group: General
   - CountByCategoryMedium:
-      title: "[[this]] per [[GenericCategoryMedium]]"
+      title: "Per [[GenericCategoryMedium]]"
       dimensions: GenericCategoryMedium
       metrics: Count
       visualization: row
@@ -144,8 +140,18 @@ cards:
       group: General
       order_by:
         - Count: descending
+  - CountBySource:
+      title: "Per [[Source]]"
+      dimensions: Source
+      metrics: Count
+      visualization: row
+      score: 80
+      height: 8
+      group: General
+      order_by:
+        - Count: descending
   - CountByCategoryLarge:
-      title: "[[this]] per [[GenericCategoryLarge]]"
+      title: "Per [[GenericCategoryLarge]]"
       dimensions: GenericCategoryLarge
       metrics: Count
       visualization: table
diff --git a/resources/frontend_client/app/img/collection-empty-state.png b/resources/frontend_client/app/img/collection-empty-state.png
index b712cc40eb54bd0fea36c61c98d7c8554286b172..41a19963a763ea81a8ea780db9fbe30122eeb27b 100644
Binary files a/resources/frontend_client/app/img/collection-empty-state.png and b/resources/frontend_client/app/img/collection-empty-state.png differ
diff --git a/resources/frontend_client/app/img/collection-empty-state.svg b/resources/frontend_client/app/img/collection-empty-state.svg
index 209f77a3efedf6735321ad5dd673c54b46f9aa61..5982fd752703af5439de03f1cd7ef8f178abdd7b 100644
--- a/resources/frontend_client/app/img/collection-empty-state.svg
+++ b/resources/frontend_client/app/img/collection-empty-state.svg
@@ -1,13 +1,12 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<svg width="270px" height="267px" viewBox="0 0 270 267" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <!-- Generator: Sketch 50 (54983) - http://www.bohemiancoding.com/sketch -->
+<svg width="198px" height="202px" viewBox="0 0 198 202" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
     <title>Artboard</title>
     <desc>Created with Sketch.</desc>
     <defs></defs>
     <g id="Artboard" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
         <g id="Group-3" transform="translate(2.000000, 2.000000)">
-            <ellipse id="Oval-2" fill="#F9FBFC" style="mix-blend-mode: multiply;" opacity="0.699999988" cx="143" cy="123" rx="122" ry="123"></ellipse>
-            <g id="Group-5" transform="translate(10.000000, 62.000000)">
+            <g id="Group-5" transform="translate(10.000000, 0.000000)">
                 <path d="M113.382413,151.888668 L140.161554,198.409543" id="Path-6" stroke="#DCE1E4" stroke-width="3.97614314"></path>
                 <rect id="Rectangle-6" stroke="#DCE1E4" stroke-width="1.19284294" fill="#F8F9FA" x="-0.281493046" y="18.0914513" width="174.959714" height="118.88668" rx="1.59045726"></rect>
                 <polygon id="Triangle-Copy-3" fill="#EDF0F1" transform="translate(164.335086, 83.335086) rotate(-135.000000) translate(-164.335086, -83.335086) " points="164.335086 81.2732123 166.989737 85.3969602 161.680436 85.3969602"></polygon>
@@ -27,9 +26,9 @@
                 <rect id="Rectangle-10-Copy" fill="#DCE1E4" x="62.601227" y="13.1332008" width="49.194274" height="18.6878728" rx="2.38568588"></rect>
                 <rect id="Rectangle-10" stroke="#DCE1E4" stroke-width="1.19284294" fill="#F8F9FA" x="62.0048055" y="10.5367793" width="50.387117" height="19.8807157" rx="2.38568588"></rect>
             </g>
-            <rect id="Rectangle-7" stroke="#DCE1E4" stroke-width="1.19284294" fill="#F8F9FA" x="-0.596421471" y="197.785288" width="195.192843" height="19.0854871" rx="0.795228628"></rect>
-            <path d="M78.5,77.5 L93.5332964,77.5" id="Line-2" stroke="#DCE1E4" stroke-width="2" stroke-linecap="square"></path>
-            <path d="M97.125,77.5 L100.883324,77.5" id="Line-2-Copy" stroke="#DCE1E4" stroke-width="2" stroke-linecap="square"></path>
+            <rect id="Rectangle-7" stroke="#DCE1E4" stroke-width="1.19284294" fill="#F8F9FA" x="-0.596421471" y="135.785288" width="195.192843" height="19.0854871" rx="0.795228628"></rect>
+            <path d="M78.5,15.5 L93.5332964,15.5" id="Line-2" stroke="#DCE1E4" stroke-width="2" stroke-linecap="square"></path>
+            <path d="M97.125,15.5 L100.883324,15.5" id="Line-2-Copy" stroke="#DCE1E4" stroke-width="2" stroke-linecap="square"></path>
         </g>
     </g>
 </svg>
\ No newline at end of file
diff --git a/resources/frontend_client/app/img/collection-empty-state@2x.png b/resources/frontend_client/app/img/collection-empty-state@2x.png
index 0e98b3713def064c235fb9627b5a3cc0e9f825a7..3ef89ea1cf605a22da2f6d1c9f2200a0cfa0130c 100644
Binary files a/resources/frontend_client/app/img/collection-empty-state@2x.png and b/resources/frontend_client/app/img/collection-empty-state@2x.png differ
diff --git a/resources/frontend_client/app/img/empty.png b/resources/frontend_client/app/img/empty.png
new file mode 100644
index 0000000000000000000000000000000000000000..bf66147252b060b1b9263432539635a529ab5a9f
Binary files /dev/null and b/resources/frontend_client/app/img/empty.png differ
diff --git a/resources/frontend_client/app/img/empty@2x.png b/resources/frontend_client/app/img/empty@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..98d2a85018f771e5a9f8a6bec77fe1eb18e6fdc2
Binary files /dev/null and b/resources/frontend_client/app/img/empty@2x.png differ
diff --git a/resources/frontend_shared/color_selector.js b/resources/frontend_shared/color_selector.js
new file mode 100644
index 0000000000000000000000000000000000000000..b1e7f4bbfb748849803e257d66f60e4c7b900661
--- /dev/null
+++ b/resources/frontend_shared/color_selector.js
@@ -0,0 +1,9062 @@
+(function webpackUniversalModuleDefinition(root, factory) {
+	if(typeof exports === 'object' && typeof module === 'object')
+		module.exports = factory();
+	else if(typeof define === 'function' && define.amd)
+		define([], factory);
+	else if(typeof exports === 'object')
+		exports["shared"] = factory();
+	else
+		root["shared"] = factory();
+})(typeof self !== 'undefined' ? self : this, function() {
+return /******/ (function(modules) { // webpackBootstrap
+/******/ 	// The module cache
+/******/ 	var installedModules = {};
+/******/
+/******/ 	// The require function
+/******/ 	function __webpack_require__(moduleId) {
+/******/
+/******/ 		// Check if module is in cache
+/******/ 		if(installedModules[moduleId]) {
+/******/ 			return installedModules[moduleId].exports;
+/******/ 		}
+/******/ 		// Create a new module (and put it into the cache)
+/******/ 		var module = installedModules[moduleId] = {
+/******/ 			i: moduleId,
+/******/ 			l: false,
+/******/ 			exports: {}
+/******/ 		};
+/******/
+/******/ 		// Execute the module function
+/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ 		// Flag the module as loaded
+/******/ 		module.l = true;
+/******/
+/******/ 		// Return the exports of the module
+/******/ 		return module.exports;
+/******/ 	}
+/******/
+/******/
+/******/ 	// expose the modules object (__webpack_modules__)
+/******/ 	__webpack_require__.m = modules;
+/******/
+/******/ 	// expose the module cache
+/******/ 	__webpack_require__.c = installedModules;
+/******/
+/******/ 	// define getter function for harmony exports
+/******/ 	__webpack_require__.d = function(exports, name, getter) {
+/******/ 		if(!__webpack_require__.o(exports, name)) {
+/******/ 			Object.defineProperty(exports, name, {
+/******/ 				configurable: false,
+/******/ 				enumerable: true,
+/******/ 				get: getter
+/******/ 			});
+/******/ 		}
+/******/ 	};
+/******/
+/******/ 	// getDefaultExport function for compatibility with non-harmony modules
+/******/ 	__webpack_require__.n = function(module) {
+/******/ 		var getter = module && module.__esModule ?
+/******/ 			function getDefault() { return module['default']; } :
+/******/ 			function getModuleExports() { return module; };
+/******/ 		__webpack_require__.d(getter, 'a', getter);
+/******/ 		return getter;
+/******/ 	};
+/******/
+/******/ 	// Object.prototype.hasOwnProperty.call
+/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
+/******/
+/******/ 	// __webpack_public_path__
+/******/ 	__webpack_require__.p = "";
+/******/
+/******/ 	// Load entry module and return exports
+/******/ 	return __webpack_require__(__webpack_require__.s = 56);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (immutable) */ __webpack_exports__["a"] = newInterval;
+var t0 = new Date,
+    t1 = new Date;
+
+function newInterval(floori, offseti, count, field) {
+
+  function interval(date) {
+    return floori(date = new Date(+date)), date;
+  }
+
+  interval.floor = interval;
+
+  interval.ceil = function(date) {
+    return floori(date = new Date(date - 1)), offseti(date, 1), floori(date), date;
+  };
+
+  interval.round = function(date) {
+    var d0 = interval(date),
+        d1 = interval.ceil(date);
+    return date - d0 < d1 - date ? d0 : d1;
+  };
+
+  interval.offset = function(date, step) {
+    return offseti(date = new Date(+date), step == null ? 1 : Math.floor(step)), date;
+  };
+
+  interval.range = function(start, stop, step) {
+    var range = [], previous;
+    start = interval.ceil(start);
+    step = step == null ? 1 : Math.floor(step);
+    if (!(start < stop) || !(step > 0)) return range; // also handles Invalid Date
+    do range.push(previous = new Date(+start)), offseti(start, step), floori(start);
+    while (previous < start && start < stop);
+    return range;
+  };
+
+  interval.filter = function(test) {
+    return newInterval(function(date) {
+      if (date >= date) while (floori(date), !test(date)) date.setTime(date - 1);
+    }, function(date, step) {
+      if (date >= date) {
+        if (step < 0) while (++step <= 0) {
+          while (offseti(date, -1), !test(date)) {} // eslint-disable-line no-empty
+        } else while (--step >= 0) {
+          while (offseti(date, +1), !test(date)) {} // eslint-disable-line no-empty
+        }
+      }
+    });
+  };
+
+  if (count) {
+    interval.count = function(start, end) {
+      t0.setTime(+start), t1.setTime(+end);
+      floori(t0), floori(t1);
+      return Math.floor(count(t0, t1));
+    };
+
+    interval.every = function(step) {
+      step = Math.floor(step);
+      return !isFinite(step) || !(step > 0) ? null
+          : !(step > 1) ? interval
+          : interval.filter(field
+              ? function(d) { return field(d) % step === 0; }
+              : function(d) { return interval.count(0, d) % step === 0; });
+    };
+  }
+
+  return interval;
+}
+
+
+/***/ }),
+/* 1 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__src_bisect__ = __webpack_require__(23);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return __WEBPACK_IMPORTED_MODULE_0__src_bisect__["a"]; });
+/* unused harmony reexport bisectRight */
+/* unused harmony reexport bisectLeft */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__src_ascending__ = __webpack_require__(4);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return __WEBPACK_IMPORTED_MODULE_1__src_ascending__["a"]; });
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__src_bisector__ = __webpack_require__(24);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "c", function() { return __WEBPACK_IMPORTED_MODULE_2__src_bisector__["a"]; });
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__src_cross__ = __webpack_require__(63);
+/* unused harmony reexport cross */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__src_descending__ = __webpack_require__(64);
+/* unused harmony reexport descending */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__src_deviation__ = __webpack_require__(26);
+/* unused harmony reexport deviation */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_6__src_extent__ = __webpack_require__(28);
+/* unused harmony reexport extent */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_7__src_histogram__ = __webpack_require__(65);
+/* unused harmony reexport histogram */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_8__src_threshold_freedmanDiaconis__ = __webpack_require__(68);
+/* unused harmony reexport thresholdFreedmanDiaconis */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_9__src_threshold_scott__ = __webpack_require__(69);
+/* unused harmony reexport thresholdScott */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_10__src_threshold_sturges__ = __webpack_require__(32);
+/* unused harmony reexport thresholdSturges */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_11__src_max__ = __webpack_require__(70);
+/* unused harmony reexport max */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_12__src_mean__ = __webpack_require__(71);
+/* unused harmony reexport mean */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_13__src_median__ = __webpack_require__(72);
+/* unused harmony reexport median */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_14__src_merge__ = __webpack_require__(73);
+/* unused harmony reexport merge */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_15__src_min__ = __webpack_require__(33);
+/* unused harmony reexport min */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_16__src_pairs__ = __webpack_require__(25);
+/* unused harmony reexport pairs */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_17__src_permute__ = __webpack_require__(74);
+/* unused harmony reexport permute */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_18__src_quantile__ = __webpack_require__(12);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "d", function() { return __WEBPACK_IMPORTED_MODULE_18__src_quantile__["a"]; });
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_19__src_range__ = __webpack_require__(30);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "e", function() { return __WEBPACK_IMPORTED_MODULE_19__src_range__["a"]; });
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_20__src_scan__ = __webpack_require__(75);
+/* unused harmony reexport scan */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_21__src_shuffle__ = __webpack_require__(76);
+/* unused harmony reexport shuffle */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_22__src_sum__ = __webpack_require__(77);
+/* unused harmony reexport sum */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_23__src_ticks__ = __webpack_require__(31);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "h", function() { return __WEBPACK_IMPORTED_MODULE_23__src_ticks__["a"]; });
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "f", function() { return __WEBPACK_IMPORTED_MODULE_23__src_ticks__["b"]; });
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "g", function() { return __WEBPACK_IMPORTED_MODULE_23__src_ticks__["c"]; });
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_24__src_transpose__ = __webpack_require__(34);
+/* unused harmony reexport transpose */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_25__src_variance__ = __webpack_require__(27);
+/* unused harmony reexport variance */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_26__src_zip__ = __webpack_require__(78);
+/* unused harmony reexport zip */
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/***/ }),
+/* 2 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "d", function() { return durationSecond; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "c", function() { return durationMinute; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return durationHour; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return durationDay; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "e", function() { return durationWeek; });
+var durationSecond = 1e3;
+var durationMinute = 6e4;
+var durationHour = 36e5;
+var durationDay = 864e5;
+var durationWeek = 6048e5;
+
+
+/***/ }),
+/* 3 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return map; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return slice; });
+var array = Array.prototype;
+
+var map = array.map;
+var slice = array.slice;
+
+
+/***/ }),
+/* 4 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony default export */ __webpack_exports__["a"] = (function(a, b) {
+  return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
+});
+
+
+/***/ }),
+/* 5 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (immutable) */ __webpack_exports__["b"] = linearish;
+/* harmony export (immutable) */ __webpack_exports__["a"] = linear;
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_d3_array__ = __webpack_require__(1);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_d3_interpolate__ = __webpack_require__(14);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__continuous__ = __webpack_require__(10);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__tickFormat__ = __webpack_require__(99);
+
+
+
+
+
+function linearish(scale) {
+  var domain = scale.domain;
+
+  scale.ticks = function(count) {
+    var d = domain();
+    return Object(__WEBPACK_IMPORTED_MODULE_0_d3_array__["h" /* ticks */])(d[0], d[d.length - 1], count == null ? 10 : count);
+  };
+
+  scale.tickFormat = function(count, specifier) {
+    return Object(__WEBPACK_IMPORTED_MODULE_3__tickFormat__["a" /* default */])(domain(), count, specifier);
+  };
+
+  scale.nice = function(count) {
+    if (count == null) count = 10;
+
+    var d = domain(),
+        i0 = 0,
+        i1 = d.length - 1,
+        start = d[i0],
+        stop = d[i1],
+        step;
+
+    if (stop < start) {
+      step = start, start = stop, stop = step;
+      step = i0, i0 = i1, i1 = step;
+    }
+
+    step = Object(__WEBPACK_IMPORTED_MODULE_0_d3_array__["f" /* tickIncrement */])(start, stop, count);
+
+    if (step > 0) {
+      start = Math.floor(start / step) * step;
+      stop = Math.ceil(stop / step) * step;
+      step = Object(__WEBPACK_IMPORTED_MODULE_0_d3_array__["f" /* tickIncrement */])(start, stop, count);
+    } else if (step < 0) {
+      start = Math.ceil(start * step) / step;
+      stop = Math.floor(stop * step) / step;
+      step = Object(__WEBPACK_IMPORTED_MODULE_0_d3_array__["f" /* tickIncrement */])(start, stop, count);
+    }
+
+    if (step > 0) {
+      d[i0] = Math.floor(start / step) * step;
+      d[i1] = Math.ceil(stop / step) * step;
+      domain(d);
+    } else if (step < 0) {
+      d[i0] = Math.ceil(start * step) / step;
+      d[i1] = Math.floor(stop * step) / step;
+      domain(d);
+    }
+
+    return scale;
+  };
+
+  return scale;
+}
+
+function linear() {
+  var scale = Object(__WEBPACK_IMPORTED_MODULE_2__continuous__["b" /* default */])(__WEBPACK_IMPORTED_MODULE_2__continuous__["c" /* deinterpolateLinear */], __WEBPACK_IMPORTED_MODULE_1_d3_interpolate__["b" /* interpolateNumber */]);
+
+  scale.copy = function() {
+    return Object(__WEBPACK_IMPORTED_MODULE_2__continuous__["a" /* copy */])(scale, linear());
+  };
+
+  return linearish(scale);
+}
+
+
+/***/ }),
+/* 6 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__src_color__ = __webpack_require__(16);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return __WEBPACK_IMPORTED_MODULE_0__src_color__["e"]; });
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "f", function() { return __WEBPACK_IMPORTED_MODULE_0__src_color__["g"]; });
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "d", function() { return __WEBPACK_IMPORTED_MODULE_0__src_color__["f"]; });
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__src_lab__ = __webpack_require__(86);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "e", function() { return __WEBPACK_IMPORTED_MODULE_1__src_lab__["a"]; });
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "c", function() { return __WEBPACK_IMPORTED_MODULE_1__src_lab__["b"]; });
+/* unused harmony reexport lch */
+/* unused harmony reexport gray */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__src_cubehelix__ = __webpack_require__(87);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return __WEBPACK_IMPORTED_MODULE_2__src_cubehelix__["a"]; });
+
+
+
+
+
+/***/ }),
+/* 7 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony default export */ __webpack_exports__["a"] = (function(x) {
+  return x === null ? NaN : +x;
+});
+
+
+/***/ }),
+/* 8 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (immutable) */ __webpack_exports__["c"] = hue;
+/* harmony export (immutable) */ __webpack_exports__["b"] = gamma;
+/* harmony export (immutable) */ __webpack_exports__["a"] = nogamma;
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__constant__ = __webpack_require__(39);
+
+
+function linear(a, d) {
+  return function(t) {
+    return a + t * d;
+  };
+}
+
+function exponential(a, b, y) {
+  return a = Math.pow(a, y), b = Math.pow(b, y) - a, y = 1 / y, function(t) {
+    return Math.pow(a + t * b, y);
+  };
+}
+
+function hue(a, b) {
+  var d = b - a;
+  return d ? linear(a, d > 180 || d < -180 ? d - 360 * Math.round(d / 360) : d) : Object(__WEBPACK_IMPORTED_MODULE_0__constant__["a" /* default */])(isNaN(a) ? b : a);
+}
+
+function gamma(y) {
+  return (y = +y) === 1 ? nogamma : function(a, b) {
+    return b - a ? exponential(a, b, y) : Object(__WEBPACK_IMPORTED_MODULE_0__constant__["a" /* default */])(isNaN(a) ? b : a);
+  };
+}
+
+function nogamma(a, b) {
+  var d = b - a;
+  return d ? linear(a, d) : Object(__WEBPACK_IMPORTED_MODULE_0__constant__["a" /* default */])(isNaN(a) ? b : a);
+}
+
+
+/***/ }),
+/* 9 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony default export */ __webpack_exports__["a"] = (function(a, b) {
+  return a = +a, b -= a, function(t) {
+    return a + b * t;
+  };
+});
+
+
+/***/ }),
+/* 10 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (immutable) */ __webpack_exports__["c"] = deinterpolateLinear;
+/* harmony export (immutable) */ __webpack_exports__["a"] = copy;
+/* harmony export (immutable) */ __webpack_exports__["b"] = continuous;
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_d3_array__ = __webpack_require__(1);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_d3_interpolate__ = __webpack_require__(14);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__array__ = __webpack_require__(3);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__constant__ = __webpack_require__(19);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__number__ = __webpack_require__(44);
+
+
+
+
+
+
+var unit = [0, 1];
+
+function deinterpolateLinear(a, b) {
+  return (b -= (a = +a))
+      ? function(x) { return (x - a) / b; }
+      : Object(__WEBPACK_IMPORTED_MODULE_3__constant__["a" /* default */])(b);
+}
+
+function deinterpolateClamp(deinterpolate) {
+  return function(a, b) {
+    var d = deinterpolate(a = +a, b = +b);
+    return function(x) { return x <= a ? 0 : x >= b ? 1 : d(x); };
+  };
+}
+
+function reinterpolateClamp(reinterpolate) {
+  return function(a, b) {
+    var r = reinterpolate(a = +a, b = +b);
+    return function(t) { return t <= 0 ? a : t >= 1 ? b : r(t); };
+  };
+}
+
+function bimap(domain, range, deinterpolate, reinterpolate) {
+  var d0 = domain[0], d1 = domain[1], r0 = range[0], r1 = range[1];
+  if (d1 < d0) d0 = deinterpolate(d1, d0), r0 = reinterpolate(r1, r0);
+  else d0 = deinterpolate(d0, d1), r0 = reinterpolate(r0, r1);
+  return function(x) { return r0(d0(x)); };
+}
+
+function polymap(domain, range, deinterpolate, reinterpolate) {
+  var j = Math.min(domain.length, range.length) - 1,
+      d = new Array(j),
+      r = new Array(j),
+      i = -1;
+
+  // Reverse descending domains.
+  if (domain[j] < domain[0]) {
+    domain = domain.slice().reverse();
+    range = range.slice().reverse();
+  }
+
+  while (++i < j) {
+    d[i] = deinterpolate(domain[i], domain[i + 1]);
+    r[i] = reinterpolate(range[i], range[i + 1]);
+  }
+
+  return function(x) {
+    var i = Object(__WEBPACK_IMPORTED_MODULE_0_d3_array__["b" /* bisect */])(domain, x, 1, j) - 1;
+    return r[i](d[i](x));
+  };
+}
+
+function copy(source, target) {
+  return target
+      .domain(source.domain())
+      .range(source.range())
+      .interpolate(source.interpolate())
+      .clamp(source.clamp());
+}
+
+// deinterpolate(a, b)(x) takes a domain value x in [a,b] and returns the corresponding parameter t in [0,1].
+// reinterpolate(a, b)(t) takes a parameter t in [0,1] and returns the corresponding domain value x in [a,b].
+function continuous(deinterpolate, reinterpolate) {
+  var domain = unit,
+      range = unit,
+      interpolate = __WEBPACK_IMPORTED_MODULE_1_d3_interpolate__["a" /* interpolate */],
+      clamp = false,
+      piecewise,
+      output,
+      input;
+
+  function rescale() {
+    piecewise = Math.min(domain.length, range.length) > 2 ? polymap : bimap;
+    output = input = null;
+    return scale;
+  }
+
+  function scale(x) {
+    return (output || (output = piecewise(domain, range, clamp ? deinterpolateClamp(deinterpolate) : deinterpolate, interpolate)))(+x);
+  }
+
+  scale.invert = function(y) {
+    return (input || (input = piecewise(range, domain, deinterpolateLinear, clamp ? reinterpolateClamp(reinterpolate) : reinterpolate)))(+y);
+  };
+
+  scale.domain = function(_) {
+    return arguments.length ? (domain = __WEBPACK_IMPORTED_MODULE_2__array__["a" /* map */].call(_, __WEBPACK_IMPORTED_MODULE_4__number__["a" /* default */]), rescale()) : domain.slice();
+  };
+
+  scale.range = function(_) {
+    return arguments.length ? (range = __WEBPACK_IMPORTED_MODULE_2__array__["b" /* slice */].call(_), rescale()) : range.slice();
+  };
+
+  scale.rangeRound = function(_) {
+    return range = __WEBPACK_IMPORTED_MODULE_2__array__["b" /* slice */].call(_), interpolate = __WEBPACK_IMPORTED_MODULE_1_d3_interpolate__["c" /* interpolateRound */], rescale();
+  };
+
+  scale.clamp = function(_) {
+    return arguments.length ? (clamp = !!_, rescale()) : clamp;
+  };
+
+  scale.interpolate = function(_) {
+    return arguments.length ? (interpolate = _, rescale()) : interpolate;
+  };
+
+  return rescale();
+}
+
+
+/***/ }),
+/* 11 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__formatDecimal__ = __webpack_require__(20);
+
+
+/* harmony default export */ __webpack_exports__["a"] = (function(x) {
+  return x = Object(__WEBPACK_IMPORTED_MODULE_0__formatDecimal__["a" /* default */])(Math.abs(x)), x ? x[1] : NaN;
+});
+
+
+/***/ }),
+/* 12 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__number__ = __webpack_require__(7);
+
+
+/* harmony default export */ __webpack_exports__["a"] = (function(values, p, valueof) {
+  if (valueof == null) valueof = __WEBPACK_IMPORTED_MODULE_0__number__["a" /* default */];
+  if (!(n = values.length)) return;
+  if ((p = +p) <= 0 || n < 2) return +valueof(values[0], 0, values);
+  if (p >= 1) return +valueof(values[n - 1], n - 1, values);
+  var n,
+      i = (n - 1) * p,
+      i0 = Math.floor(i),
+      value0 = +valueof(values[i0], i0, values),
+      value1 = +valueof(values[i0 + 1], i0 + 1, values);
+  return value0 + (value1 - value0) * (i - i0);
+});
+
+
+/***/ }),
+/* 13 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return prefix; });
+var prefix = "$";
+
+function Map() {}
+
+Map.prototype = map.prototype = {
+  constructor: Map,
+  has: function(key) {
+    return (prefix + key) in this;
+  },
+  get: function(key) {
+    return this[prefix + key];
+  },
+  set: function(key, value) {
+    this[prefix + key] = value;
+    return this;
+  },
+  remove: function(key) {
+    var property = prefix + key;
+    return property in this && delete this[property];
+  },
+  clear: function() {
+    for (var property in this) if (property[0] === prefix) delete this[property];
+  },
+  keys: function() {
+    var keys = [];
+    for (var property in this) if (property[0] === prefix) keys.push(property.slice(1));
+    return keys;
+  },
+  values: function() {
+    var values = [];
+    for (var property in this) if (property[0] === prefix) values.push(this[property]);
+    return values;
+  },
+  entries: function() {
+    var entries = [];
+    for (var property in this) if (property[0] === prefix) entries.push({key: property.slice(1), value: this[property]});
+    return entries;
+  },
+  size: function() {
+    var size = 0;
+    for (var property in this) if (property[0] === prefix) ++size;
+    return size;
+  },
+  empty: function() {
+    for (var property in this) if (property[0] === prefix) return false;
+    return true;
+  },
+  each: function(f) {
+    for (var property in this) if (property[0] === prefix) f(this[property], property.slice(1), this);
+  }
+};
+
+function map(object, f) {
+  var map = new Map;
+
+  // Copy constructor.
+  if (object instanceof Map) object.each(function(value, key) { map.set(key, value); });
+
+  // Index array by numeric index or specified key function.
+  else if (Array.isArray(object)) {
+    var i = -1,
+        n = object.length,
+        o;
+
+    if (f == null) while (++i < n) map.set(i, object[i]);
+    else while (++i < n) map.set(f(o = object[i], i, object), o);
+  }
+
+  // Convert object to map.
+  else if (object) for (var key in object) map.set(key, object[key]);
+
+  return map;
+}
+
+/* harmony default export */ __webpack_exports__["a"] = (map);
+
+
+/***/ }),
+/* 14 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__src_value__ = __webpack_require__(15);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return __WEBPACK_IMPORTED_MODULE_0__src_value__["a"]; });
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__src_array__ = __webpack_require__(40);
+/* unused harmony reexport interpolateArray */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__src_basis__ = __webpack_require__(18);
+/* unused harmony reexport interpolateBasis */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__src_basisClosed__ = __webpack_require__(38);
+/* unused harmony reexport interpolateBasisClosed */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__src_date__ = __webpack_require__(41);
+/* unused harmony reexport interpolateDate */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__src_number__ = __webpack_require__(9);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return __WEBPACK_IMPORTED_MODULE_5__src_number__["a"]; });
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_6__src_object__ = __webpack_require__(42);
+/* unused harmony reexport interpolateObject */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_7__src_round__ = __webpack_require__(88);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "c", function() { return __WEBPACK_IMPORTED_MODULE_7__src_round__["a"]; });
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_8__src_string__ = __webpack_require__(43);
+/* unused harmony reexport interpolateString */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_9__src_transform_index__ = __webpack_require__(89);
+/* unused harmony reexport interpolateTransformCss */
+/* unused harmony reexport interpolateTransformSvg */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_10__src_zoom__ = __webpack_require__(92);
+/* unused harmony reexport interpolateZoom */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_11__src_rgb__ = __webpack_require__(37);
+/* unused harmony reexport interpolateRgb */
+/* unused harmony reexport interpolateRgbBasis */
+/* unused harmony reexport interpolateRgbBasisClosed */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_12__src_hsl__ = __webpack_require__(93);
+/* unused harmony reexport interpolateHsl */
+/* unused harmony reexport interpolateHslLong */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_13__src_lab__ = __webpack_require__(94);
+/* unused harmony reexport interpolateLab */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_14__src_hcl__ = __webpack_require__(95);
+/* unused harmony reexport interpolateHcl */
+/* unused harmony reexport interpolateHclLong */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_15__src_cubehelix__ = __webpack_require__(96);
+/* unused harmony reexport interpolateCubehelix */
+/* unused harmony reexport interpolateCubehelixLong */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_16__src_piecewise__ = __webpack_require__(97);
+/* unused harmony reexport piecewise */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_17__src_quantize__ = __webpack_require__(98);
+/* unused harmony reexport quantize */
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/***/ }),
+/* 15 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_d3_color__ = __webpack_require__(6);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__rgb__ = __webpack_require__(37);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__array__ = __webpack_require__(40);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__date__ = __webpack_require__(41);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__number__ = __webpack_require__(9);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__object__ = __webpack_require__(42);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_6__string__ = __webpack_require__(43);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_7__constant__ = __webpack_require__(39);
+
+
+
+
+
+
+
+
+
+/* harmony default export */ __webpack_exports__["a"] = (function(a, b) {
+  var t = typeof b, c;
+  return b == null || t === "boolean" ? Object(__WEBPACK_IMPORTED_MODULE_7__constant__["a" /* default */])(b)
+      : (t === "number" ? __WEBPACK_IMPORTED_MODULE_4__number__["a" /* default */]
+      : t === "string" ? ((c = Object(__WEBPACK_IMPORTED_MODULE_0_d3_color__["a" /* color */])(b)) ? (b = c, __WEBPACK_IMPORTED_MODULE_1__rgb__["a" /* default */]) : __WEBPACK_IMPORTED_MODULE_6__string__["a" /* default */])
+      : b instanceof __WEBPACK_IMPORTED_MODULE_0_d3_color__["a" /* color */] ? __WEBPACK_IMPORTED_MODULE_1__rgb__["a" /* default */]
+      : b instanceof Date ? __WEBPACK_IMPORTED_MODULE_3__date__["a" /* default */]
+      : Array.isArray(b) ? __WEBPACK_IMPORTED_MODULE_2__array__["a" /* default */]
+      : typeof b.valueOf !== "function" && typeof b.toString !== "function" || isNaN(b) ? __WEBPACK_IMPORTED_MODULE_5__object__["a" /* default */]
+      : __WEBPACK_IMPORTED_MODULE_4__number__["a" /* default */])(a, b);
+});
+
+
+/***/ }),
+/* 16 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (immutable) */ __webpack_exports__["a"] = Color;
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "d", function() { return darker; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "c", function() { return brighter; });
+/* harmony export (immutable) */ __webpack_exports__["e"] = color;
+/* harmony export (immutable) */ __webpack_exports__["h"] = rgbConvert;
+/* harmony export (immutable) */ __webpack_exports__["g"] = rgb;
+/* harmony export (immutable) */ __webpack_exports__["b"] = Rgb;
+/* unused harmony export hslConvert */
+/* harmony export (immutable) */ __webpack_exports__["f"] = hsl;
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__define__ = __webpack_require__(17);
+
+
+function Color() {}
+
+var darker = 0.7;
+var brighter = 1 / darker;
+
+var reI = "\\s*([+-]?\\d+)\\s*",
+    reN = "\\s*([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)\\s*",
+    reP = "\\s*([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)%\\s*",
+    reHex3 = /^#([0-9a-f]{3})$/,
+    reHex6 = /^#([0-9a-f]{6})$/,
+    reRgbInteger = new RegExp("^rgb\\(" + [reI, reI, reI] + "\\)$"),
+    reRgbPercent = new RegExp("^rgb\\(" + [reP, reP, reP] + "\\)$"),
+    reRgbaInteger = new RegExp("^rgba\\(" + [reI, reI, reI, reN] + "\\)$"),
+    reRgbaPercent = new RegExp("^rgba\\(" + [reP, reP, reP, reN] + "\\)$"),
+    reHslPercent = new RegExp("^hsl\\(" + [reN, reP, reP] + "\\)$"),
+    reHslaPercent = new RegExp("^hsla\\(" + [reN, reP, reP, reN] + "\\)$");
+
+var named = {
+  aliceblue: 0xf0f8ff,
+  antiquewhite: 0xfaebd7,
+  aqua: 0x00ffff,
+  aquamarine: 0x7fffd4,
+  azure: 0xf0ffff,
+  beige: 0xf5f5dc,
+  bisque: 0xffe4c4,
+  black: 0x000000,
+  blanchedalmond: 0xffebcd,
+  blue: 0x0000ff,
+  blueviolet: 0x8a2be2,
+  brown: 0xa52a2a,
+  burlywood: 0xdeb887,
+  cadetblue: 0x5f9ea0,
+  chartreuse: 0x7fff00,
+  chocolate: 0xd2691e,
+  coral: 0xff7f50,
+  cornflowerblue: 0x6495ed,
+  cornsilk: 0xfff8dc,
+  crimson: 0xdc143c,
+  cyan: 0x00ffff,
+  darkblue: 0x00008b,
+  darkcyan: 0x008b8b,
+  darkgoldenrod: 0xb8860b,
+  darkgray: 0xa9a9a9,
+  darkgreen: 0x006400,
+  darkgrey: 0xa9a9a9,
+  darkkhaki: 0xbdb76b,
+  darkmagenta: 0x8b008b,
+  darkolivegreen: 0x556b2f,
+  darkorange: 0xff8c00,
+  darkorchid: 0x9932cc,
+  darkred: 0x8b0000,
+  darksalmon: 0xe9967a,
+  darkseagreen: 0x8fbc8f,
+  darkslateblue: 0x483d8b,
+  darkslategray: 0x2f4f4f,
+  darkslategrey: 0x2f4f4f,
+  darkturquoise: 0x00ced1,
+  darkviolet: 0x9400d3,
+  deeppink: 0xff1493,
+  deepskyblue: 0x00bfff,
+  dimgray: 0x696969,
+  dimgrey: 0x696969,
+  dodgerblue: 0x1e90ff,
+  firebrick: 0xb22222,
+  floralwhite: 0xfffaf0,
+  forestgreen: 0x228b22,
+  fuchsia: 0xff00ff,
+  gainsboro: 0xdcdcdc,
+  ghostwhite: 0xf8f8ff,
+  gold: 0xffd700,
+  goldenrod: 0xdaa520,
+  gray: 0x808080,
+  green: 0x008000,
+  greenyellow: 0xadff2f,
+  grey: 0x808080,
+  honeydew: 0xf0fff0,
+  hotpink: 0xff69b4,
+  indianred: 0xcd5c5c,
+  indigo: 0x4b0082,
+  ivory: 0xfffff0,
+  khaki: 0xf0e68c,
+  lavender: 0xe6e6fa,
+  lavenderblush: 0xfff0f5,
+  lawngreen: 0x7cfc00,
+  lemonchiffon: 0xfffacd,
+  lightblue: 0xadd8e6,
+  lightcoral: 0xf08080,
+  lightcyan: 0xe0ffff,
+  lightgoldenrodyellow: 0xfafad2,
+  lightgray: 0xd3d3d3,
+  lightgreen: 0x90ee90,
+  lightgrey: 0xd3d3d3,
+  lightpink: 0xffb6c1,
+  lightsalmon: 0xffa07a,
+  lightseagreen: 0x20b2aa,
+  lightskyblue: 0x87cefa,
+  lightslategray: 0x778899,
+  lightslategrey: 0x778899,
+  lightsteelblue: 0xb0c4de,
+  lightyellow: 0xffffe0,
+  lime: 0x00ff00,
+  limegreen: 0x32cd32,
+  linen: 0xfaf0e6,
+  magenta: 0xff00ff,
+  maroon: 0x800000,
+  mediumaquamarine: 0x66cdaa,
+  mediumblue: 0x0000cd,
+  mediumorchid: 0xba55d3,
+  mediumpurple: 0x9370db,
+  mediumseagreen: 0x3cb371,
+  mediumslateblue: 0x7b68ee,
+  mediumspringgreen: 0x00fa9a,
+  mediumturquoise: 0x48d1cc,
+  mediumvioletred: 0xc71585,
+  midnightblue: 0x191970,
+  mintcream: 0xf5fffa,
+  mistyrose: 0xffe4e1,
+  moccasin: 0xffe4b5,
+  navajowhite: 0xffdead,
+  navy: 0x000080,
+  oldlace: 0xfdf5e6,
+  olive: 0x808000,
+  olivedrab: 0x6b8e23,
+  orange: 0xffa500,
+  orangered: 0xff4500,
+  orchid: 0xda70d6,
+  palegoldenrod: 0xeee8aa,
+  palegreen: 0x98fb98,
+  paleturquoise: 0xafeeee,
+  palevioletred: 0xdb7093,
+  papayawhip: 0xffefd5,
+  peachpuff: 0xffdab9,
+  peru: 0xcd853f,
+  pink: 0xffc0cb,
+  plum: 0xdda0dd,
+  powderblue: 0xb0e0e6,
+  purple: 0x800080,
+  rebeccapurple: 0x663399,
+  red: 0xff0000,
+  rosybrown: 0xbc8f8f,
+  royalblue: 0x4169e1,
+  saddlebrown: 0x8b4513,
+  salmon: 0xfa8072,
+  sandybrown: 0xf4a460,
+  seagreen: 0x2e8b57,
+  seashell: 0xfff5ee,
+  sienna: 0xa0522d,
+  silver: 0xc0c0c0,
+  skyblue: 0x87ceeb,
+  slateblue: 0x6a5acd,
+  slategray: 0x708090,
+  slategrey: 0x708090,
+  snow: 0xfffafa,
+  springgreen: 0x00ff7f,
+  steelblue: 0x4682b4,
+  tan: 0xd2b48c,
+  teal: 0x008080,
+  thistle: 0xd8bfd8,
+  tomato: 0xff6347,
+  turquoise: 0x40e0d0,
+  violet: 0xee82ee,
+  wheat: 0xf5deb3,
+  white: 0xffffff,
+  whitesmoke: 0xf5f5f5,
+  yellow: 0xffff00,
+  yellowgreen: 0x9acd32
+};
+
+Object(__WEBPACK_IMPORTED_MODULE_0__define__["a" /* default */])(Color, color, {
+  displayable: function() {
+    return this.rgb().displayable();
+  },
+  hex: function() {
+    return this.rgb().hex();
+  },
+  toString: function() {
+    return this.rgb() + "";
+  }
+});
+
+function color(format) {
+  var m;
+  format = (format + "").trim().toLowerCase();
+  return (m = reHex3.exec(format)) ? (m = parseInt(m[1], 16), new Rgb((m >> 8 & 0xf) | (m >> 4 & 0x0f0), (m >> 4 & 0xf) | (m & 0xf0), ((m & 0xf) << 4) | (m & 0xf), 1)) // #f00
+      : (m = reHex6.exec(format)) ? rgbn(parseInt(m[1], 16)) // #ff0000
+      : (m = reRgbInteger.exec(format)) ? new Rgb(m[1], m[2], m[3], 1) // rgb(255, 0, 0)
+      : (m = reRgbPercent.exec(format)) ? new Rgb(m[1] * 255 / 100, m[2] * 255 / 100, m[3] * 255 / 100, 1) // rgb(100%, 0%, 0%)
+      : (m = reRgbaInteger.exec(format)) ? rgba(m[1], m[2], m[3], m[4]) // rgba(255, 0, 0, 1)
+      : (m = reRgbaPercent.exec(format)) ? rgba(m[1] * 255 / 100, m[2] * 255 / 100, m[3] * 255 / 100, m[4]) // rgb(100%, 0%, 0%, 1)
+      : (m = reHslPercent.exec(format)) ? hsla(m[1], m[2] / 100, m[3] / 100, 1) // hsl(120, 50%, 50%)
+      : (m = reHslaPercent.exec(format)) ? hsla(m[1], m[2] / 100, m[3] / 100, m[4]) // hsla(120, 50%, 50%, 1)
+      : named.hasOwnProperty(format) ? rgbn(named[format])
+      : format === "transparent" ? new Rgb(NaN, NaN, NaN, 0)
+      : null;
+}
+
+function rgbn(n) {
+  return new Rgb(n >> 16 & 0xff, n >> 8 & 0xff, n & 0xff, 1);
+}
+
+function rgba(r, g, b, a) {
+  if (a <= 0) r = g = b = NaN;
+  return new Rgb(r, g, b, a);
+}
+
+function rgbConvert(o) {
+  if (!(o instanceof Color)) o = color(o);
+  if (!o) return new Rgb;
+  o = o.rgb();
+  return new Rgb(o.r, o.g, o.b, o.opacity);
+}
+
+function rgb(r, g, b, opacity) {
+  return arguments.length === 1 ? rgbConvert(r) : new Rgb(r, g, b, opacity == null ? 1 : opacity);
+}
+
+function Rgb(r, g, b, opacity) {
+  this.r = +r;
+  this.g = +g;
+  this.b = +b;
+  this.opacity = +opacity;
+}
+
+Object(__WEBPACK_IMPORTED_MODULE_0__define__["a" /* default */])(Rgb, rgb, Object(__WEBPACK_IMPORTED_MODULE_0__define__["b" /* extend */])(Color, {
+  brighter: function(k) {
+    k = k == null ? brighter : Math.pow(brighter, k);
+    return new Rgb(this.r * k, this.g * k, this.b * k, this.opacity);
+  },
+  darker: function(k) {
+    k = k == null ? darker : Math.pow(darker, k);
+    return new Rgb(this.r * k, this.g * k, this.b * k, this.opacity);
+  },
+  rgb: function() {
+    return this;
+  },
+  displayable: function() {
+    return (0 <= this.r && this.r <= 255)
+        && (0 <= this.g && this.g <= 255)
+        && (0 <= this.b && this.b <= 255)
+        && (0 <= this.opacity && this.opacity <= 1);
+  },
+  hex: function() {
+    return "#" + hex(this.r) + hex(this.g) + hex(this.b);
+  },
+  toString: function() {
+    var a = this.opacity; a = isNaN(a) ? 1 : Math.max(0, Math.min(1, a));
+    return (a === 1 ? "rgb(" : "rgba(")
+        + Math.max(0, Math.min(255, Math.round(this.r) || 0)) + ", "
+        + Math.max(0, Math.min(255, Math.round(this.g) || 0)) + ", "
+        + Math.max(0, Math.min(255, Math.round(this.b) || 0))
+        + (a === 1 ? ")" : ", " + a + ")");
+  }
+}));
+
+function hex(value) {
+  value = Math.max(0, Math.min(255, Math.round(value) || 0));
+  return (value < 16 ? "0" : "") + value.toString(16);
+}
+
+function hsla(h, s, l, a) {
+  if (a <= 0) h = s = l = NaN;
+  else if (l <= 0 || l >= 1) h = s = NaN;
+  else if (s <= 0) h = NaN;
+  return new Hsl(h, s, l, a);
+}
+
+function hslConvert(o) {
+  if (o instanceof Hsl) return new Hsl(o.h, o.s, o.l, o.opacity);
+  if (!(o instanceof Color)) o = color(o);
+  if (!o) return new Hsl;
+  if (o instanceof Hsl) return o;
+  o = o.rgb();
+  var r = o.r / 255,
+      g = o.g / 255,
+      b = o.b / 255,
+      min = Math.min(r, g, b),
+      max = Math.max(r, g, b),
+      h = NaN,
+      s = max - min,
+      l = (max + min) / 2;
+  if (s) {
+    if (r === max) h = (g - b) / s + (g < b) * 6;
+    else if (g === max) h = (b - r) / s + 2;
+    else h = (r - g) / s + 4;
+    s /= l < 0.5 ? max + min : 2 - max - min;
+    h *= 60;
+  } else {
+    s = l > 0 && l < 1 ? 0 : h;
+  }
+  return new Hsl(h, s, l, o.opacity);
+}
+
+function hsl(h, s, l, opacity) {
+  return arguments.length === 1 ? hslConvert(h) : new Hsl(h, s, l, opacity == null ? 1 : opacity);
+}
+
+function Hsl(h, s, l, opacity) {
+  this.h = +h;
+  this.s = +s;
+  this.l = +l;
+  this.opacity = +opacity;
+}
+
+Object(__WEBPACK_IMPORTED_MODULE_0__define__["a" /* default */])(Hsl, hsl, Object(__WEBPACK_IMPORTED_MODULE_0__define__["b" /* extend */])(Color, {
+  brighter: function(k) {
+    k = k == null ? brighter : Math.pow(brighter, k);
+    return new Hsl(this.h, this.s, this.l * k, this.opacity);
+  },
+  darker: function(k) {
+    k = k == null ? darker : Math.pow(darker, k);
+    return new Hsl(this.h, this.s, this.l * k, this.opacity);
+  },
+  rgb: function() {
+    var h = this.h % 360 + (this.h < 0) * 360,
+        s = isNaN(h) || isNaN(this.s) ? 0 : this.s,
+        l = this.l,
+        m2 = l + (l < 0.5 ? l : 1 - l) * s,
+        m1 = 2 * l - m2;
+    return new Rgb(
+      hsl2rgb(h >= 240 ? h - 240 : h + 120, m1, m2),
+      hsl2rgb(h, m1, m2),
+      hsl2rgb(h < 120 ? h + 240 : h - 120, m1, m2),
+      this.opacity
+    );
+  },
+  displayable: function() {
+    return (0 <= this.s && this.s <= 1 || isNaN(this.s))
+        && (0 <= this.l && this.l <= 1)
+        && (0 <= this.opacity && this.opacity <= 1);
+  }
+}));
+
+/* From FvD 13.37, CSS Color Module Level 3 */
+function hsl2rgb(h, m1, m2) {
+  return (h < 60 ? m1 + (m2 - m1) * h / 60
+      : h < 180 ? m2
+      : h < 240 ? m1 + (m2 - m1) * (240 - h) / 60
+      : m1) * 255;
+}
+
+
+/***/ }),
+/* 17 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (immutable) */ __webpack_exports__["b"] = extend;
+/* harmony default export */ __webpack_exports__["a"] = (function(constructor, factory, prototype) {
+  constructor.prototype = factory.prototype = prototype;
+  prototype.constructor = constructor;
+});
+
+function extend(parent, definition) {
+  var prototype = Object.create(parent.prototype);
+  for (var key in definition) prototype[key] = definition[key];
+  return prototype;
+}
+
+
+/***/ }),
+/* 18 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (immutable) */ __webpack_exports__["a"] = basis;
+function basis(t1, v0, v1, v2, v3) {
+  var t2 = t1 * t1, t3 = t2 * t1;
+  return ((1 - 3 * t1 + 3 * t2 - t3) * v0
+      + (4 - 6 * t2 + 3 * t3) * v1
+      + (1 + 3 * t1 + 3 * t2 - 3 * t3) * v2
+      + t3 * v3) / 6;
+}
+
+/* harmony default export */ __webpack_exports__["b"] = (function(values) {
+  var n = values.length - 1;
+  return function(t) {
+    var i = t <= 0 ? (t = 0) : t >= 1 ? (t = 1, n - 1) : Math.floor(t * n),
+        v1 = values[i],
+        v2 = values[i + 1],
+        v0 = i > 0 ? values[i - 1] : 2 * v1 - v2,
+        v3 = i < n - 1 ? values[i + 2] : 2 * v2 - v1;
+    return basis((t - i / n) * n, v0, v1, v2, v3);
+  };
+});
+
+
+/***/ }),
+/* 19 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony default export */ __webpack_exports__["a"] = (function(x) {
+  return function() {
+    return x;
+  };
+});
+
+
+/***/ }),
+/* 20 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+// Computes the decimal coefficient and exponent of the specified number x with
+// significant digits p, where x is positive and p is in [1, 21] or undefined.
+// For example, formatDecimal(1.23) returns ["123", 0].
+/* harmony default export */ __webpack_exports__["a"] = (function(x, p) {
+  if ((i = (x = p ? x.toExponential(p - 1) : x.toExponential()).indexOf("e")) < 0) return null; // NaN, ±Infinity
+  var i, coefficient = x.slice(0, i);
+
+  // The string returned by toExponential either has the form \d\.\d+e[-+]\d+
+  // (e.g., 1.2e+3) or the form \de[-+]\d+ (e.g., 1e+3).
+  return [
+    coefficient.length > 1 ? coefficient[0] + coefficient.slice(2) : coefficient,
+    +x.slice(i + 1)
+  ];
+});
+
+
+/***/ }),
+/* 21 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__src_interval__ = __webpack_require__(0);
+/* unused harmony reexport timeInterval */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__src_millisecond__ = __webpack_require__(115);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "c", function() { return __WEBPACK_IMPORTED_MODULE_1__src_millisecond__["a"]; });
+/* unused harmony reexport timeMilliseconds */
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "n", function() { return __WEBPACK_IMPORTED_MODULE_1__src_millisecond__["a"]; });
+/* unused harmony reexport utcMilliseconds */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__src_second__ = __webpack_require__(116);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "g", function() { return __WEBPACK_IMPORTED_MODULE_2__src_second__["a"]; });
+/* unused harmony reexport timeSeconds */
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "r", function() { return __WEBPACK_IMPORTED_MODULE_2__src_second__["a"]; });
+/* unused harmony reexport utcSeconds */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__src_minute__ = __webpack_require__(117);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "d", function() { return __WEBPACK_IMPORTED_MODULE_3__src_minute__["a"]; });
+/* unused harmony reexport timeMinutes */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__src_hour__ = __webpack_require__(118);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return __WEBPACK_IMPORTED_MODULE_4__src_hour__["a"]; });
+/* unused harmony reexport timeHours */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__src_day__ = __webpack_require__(119);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return __WEBPACK_IMPORTED_MODULE_5__src_day__["a"]; });
+/* unused harmony reexport timeDays */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_6__src_week__ = __webpack_require__(120);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "j", function() { return __WEBPACK_IMPORTED_MODULE_6__src_week__["b"]; });
+/* unused harmony reexport timeWeeks */
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "h", function() { return __WEBPACK_IMPORTED_MODULE_6__src_week__["b"]; });
+/* unused harmony reexport timeSundays */
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "e", function() { return __WEBPACK_IMPORTED_MODULE_6__src_week__["a"]; });
+/* unused harmony reexport timeMondays */
+/* unused harmony reexport timeTuesday */
+/* unused harmony reexport timeTuesdays */
+/* unused harmony reexport timeWednesday */
+/* unused harmony reexport timeWednesdays */
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "i", function() { return __WEBPACK_IMPORTED_MODULE_6__src_week__["c"]; });
+/* unused harmony reexport timeThursdays */
+/* unused harmony reexport timeFriday */
+/* unused harmony reexport timeFridays */
+/* unused harmony reexport timeSaturday */
+/* unused harmony reexport timeSaturdays */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_7__src_month__ = __webpack_require__(121);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "f", function() { return __WEBPACK_IMPORTED_MODULE_7__src_month__["a"]; });
+/* unused harmony reexport timeMonths */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_8__src_year__ = __webpack_require__(122);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "k", function() { return __WEBPACK_IMPORTED_MODULE_8__src_year__["a"]; });
+/* unused harmony reexport timeYears */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_9__src_utcMinute__ = __webpack_require__(123);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "o", function() { return __WEBPACK_IMPORTED_MODULE_9__src_utcMinute__["a"]; });
+/* unused harmony reexport utcMinutes */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_10__src_utcHour__ = __webpack_require__(124);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "m", function() { return __WEBPACK_IMPORTED_MODULE_10__src_utcHour__["a"]; });
+/* unused harmony reexport utcHours */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_11__src_utcDay__ = __webpack_require__(125);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "l", function() { return __WEBPACK_IMPORTED_MODULE_11__src_utcDay__["a"]; });
+/* unused harmony reexport utcDays */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_12__src_utcWeek__ = __webpack_require__(126);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "u", function() { return __WEBPACK_IMPORTED_MODULE_12__src_utcWeek__["b"]; });
+/* unused harmony reexport utcWeeks */
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "s", function() { return __WEBPACK_IMPORTED_MODULE_12__src_utcWeek__["b"]; });
+/* unused harmony reexport utcSundays */
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "p", function() { return __WEBPACK_IMPORTED_MODULE_12__src_utcWeek__["a"]; });
+/* unused harmony reexport utcMondays */
+/* unused harmony reexport utcTuesday */
+/* unused harmony reexport utcTuesdays */
+/* unused harmony reexport utcWednesday */
+/* unused harmony reexport utcWednesdays */
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "t", function() { return __WEBPACK_IMPORTED_MODULE_12__src_utcWeek__["c"]; });
+/* unused harmony reexport utcThursdays */
+/* unused harmony reexport utcFriday */
+/* unused harmony reexport utcFridays */
+/* unused harmony reexport utcSaturday */
+/* unused harmony reexport utcSaturdays */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_13__src_utcMonth__ = __webpack_require__(127);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "q", function() { return __WEBPACK_IMPORTED_MODULE_13__src_utcMonth__["a"]; });
+/* unused harmony reexport utcMonths */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_14__src_utcYear__ = __webpack_require__(128);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "v", function() { return __WEBPACK_IMPORTED_MODULE_14__src_utcYear__["a"]; });
+/* unused harmony reexport utcYears */
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/***/ }),
+/* 22 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return timeFormat; });
+/* unused harmony export timeParse */
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return utcFormat; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "c", function() { return utcParse; });
+/* unused harmony export default */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__locale__ = __webpack_require__(52);
+
+
+var locale;
+var timeFormat;
+var timeParse;
+var utcFormat;
+var utcParse;
+
+defaultLocale({
+  dateTime: "%x, %X",
+  date: "%-m/%-d/%Y",
+  time: "%-I:%M:%S %p",
+  periods: ["AM", "PM"],
+  days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
+  shortDays: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
+  months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
+  shortMonths: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
+});
+
+function defaultLocale(definition) {
+  locale = Object(__WEBPACK_IMPORTED_MODULE_0__locale__["a" /* default */])(definition);
+  timeFormat = locale.format;
+  timeParse = locale.parse;
+  utcFormat = locale.utcFormat;
+  utcParse = locale.utcParse;
+  return locale;
+}
+
+
+/***/ }),
+/* 23 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* unused harmony export bisectRight */
+/* unused harmony export bisectLeft */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__ascending__ = __webpack_require__(4);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__bisector__ = __webpack_require__(24);
+
+
+
+var ascendingBisect = Object(__WEBPACK_IMPORTED_MODULE_1__bisector__["a" /* default */])(__WEBPACK_IMPORTED_MODULE_0__ascending__["a" /* default */]);
+var bisectRight = ascendingBisect.right;
+var bisectLeft = ascendingBisect.left;
+/* harmony default export */ __webpack_exports__["a"] = (bisectRight);
+
+
+/***/ }),
+/* 24 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__ascending__ = __webpack_require__(4);
+
+
+/* harmony default export */ __webpack_exports__["a"] = (function(compare) {
+  if (compare.length === 1) compare = ascendingComparator(compare);
+  return {
+    left: function(a, x, lo, hi) {
+      if (lo == null) lo = 0;
+      if (hi == null) hi = a.length;
+      while (lo < hi) {
+        var mid = lo + hi >>> 1;
+        if (compare(a[mid], x) < 0) lo = mid + 1;
+        else hi = mid;
+      }
+      return lo;
+    },
+    right: function(a, x, lo, hi) {
+      if (lo == null) lo = 0;
+      if (hi == null) hi = a.length;
+      while (lo < hi) {
+        var mid = lo + hi >>> 1;
+        if (compare(a[mid], x) > 0) hi = mid;
+        else lo = mid + 1;
+      }
+      return lo;
+    }
+  };
+});
+
+function ascendingComparator(f) {
+  return function(d, x) {
+    return Object(__WEBPACK_IMPORTED_MODULE_0__ascending__["a" /* default */])(f(d), x);
+  };
+}
+
+
+/***/ }),
+/* 25 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (immutable) */ __webpack_exports__["a"] = pair;
+/* unused harmony default export */ var _unused_webpack_default_export = (function(array, f) {
+  if (f == null) f = pair;
+  var i = 0, n = array.length - 1, p = array[0], pairs = new Array(n < 0 ? 0 : n);
+  while (i < n) pairs[i] = f(p, p = array[++i]);
+  return pairs;
+});
+
+function pair(a, b) {
+  return [a, b];
+}
+
+
+/***/ }),
+/* 26 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__variance__ = __webpack_require__(27);
+
+
+/* harmony default export */ __webpack_exports__["a"] = (function(array, f) {
+  var v = Object(__WEBPACK_IMPORTED_MODULE_0__variance__["a" /* default */])(array, f);
+  return v ? Math.sqrt(v) : v;
+});
+
+
+/***/ }),
+/* 27 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__number__ = __webpack_require__(7);
+
+
+/* harmony default export */ __webpack_exports__["a"] = (function(values, valueof) {
+  var n = values.length,
+      m = 0,
+      i = -1,
+      mean = 0,
+      value,
+      delta,
+      sum = 0;
+
+  if (valueof == null) {
+    while (++i < n) {
+      if (!isNaN(value = Object(__WEBPACK_IMPORTED_MODULE_0__number__["a" /* default */])(values[i]))) {
+        delta = value - mean;
+        mean += delta / ++m;
+        sum += delta * (value - mean);
+      }
+    }
+  }
+
+  else {
+    while (++i < n) {
+      if (!isNaN(value = Object(__WEBPACK_IMPORTED_MODULE_0__number__["a" /* default */])(valueof(values[i], i, values)))) {
+        delta = value - mean;
+        mean += delta / ++m;
+        sum += delta * (value - mean);
+      }
+    }
+  }
+
+  if (m > 1) return sum / (m - 1);
+});
+
+
+/***/ }),
+/* 28 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony default export */ __webpack_exports__["a"] = (function(values, valueof) {
+  var n = values.length,
+      i = -1,
+      value,
+      min,
+      max;
+
+  if (valueof == null) {
+    while (++i < n) { // Find the first comparable value.
+      if ((value = values[i]) != null && value >= value) {
+        min = max = value;
+        while (++i < n) { // Compare the remaining values.
+          if ((value = values[i]) != null) {
+            if (min > value) min = value;
+            if (max < value) max = value;
+          }
+        }
+      }
+    }
+  }
+
+  else {
+    while (++i < n) { // Find the first comparable value.
+      if ((value = valueof(values[i], i, values)) != null && value >= value) {
+        min = max = value;
+        while (++i < n) { // Compare the remaining values.
+          if ((value = valueof(values[i], i, values)) != null) {
+            if (min > value) min = value;
+            if (max < value) max = value;
+          }
+        }
+      }
+    }
+  }
+
+  return [min, max];
+});
+
+
+/***/ }),
+/* 29 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return slice; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return map; });
+var array = Array.prototype;
+
+var slice = array.slice;
+var map = array.map;
+
+
+/***/ }),
+/* 30 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony default export */ __webpack_exports__["a"] = (function(start, stop, step) {
+  start = +start, stop = +stop, step = (n = arguments.length) < 2 ? (stop = start, start = 0, 1) : n < 3 ? 1 : +step;
+
+  var i = -1,
+      n = Math.max(0, Math.ceil((stop - start) / step)) | 0,
+      range = new Array(n);
+
+  while (++i < n) {
+    range[i] = start + i * step;
+  }
+
+  return range;
+});
+
+
+/***/ }),
+/* 31 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (immutable) */ __webpack_exports__["b"] = tickIncrement;
+/* harmony export (immutable) */ __webpack_exports__["c"] = tickStep;
+var e10 = Math.sqrt(50),
+    e5 = Math.sqrt(10),
+    e2 = Math.sqrt(2);
+
+/* harmony default export */ __webpack_exports__["a"] = (function(start, stop, count) {
+  var reverse,
+      i = -1,
+      n,
+      ticks,
+      step;
+
+  stop = +stop, start = +start, count = +count;
+  if (start === stop && count > 0) return [start];
+  if (reverse = stop < start) n = start, start = stop, stop = n;
+  if ((step = tickIncrement(start, stop, count)) === 0 || !isFinite(step)) return [];
+
+  if (step > 0) {
+    start = Math.ceil(start / step);
+    stop = Math.floor(stop / step);
+    ticks = new Array(n = Math.ceil(stop - start + 1));
+    while (++i < n) ticks[i] = (start + i) * step;
+  } else {
+    start = Math.floor(start * step);
+    stop = Math.ceil(stop * step);
+    ticks = new Array(n = Math.ceil(start - stop + 1));
+    while (++i < n) ticks[i] = (start - i) / step;
+  }
+
+  if (reverse) ticks.reverse();
+
+  return ticks;
+});
+
+function tickIncrement(start, stop, count) {
+  var step = (stop - start) / Math.max(0, count),
+      power = Math.floor(Math.log(step) / Math.LN10),
+      error = step / Math.pow(10, power);
+  return power >= 0
+      ? (error >= e10 ? 10 : error >= e5 ? 5 : error >= e2 ? 2 : 1) * Math.pow(10, power)
+      : -Math.pow(10, -power) / (error >= e10 ? 10 : error >= e5 ? 5 : error >= e2 ? 2 : 1);
+}
+
+function tickStep(start, stop, count) {
+  var step0 = Math.abs(stop - start) / Math.max(0, count),
+      step1 = Math.pow(10, Math.floor(Math.log(step0) / Math.LN10)),
+      error = step0 / step1;
+  if (error >= e10) step1 *= 10;
+  else if (error >= e5) step1 *= 5;
+  else if (error >= e2) step1 *= 2;
+  return stop < start ? -step1 : step1;
+}
+
+
+/***/ }),
+/* 32 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony default export */ __webpack_exports__["a"] = (function(values) {
+  return Math.ceil(Math.log(values.length) / Math.LN2) + 1;
+});
+
+
+/***/ }),
+/* 33 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony default export */ __webpack_exports__["a"] = (function(values, valueof) {
+  var n = values.length,
+      i = -1,
+      value,
+      min;
+
+  if (valueof == null) {
+    while (++i < n) { // Find the first comparable value.
+      if ((value = values[i]) != null && value >= value) {
+        min = value;
+        while (++i < n) { // Compare the remaining values.
+          if ((value = values[i]) != null && min > value) {
+            min = value;
+          }
+        }
+      }
+    }
+  }
+
+  else {
+    while (++i < n) { // Find the first comparable value.
+      if ((value = valueof(values[i], i, values)) != null && value >= value) {
+        min = value;
+        while (++i < n) { // Compare the remaining values.
+          if ((value = valueof(values[i], i, values)) != null && min > value) {
+            min = value;
+          }
+        }
+      }
+    }
+  }
+
+  return min;
+});
+
+
+/***/ }),
+/* 34 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__min__ = __webpack_require__(33);
+
+
+/* harmony default export */ __webpack_exports__["a"] = (function(matrix) {
+  if (!(n = matrix.length)) return [];
+  for (var i = -1, m = Object(__WEBPACK_IMPORTED_MODULE_0__min__["a" /* default */])(matrix, length), transpose = new Array(m); ++i < m;) {
+    for (var j = -1, n, row = transpose[i] = new Array(n); ++j < n;) {
+      row[j] = matrix[j][i];
+    }
+  }
+  return transpose;
+});
+
+function length(d) {
+  return d.length;
+}
+
+
+/***/ }),
+/* 35 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return implicit; });
+/* harmony export (immutable) */ __webpack_exports__["a"] = ordinal;
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_d3_collection__ = __webpack_require__(79);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__array__ = __webpack_require__(3);
+
+
+
+var implicit = {name: "implicit"};
+
+function ordinal(range) {
+  var index = Object(__WEBPACK_IMPORTED_MODULE_0_d3_collection__["a" /* map */])(),
+      domain = [],
+      unknown = implicit;
+
+  range = range == null ? [] : __WEBPACK_IMPORTED_MODULE_1__array__["b" /* slice */].call(range);
+
+  function scale(d) {
+    var key = d + "", i = index.get(key);
+    if (!i) {
+      if (unknown !== implicit) return unknown;
+      index.set(key, i = domain.push(d));
+    }
+    return range[(i - 1) % range.length];
+  }
+
+  scale.domain = function(_) {
+    if (!arguments.length) return domain.slice();
+    domain = [], index = Object(__WEBPACK_IMPORTED_MODULE_0_d3_collection__["a" /* map */])();
+    var i = -1, n = _.length, d, key;
+    while (++i < n) if (!index.has(key = (d = _[i]) + "")) index.set(key, domain.push(d));
+    return scale;
+  };
+
+  scale.range = function(_) {
+    return arguments.length ? (range = __WEBPACK_IMPORTED_MODULE_1__array__["b" /* slice */].call(_), scale) : range.slice();
+  };
+
+  scale.unknown = function(_) {
+    return arguments.length ? (unknown = _, scale) : unknown;
+  };
+
+  scale.copy = function() {
+    return ordinal()
+        .domain(domain)
+        .range(range)
+        .unknown(unknown);
+  };
+
+  return scale;
+}
+
+
+/***/ }),
+/* 36 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return deg2rad; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return rad2deg; });
+var deg2rad = Math.PI / 180;
+var rad2deg = 180 / Math.PI;
+
+
+/***/ }),
+/* 37 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* unused harmony export rgbBasis */
+/* unused harmony export rgbBasisClosed */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_d3_color__ = __webpack_require__(6);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__basis__ = __webpack_require__(18);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__basisClosed__ = __webpack_require__(38);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__color__ = __webpack_require__(8);
+
+
+
+
+
+/* harmony default export */ __webpack_exports__["a"] = ((function rgbGamma(y) {
+  var color = Object(__WEBPACK_IMPORTED_MODULE_3__color__["b" /* gamma */])(y);
+
+  function rgb(start, end) {
+    var r = color((start = Object(__WEBPACK_IMPORTED_MODULE_0_d3_color__["f" /* rgb */])(start)).r, (end = Object(__WEBPACK_IMPORTED_MODULE_0_d3_color__["f" /* rgb */])(end)).r),
+        g = color(start.g, end.g),
+        b = color(start.b, end.b),
+        opacity = Object(__WEBPACK_IMPORTED_MODULE_3__color__["a" /* default */])(start.opacity, end.opacity);
+    return function(t) {
+      start.r = r(t);
+      start.g = g(t);
+      start.b = b(t);
+      start.opacity = opacity(t);
+      return start + "";
+    };
+  }
+
+  rgb.gamma = rgbGamma;
+
+  return rgb;
+})(1));
+
+function rgbSpline(spline) {
+  return function(colors) {
+    var n = colors.length,
+        r = new Array(n),
+        g = new Array(n),
+        b = new Array(n),
+        i, color;
+    for (i = 0; i < n; ++i) {
+      color = Object(__WEBPACK_IMPORTED_MODULE_0_d3_color__["f" /* rgb */])(colors[i]);
+      r[i] = color.r || 0;
+      g[i] = color.g || 0;
+      b[i] = color.b || 0;
+    }
+    r = spline(r);
+    g = spline(g);
+    b = spline(b);
+    color.opacity = 1;
+    return function(t) {
+      color.r = r(t);
+      color.g = g(t);
+      color.b = b(t);
+      return color + "";
+    };
+  };
+}
+
+var rgbBasis = rgbSpline(__WEBPACK_IMPORTED_MODULE_1__basis__["b" /* default */]);
+var rgbBasisClosed = rgbSpline(__WEBPACK_IMPORTED_MODULE_2__basisClosed__["a" /* default */]);
+
+
+/***/ }),
+/* 38 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__basis__ = __webpack_require__(18);
+
+
+/* harmony default export */ __webpack_exports__["a"] = (function(values) {
+  var n = values.length;
+  return function(t) {
+    var i = Math.floor(((t %= 1) < 0 ? ++t : t) * n),
+        v0 = values[(i + n - 1) % n],
+        v1 = values[i % n],
+        v2 = values[(i + 1) % n],
+        v3 = values[(i + 2) % n];
+    return Object(__WEBPACK_IMPORTED_MODULE_0__basis__["a" /* basis */])((t - i / n) * n, v0, v1, v2, v3);
+  };
+});
+
+
+/***/ }),
+/* 39 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony default export */ __webpack_exports__["a"] = (function(x) {
+  return function() {
+    return x;
+  };
+});
+
+
+/***/ }),
+/* 40 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__value__ = __webpack_require__(15);
+
+
+/* harmony default export */ __webpack_exports__["a"] = (function(a, b) {
+  var nb = b ? b.length : 0,
+      na = a ? Math.min(nb, a.length) : 0,
+      x = new Array(na),
+      c = new Array(nb),
+      i;
+
+  for (i = 0; i < na; ++i) x[i] = Object(__WEBPACK_IMPORTED_MODULE_0__value__["a" /* default */])(a[i], b[i]);
+  for (; i < nb; ++i) c[i] = b[i];
+
+  return function(t) {
+    for (i = 0; i < na; ++i) c[i] = x[i](t);
+    return c;
+  };
+});
+
+
+/***/ }),
+/* 41 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony default export */ __webpack_exports__["a"] = (function(a, b) {
+  var d = new Date;
+  return a = +a, b -= a, function(t) {
+    return d.setTime(a + b * t), d;
+  };
+});
+
+
+/***/ }),
+/* 42 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__value__ = __webpack_require__(15);
+
+
+/* harmony default export */ __webpack_exports__["a"] = (function(a, b) {
+  var i = {},
+      c = {},
+      k;
+
+  if (a === null || typeof a !== "object") a = {};
+  if (b === null || typeof b !== "object") b = {};
+
+  for (k in b) {
+    if (k in a) {
+      i[k] = Object(__WEBPACK_IMPORTED_MODULE_0__value__["a" /* default */])(a[k], b[k]);
+    } else {
+      c[k] = b[k];
+    }
+  }
+
+  return function(t) {
+    for (k in i) c[k] = i[k](t);
+    return c;
+  };
+});
+
+
+/***/ }),
+/* 43 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__number__ = __webpack_require__(9);
+
+
+var reA = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g,
+    reB = new RegExp(reA.source, "g");
+
+function zero(b) {
+  return function() {
+    return b;
+  };
+}
+
+function one(b) {
+  return function(t) {
+    return b(t) + "";
+  };
+}
+
+/* harmony default export */ __webpack_exports__["a"] = (function(a, b) {
+  var bi = reA.lastIndex = reB.lastIndex = 0, // scan index for next number in b
+      am, // current match in a
+      bm, // current match in b
+      bs, // string preceding current number in b, if any
+      i = -1, // index in s
+      s = [], // string constants and placeholders
+      q = []; // number interpolators
+
+  // Coerce inputs to strings.
+  a = a + "", b = b + "";
+
+  // Interpolate pairs of numbers in a & b.
+  while ((am = reA.exec(a))
+      && (bm = reB.exec(b))) {
+    if ((bs = bm.index) > bi) { // a string precedes the next number in b
+      bs = b.slice(bi, bs);
+      if (s[i]) s[i] += bs; // coalesce with previous string
+      else s[++i] = bs;
+    }
+    if ((am = am[0]) === (bm = bm[0])) { // numbers in a & b match
+      if (s[i]) s[i] += bm; // coalesce with previous string
+      else s[++i] = bm;
+    } else { // interpolate non-matching numbers
+      s[++i] = null;
+      q.push({i: i, x: Object(__WEBPACK_IMPORTED_MODULE_0__number__["a" /* default */])(am, bm)});
+    }
+    bi = reB.lastIndex;
+  }
+
+  // Add remains of b.
+  if (bi < b.length) {
+    bs = b.slice(bi);
+    if (s[i]) s[i] += bs; // coalesce with previous string
+    else s[++i] = bs;
+  }
+
+  // Special optimization for only a single match.
+  // Otherwise, interpolate each of the numbers and rejoin the string.
+  return s.length < 2 ? (q[0]
+      ? one(q[0].x)
+      : zero(b))
+      : (b = q.length, function(t) {
+          for (var i = 0, o; i < b; ++i) s[(o = q[i]).i] = o.x(t);
+          return s.join("");
+        });
+});
+
+
+/***/ }),
+/* 44 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony default export */ __webpack_exports__["a"] = (function(x) {
+  return +x;
+});
+
+
+/***/ }),
+/* 45 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__src_defaultLocale__ = __webpack_require__(100);
+/* unused harmony reexport formatDefaultLocale */
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return __WEBPACK_IMPORTED_MODULE_0__src_defaultLocale__["a"]; });
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return __WEBPACK_IMPORTED_MODULE_0__src_defaultLocale__["b"]; });
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__src_locale__ = __webpack_require__(46);
+/* unused harmony reexport formatLocale */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__src_formatSpecifier__ = __webpack_require__(47);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "c", function() { return __WEBPACK_IMPORTED_MODULE_2__src_formatSpecifier__["a"]; });
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__src_precisionFixed__ = __webpack_require__(107);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "d", function() { return __WEBPACK_IMPORTED_MODULE_3__src_precisionFixed__["a"]; });
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__src_precisionPrefix__ = __webpack_require__(108);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "e", function() { return __WEBPACK_IMPORTED_MODULE_4__src_precisionPrefix__["a"]; });
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__src_precisionRound__ = __webpack_require__(109);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "f", function() { return __WEBPACK_IMPORTED_MODULE_5__src_precisionRound__["a"]; });
+
+
+
+
+
+
+
+
+/***/ }),
+/* 46 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__exponent__ = __webpack_require__(11);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__formatGroup__ = __webpack_require__(101);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__formatNumerals__ = __webpack_require__(102);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__formatSpecifier__ = __webpack_require__(47);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__formatTrim__ = __webpack_require__(103);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__formatTypes__ = __webpack_require__(104);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_6__formatPrefixAuto__ = __webpack_require__(48);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_7__identity__ = __webpack_require__(106);
+
+
+
+
+
+
+
+
+
+var prefixes = ["y","z","a","f","p","n","µ","m","","k","M","G","T","P","E","Z","Y"];
+
+/* harmony default export */ __webpack_exports__["a"] = (function(locale) {
+  var group = locale.grouping && locale.thousands ? Object(__WEBPACK_IMPORTED_MODULE_1__formatGroup__["a" /* default */])(locale.grouping, locale.thousands) : __WEBPACK_IMPORTED_MODULE_7__identity__["a" /* default */],
+      currency = locale.currency,
+      decimal = locale.decimal,
+      numerals = locale.numerals ? Object(__WEBPACK_IMPORTED_MODULE_2__formatNumerals__["a" /* default */])(locale.numerals) : __WEBPACK_IMPORTED_MODULE_7__identity__["a" /* default */],
+      percent = locale.percent || "%";
+
+  function newFormat(specifier) {
+    specifier = Object(__WEBPACK_IMPORTED_MODULE_3__formatSpecifier__["a" /* default */])(specifier);
+
+    var fill = specifier.fill,
+        align = specifier.align,
+        sign = specifier.sign,
+        symbol = specifier.symbol,
+        zero = specifier.zero,
+        width = specifier.width,
+        comma = specifier.comma,
+        precision = specifier.precision,
+        trim = specifier.trim,
+        type = specifier.type;
+
+    // The "n" type is an alias for ",g".
+    if (type === "n") comma = true, type = "g";
+
+    // The "" type, and any invalid type, is an alias for ".12~g".
+    else if (!__WEBPACK_IMPORTED_MODULE_5__formatTypes__["a" /* default */][type]) precision == null && (precision = 12), trim = true, type = "g";
+
+    // If zero fill is specified, padding goes after sign and before digits.
+    if (zero || (fill === "0" && align === "=")) zero = true, fill = "0", align = "=";
+
+    // Compute the prefix and suffix.
+    // For SI-prefix, the suffix is lazily computed.
+    var prefix = symbol === "$" ? currency[0] : symbol === "#" && /[boxX]/.test(type) ? "0" + type.toLowerCase() : "",
+        suffix = symbol === "$" ? currency[1] : /[%p]/.test(type) ? percent : "";
+
+    // What format function should we use?
+    // Is this an integer type?
+    // Can this type generate exponential notation?
+    var formatType = __WEBPACK_IMPORTED_MODULE_5__formatTypes__["a" /* default */][type],
+        maybeSuffix = /[defgprs%]/.test(type);
+
+    // Set the default precision if not specified,
+    // or clamp the specified precision to the supported range.
+    // For significant precision, it must be in [1, 21].
+    // For fixed precision, it must be in [0, 20].
+    precision = precision == null ? 6
+        : /[gprs]/.test(type) ? Math.max(1, Math.min(21, precision))
+        : Math.max(0, Math.min(20, precision));
+
+    function format(value) {
+      var valuePrefix = prefix,
+          valueSuffix = suffix,
+          i, n, c;
+
+      if (type === "c") {
+        valueSuffix = formatType(value) + valueSuffix;
+        value = "";
+      } else {
+        value = +value;
+
+        // Perform the initial formatting.
+        var valueNegative = value < 0;
+        value = formatType(Math.abs(value), precision);
+
+        // Trim insignificant zeros.
+        if (trim) value = Object(__WEBPACK_IMPORTED_MODULE_4__formatTrim__["a" /* default */])(value);
+
+        // If a negative value rounds to zero during formatting, treat as positive.
+        if (valueNegative && +value === 0) valueNegative = false;
+
+        // Compute the prefix and suffix.
+        valuePrefix = (valueNegative ? (sign === "(" ? sign : "-") : sign === "-" || sign === "(" ? "" : sign) + valuePrefix;
+        valueSuffix = (type === "s" ? prefixes[8 + __WEBPACK_IMPORTED_MODULE_6__formatPrefixAuto__["b" /* prefixExponent */] / 3] : "") + valueSuffix + (valueNegative && sign === "(" ? ")" : "");
+
+        // Break the formatted value into the integer “value” part that can be
+        // grouped, and fractional or exponential “suffix” part that is not.
+        if (maybeSuffix) {
+          i = -1, n = value.length;
+          while (++i < n) {
+            if (c = value.charCodeAt(i), 48 > c || c > 57) {
+              valueSuffix = (c === 46 ? decimal + value.slice(i + 1) : value.slice(i)) + valueSuffix;
+              value = value.slice(0, i);
+              break;
+            }
+          }
+        }
+      }
+
+      // If the fill character is not "0", grouping is applied before padding.
+      if (comma && !zero) value = group(value, Infinity);
+
+      // Compute the padding.
+      var length = valuePrefix.length + value.length + valueSuffix.length,
+          padding = length < width ? new Array(width - length + 1).join(fill) : "";
+
+      // If the fill character is "0", grouping is applied after padding.
+      if (comma && zero) value = group(padding + value, padding.length ? width - valueSuffix.length : Infinity), padding = "";
+
+      // Reconstruct the final output based on the desired alignment.
+      switch (align) {
+        case "<": value = valuePrefix + value + valueSuffix + padding; break;
+        case "=": value = valuePrefix + padding + value + valueSuffix; break;
+        case "^": value = padding.slice(0, length = padding.length >> 1) + valuePrefix + value + valueSuffix + padding.slice(length); break;
+        default: value = padding + valuePrefix + value + valueSuffix; break;
+      }
+
+      return numerals(value);
+    }
+
+    format.toString = function() {
+      return specifier + "";
+    };
+
+    return format;
+  }
+
+  function formatPrefix(specifier, value) {
+    var f = newFormat((specifier = Object(__WEBPACK_IMPORTED_MODULE_3__formatSpecifier__["a" /* default */])(specifier), specifier.type = "f", specifier)),
+        e = Math.max(-8, Math.min(8, Math.floor(Object(__WEBPACK_IMPORTED_MODULE_0__exponent__["a" /* default */])(value) / 3))) * 3,
+        k = Math.pow(10, -e),
+        prefix = prefixes[8 + e / 3];
+    return function(value) {
+      return f(k * value) + prefix;
+    };
+  }
+
+  return {
+    format: newFormat,
+    formatPrefix: formatPrefix
+  };
+});
+
+
+/***/ }),
+/* 47 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (immutable) */ __webpack_exports__["a"] = formatSpecifier;
+// [[fill]align][sign][symbol][0][width][,][.precision][~][type]
+var re = /^(?:(.)?([<>=^]))?([+\-\( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;
+
+function formatSpecifier(specifier) {
+  return new FormatSpecifier(specifier);
+}
+
+formatSpecifier.prototype = FormatSpecifier.prototype; // instanceof
+
+function FormatSpecifier(specifier) {
+  if (!(match = re.exec(specifier))) throw new Error("invalid format: " + specifier);
+  var match;
+  this.fill = match[1] || " ";
+  this.align = match[2] || ">";
+  this.sign = match[3] || "-";
+  this.symbol = match[4] || "";
+  this.zero = !!match[5];
+  this.width = match[6] && +match[6];
+  this.comma = !!match[7];
+  this.precision = match[8] && +match[8].slice(1);
+  this.trim = !!match[9];
+  this.type = match[10] || "";
+}
+
+FormatSpecifier.prototype.toString = function() {
+  return this.fill
+      + this.align
+      + this.sign
+      + this.symbol
+      + (this.zero ? "0" : "")
+      + (this.width == null ? "" : Math.max(1, this.width | 0))
+      + (this.comma ? "," : "")
+      + (this.precision == null ? "" : "." + Math.max(0, this.precision | 0))
+      + (this.trim ? "~" : "")
+      + this.type;
+};
+
+
+/***/ }),
+/* 48 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return prefixExponent; });
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__formatDecimal__ = __webpack_require__(20);
+
+
+var prefixExponent;
+
+/* harmony default export */ __webpack_exports__["a"] = (function(x, p) {
+  var d = Object(__WEBPACK_IMPORTED_MODULE_0__formatDecimal__["a" /* default */])(x, p);
+  if (!d) return x + "";
+  var coefficient = d[0],
+      exponent = d[1],
+      i = exponent - (prefixExponent = Math.max(-8, Math.min(8, Math.floor(exponent / 3))) * 3) + 1,
+      n = coefficient.length;
+  return i === n ? coefficient
+      : i > n ? coefficient + new Array(i - n + 1).join("0")
+      : i > 0 ? coefficient.slice(0, i) + "." + coefficient.slice(i)
+      : "0." + new Array(1 - i).join("0") + Object(__WEBPACK_IMPORTED_MODULE_0__formatDecimal__["a" /* default */])(x, Math.max(0, p + i - 1))[0]; // less than 1y!
+});
+
+
+/***/ }),
+/* 49 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony default export */ __webpack_exports__["a"] = (function(domain, interval) {
+  domain = domain.slice();
+
+  var i0 = 0,
+      i1 = domain.length - 1,
+      x0 = domain[i0],
+      x1 = domain[i1],
+      t;
+
+  if (x1 < x0) {
+    t = i0, i0 = i1, i1 = t;
+    t = x0, x0 = x1, x1 = t;
+  }
+
+  domain[i0] = interval.floor(x0);
+  domain[i1] = interval.ceil(x1);
+  return domain;
+});
+
+
+/***/ }),
+/* 50 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (immutable) */ __webpack_exports__["a"] = calendar;
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_d3_array__ = __webpack_require__(1);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_d3_interpolate__ = __webpack_require__(14);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2_d3_time__ = __webpack_require__(21);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3_d3_time_format__ = __webpack_require__(51);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__array__ = __webpack_require__(3);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__continuous__ = __webpack_require__(10);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_6__nice__ = __webpack_require__(49);
+
+
+
+
+
+
+
+
+var durationSecond = 1000,
+    durationMinute = durationSecond * 60,
+    durationHour = durationMinute * 60,
+    durationDay = durationHour * 24,
+    durationWeek = durationDay * 7,
+    durationMonth = durationDay * 30,
+    durationYear = durationDay * 365;
+
+function date(t) {
+  return new Date(t);
+}
+
+function number(t) {
+  return t instanceof Date ? +t : +new Date(+t);
+}
+
+function calendar(year, month, week, day, hour, minute, second, millisecond, format) {
+  var scale = Object(__WEBPACK_IMPORTED_MODULE_5__continuous__["b" /* default */])(__WEBPACK_IMPORTED_MODULE_5__continuous__["c" /* deinterpolateLinear */], __WEBPACK_IMPORTED_MODULE_1_d3_interpolate__["b" /* interpolateNumber */]),
+      invert = scale.invert,
+      domain = scale.domain;
+
+  var formatMillisecond = format(".%L"),
+      formatSecond = format(":%S"),
+      formatMinute = format("%I:%M"),
+      formatHour = format("%I %p"),
+      formatDay = format("%a %d"),
+      formatWeek = format("%b %d"),
+      formatMonth = format("%B"),
+      formatYear = format("%Y");
+
+  var tickIntervals = [
+    [second,  1,      durationSecond],
+    [second,  5,  5 * durationSecond],
+    [second, 15, 15 * durationSecond],
+    [second, 30, 30 * durationSecond],
+    [minute,  1,      durationMinute],
+    [minute,  5,  5 * durationMinute],
+    [minute, 15, 15 * durationMinute],
+    [minute, 30, 30 * durationMinute],
+    [  hour,  1,      durationHour  ],
+    [  hour,  3,  3 * durationHour  ],
+    [  hour,  6,  6 * durationHour  ],
+    [  hour, 12, 12 * durationHour  ],
+    [   day,  1,      durationDay   ],
+    [   day,  2,  2 * durationDay   ],
+    [  week,  1,      durationWeek  ],
+    [ month,  1,      durationMonth ],
+    [ month,  3,  3 * durationMonth ],
+    [  year,  1,      durationYear  ]
+  ];
+
+  function tickFormat(date) {
+    return (second(date) < date ? formatMillisecond
+        : minute(date) < date ? formatSecond
+        : hour(date) < date ? formatMinute
+        : day(date) < date ? formatHour
+        : month(date) < date ? (week(date) < date ? formatDay : formatWeek)
+        : year(date) < date ? formatMonth
+        : formatYear)(date);
+  }
+
+  function tickInterval(interval, start, stop, step) {
+    if (interval == null) interval = 10;
+
+    // If a desired tick count is specified, pick a reasonable tick interval
+    // based on the extent of the domain and a rough estimate of tick size.
+    // Otherwise, assume interval is already a time interval and use it.
+    if (typeof interval === "number") {
+      var target = Math.abs(stop - start) / interval,
+          i = Object(__WEBPACK_IMPORTED_MODULE_0_d3_array__["c" /* bisector */])(function(i) { return i[2]; }).right(tickIntervals, target);
+      if (i === tickIntervals.length) {
+        step = Object(__WEBPACK_IMPORTED_MODULE_0_d3_array__["g" /* tickStep */])(start / durationYear, stop / durationYear, interval);
+        interval = year;
+      } else if (i) {
+        i = tickIntervals[target / tickIntervals[i - 1][2] < tickIntervals[i][2] / target ? i - 1 : i];
+        step = i[1];
+        interval = i[0];
+      } else {
+        step = Math.max(Object(__WEBPACK_IMPORTED_MODULE_0_d3_array__["g" /* tickStep */])(start, stop, interval), 1);
+        interval = millisecond;
+      }
+    }
+
+    return step == null ? interval : interval.every(step);
+  }
+
+  scale.invert = function(y) {
+    return new Date(invert(y));
+  };
+
+  scale.domain = function(_) {
+    return arguments.length ? domain(__WEBPACK_IMPORTED_MODULE_4__array__["a" /* map */].call(_, number)) : domain().map(date);
+  };
+
+  scale.ticks = function(interval, step) {
+    var d = domain(),
+        t0 = d[0],
+        t1 = d[d.length - 1],
+        r = t1 < t0,
+        t;
+    if (r) t = t0, t0 = t1, t1 = t;
+    t = tickInterval(interval, t0, t1, step);
+    t = t ? t.range(t0, t1 + 1) : []; // inclusive stop
+    return r ? t.reverse() : t;
+  };
+
+  scale.tickFormat = function(count, specifier) {
+    return specifier == null ? tickFormat : format(specifier);
+  };
+
+  scale.nice = function(interval, step) {
+    var d = domain();
+    return (interval = tickInterval(interval, d[0], d[d.length - 1], step))
+        ? domain(Object(__WEBPACK_IMPORTED_MODULE_6__nice__["a" /* default */])(d, interval))
+        : scale;
+  };
+
+  scale.copy = function() {
+    return Object(__WEBPACK_IMPORTED_MODULE_5__continuous__["a" /* copy */])(scale, calendar(year, month, week, day, hour, minute, second, millisecond, format));
+  };
+
+  return scale;
+}
+
+/* harmony default export */ __webpack_exports__["b"] = (function() {
+  return calendar(__WEBPACK_IMPORTED_MODULE_2_d3_time__["k" /* timeYear */], __WEBPACK_IMPORTED_MODULE_2_d3_time__["f" /* timeMonth */], __WEBPACK_IMPORTED_MODULE_2_d3_time__["j" /* timeWeek */], __WEBPACK_IMPORTED_MODULE_2_d3_time__["a" /* timeDay */], __WEBPACK_IMPORTED_MODULE_2_d3_time__["b" /* timeHour */], __WEBPACK_IMPORTED_MODULE_2_d3_time__["d" /* timeMinute */], __WEBPACK_IMPORTED_MODULE_2_d3_time__["g" /* timeSecond */], __WEBPACK_IMPORTED_MODULE_2_d3_time__["c" /* timeMillisecond */], __WEBPACK_IMPORTED_MODULE_3_d3_time_format__["a" /* timeFormat */]).domain([new Date(2000, 0, 1), new Date(2000, 0, 2)]);
+});
+
+
+/***/ }),
+/* 51 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__src_defaultLocale__ = __webpack_require__(22);
+/* unused harmony reexport timeFormatDefaultLocale */
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return __WEBPACK_IMPORTED_MODULE_0__src_defaultLocale__["a"]; });
+/* unused harmony reexport timeParse */
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return __WEBPACK_IMPORTED_MODULE_0__src_defaultLocale__["b"]; });
+/* unused harmony reexport utcParse */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__src_locale__ = __webpack_require__(52);
+/* unused harmony reexport timeFormatLocale */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__src_isoFormat__ = __webpack_require__(53);
+/* unused harmony reexport isoFormat */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__src_isoParse__ = __webpack_require__(129);
+/* unused harmony reexport isoParse */
+
+
+
+
+
+
+/***/ }),
+/* 52 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (immutable) */ __webpack_exports__["a"] = formatLocale;
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_d3_time__ = __webpack_require__(21);
+
+
+function localDate(d) {
+  if (0 <= d.y && d.y < 100) {
+    var date = new Date(-1, d.m, d.d, d.H, d.M, d.S, d.L);
+    date.setFullYear(d.y);
+    return date;
+  }
+  return new Date(d.y, d.m, d.d, d.H, d.M, d.S, d.L);
+}
+
+function utcDate(d) {
+  if (0 <= d.y && d.y < 100) {
+    var date = new Date(Date.UTC(-1, d.m, d.d, d.H, d.M, d.S, d.L));
+    date.setUTCFullYear(d.y);
+    return date;
+  }
+  return new Date(Date.UTC(d.y, d.m, d.d, d.H, d.M, d.S, d.L));
+}
+
+function newYear(y) {
+  return {y: y, m: 0, d: 1, H: 0, M: 0, S: 0, L: 0};
+}
+
+function formatLocale(locale) {
+  var locale_dateTime = locale.dateTime,
+      locale_date = locale.date,
+      locale_time = locale.time,
+      locale_periods = locale.periods,
+      locale_weekdays = locale.days,
+      locale_shortWeekdays = locale.shortDays,
+      locale_months = locale.months,
+      locale_shortMonths = locale.shortMonths;
+
+  var periodRe = formatRe(locale_periods),
+      periodLookup = formatLookup(locale_periods),
+      weekdayRe = formatRe(locale_weekdays),
+      weekdayLookup = formatLookup(locale_weekdays),
+      shortWeekdayRe = formatRe(locale_shortWeekdays),
+      shortWeekdayLookup = formatLookup(locale_shortWeekdays),
+      monthRe = formatRe(locale_months),
+      monthLookup = formatLookup(locale_months),
+      shortMonthRe = formatRe(locale_shortMonths),
+      shortMonthLookup = formatLookup(locale_shortMonths);
+
+  var formats = {
+    "a": formatShortWeekday,
+    "A": formatWeekday,
+    "b": formatShortMonth,
+    "B": formatMonth,
+    "c": null,
+    "d": formatDayOfMonth,
+    "e": formatDayOfMonth,
+    "f": formatMicroseconds,
+    "H": formatHour24,
+    "I": formatHour12,
+    "j": formatDayOfYear,
+    "L": formatMilliseconds,
+    "m": formatMonthNumber,
+    "M": formatMinutes,
+    "p": formatPeriod,
+    "Q": formatUnixTimestamp,
+    "s": formatUnixTimestampSeconds,
+    "S": formatSeconds,
+    "u": formatWeekdayNumberMonday,
+    "U": formatWeekNumberSunday,
+    "V": formatWeekNumberISO,
+    "w": formatWeekdayNumberSunday,
+    "W": formatWeekNumberMonday,
+    "x": null,
+    "X": null,
+    "y": formatYear,
+    "Y": formatFullYear,
+    "Z": formatZone,
+    "%": formatLiteralPercent
+  };
+
+  var utcFormats = {
+    "a": formatUTCShortWeekday,
+    "A": formatUTCWeekday,
+    "b": formatUTCShortMonth,
+    "B": formatUTCMonth,
+    "c": null,
+    "d": formatUTCDayOfMonth,
+    "e": formatUTCDayOfMonth,
+    "f": formatUTCMicroseconds,
+    "H": formatUTCHour24,
+    "I": formatUTCHour12,
+    "j": formatUTCDayOfYear,
+    "L": formatUTCMilliseconds,
+    "m": formatUTCMonthNumber,
+    "M": formatUTCMinutes,
+    "p": formatUTCPeriod,
+    "Q": formatUnixTimestamp,
+    "s": formatUnixTimestampSeconds,
+    "S": formatUTCSeconds,
+    "u": formatUTCWeekdayNumberMonday,
+    "U": formatUTCWeekNumberSunday,
+    "V": formatUTCWeekNumberISO,
+    "w": formatUTCWeekdayNumberSunday,
+    "W": formatUTCWeekNumberMonday,
+    "x": null,
+    "X": null,
+    "y": formatUTCYear,
+    "Y": formatUTCFullYear,
+    "Z": formatUTCZone,
+    "%": formatLiteralPercent
+  };
+
+  var parses = {
+    "a": parseShortWeekday,
+    "A": parseWeekday,
+    "b": parseShortMonth,
+    "B": parseMonth,
+    "c": parseLocaleDateTime,
+    "d": parseDayOfMonth,
+    "e": parseDayOfMonth,
+    "f": parseMicroseconds,
+    "H": parseHour24,
+    "I": parseHour24,
+    "j": parseDayOfYear,
+    "L": parseMilliseconds,
+    "m": parseMonthNumber,
+    "M": parseMinutes,
+    "p": parsePeriod,
+    "Q": parseUnixTimestamp,
+    "s": parseUnixTimestampSeconds,
+    "S": parseSeconds,
+    "u": parseWeekdayNumberMonday,
+    "U": parseWeekNumberSunday,
+    "V": parseWeekNumberISO,
+    "w": parseWeekdayNumberSunday,
+    "W": parseWeekNumberMonday,
+    "x": parseLocaleDate,
+    "X": parseLocaleTime,
+    "y": parseYear,
+    "Y": parseFullYear,
+    "Z": parseZone,
+    "%": parseLiteralPercent
+  };
+
+  // These recursive directive definitions must be deferred.
+  formats.x = newFormat(locale_date, formats);
+  formats.X = newFormat(locale_time, formats);
+  formats.c = newFormat(locale_dateTime, formats);
+  utcFormats.x = newFormat(locale_date, utcFormats);
+  utcFormats.X = newFormat(locale_time, utcFormats);
+  utcFormats.c = newFormat(locale_dateTime, utcFormats);
+
+  function newFormat(specifier, formats) {
+    return function(date) {
+      var string = [],
+          i = -1,
+          j = 0,
+          n = specifier.length,
+          c,
+          pad,
+          format;
+
+      if (!(date instanceof Date)) date = new Date(+date);
+
+      while (++i < n) {
+        if (specifier.charCodeAt(i) === 37) {
+          string.push(specifier.slice(j, i));
+          if ((pad = pads[c = specifier.charAt(++i)]) != null) c = specifier.charAt(++i);
+          else pad = c === "e" ? " " : "0";
+          if (format = formats[c]) c = format(date, pad);
+          string.push(c);
+          j = i + 1;
+        }
+      }
+
+      string.push(specifier.slice(j, i));
+      return string.join("");
+    };
+  }
+
+  function newParse(specifier, newDate) {
+    return function(string) {
+      var d = newYear(1900),
+          i = parseSpecifier(d, specifier, string += "", 0),
+          week, day;
+      if (i != string.length) return null;
+
+      // If a UNIX timestamp is specified, return it.
+      if ("Q" in d) return new Date(d.Q);
+
+      // The am-pm flag is 0 for AM, and 1 for PM.
+      if ("p" in d) d.H = d.H % 12 + d.p * 12;
+
+      // Convert day-of-week and week-of-year to day-of-year.
+      if ("V" in d) {
+        if (d.V < 1 || d.V > 53) return null;
+        if (!("w" in d)) d.w = 1;
+        if ("Z" in d) {
+          week = utcDate(newYear(d.y)), day = week.getUTCDay();
+          week = day > 4 || day === 0 ? __WEBPACK_IMPORTED_MODULE_0_d3_time__["p" /* utcMonday */].ceil(week) : Object(__WEBPACK_IMPORTED_MODULE_0_d3_time__["p" /* utcMonday */])(week);
+          week = __WEBPACK_IMPORTED_MODULE_0_d3_time__["l" /* utcDay */].offset(week, (d.V - 1) * 7);
+          d.y = week.getUTCFullYear();
+          d.m = week.getUTCMonth();
+          d.d = week.getUTCDate() + (d.w + 6) % 7;
+        } else {
+          week = newDate(newYear(d.y)), day = week.getDay();
+          week = day > 4 || day === 0 ? __WEBPACK_IMPORTED_MODULE_0_d3_time__["e" /* timeMonday */].ceil(week) : Object(__WEBPACK_IMPORTED_MODULE_0_d3_time__["e" /* timeMonday */])(week);
+          week = __WEBPACK_IMPORTED_MODULE_0_d3_time__["a" /* timeDay */].offset(week, (d.V - 1) * 7);
+          d.y = week.getFullYear();
+          d.m = week.getMonth();
+          d.d = week.getDate() + (d.w + 6) % 7;
+        }
+      } else if ("W" in d || "U" in d) {
+        if (!("w" in d)) d.w = "u" in d ? d.u % 7 : "W" in d ? 1 : 0;
+        day = "Z" in d ? utcDate(newYear(d.y)).getUTCDay() : newDate(newYear(d.y)).getDay();
+        d.m = 0;
+        d.d = "W" in d ? (d.w + 6) % 7 + d.W * 7 - (day + 5) % 7 : d.w + d.U * 7 - (day + 6) % 7;
+      }
+
+      // If a time zone is specified, all fields are interpreted as UTC and then
+      // offset according to the specified time zone.
+      if ("Z" in d) {
+        d.H += d.Z / 100 | 0;
+        d.M += d.Z % 100;
+        return utcDate(d);
+      }
+
+      // Otherwise, all fields are in local time.
+      return newDate(d);
+    };
+  }
+
+  function parseSpecifier(d, specifier, string, j) {
+    var i = 0,
+        n = specifier.length,
+        m = string.length,
+        c,
+        parse;
+
+    while (i < n) {
+      if (j >= m) return -1;
+      c = specifier.charCodeAt(i++);
+      if (c === 37) {
+        c = specifier.charAt(i++);
+        parse = parses[c in pads ? specifier.charAt(i++) : c];
+        if (!parse || ((j = parse(d, string, j)) < 0)) return -1;
+      } else if (c != string.charCodeAt(j++)) {
+        return -1;
+      }
+    }
+
+    return j;
+  }
+
+  function parsePeriod(d, string, i) {
+    var n = periodRe.exec(string.slice(i));
+    return n ? (d.p = periodLookup[n[0].toLowerCase()], i + n[0].length) : -1;
+  }
+
+  function parseShortWeekday(d, string, i) {
+    var n = shortWeekdayRe.exec(string.slice(i));
+    return n ? (d.w = shortWeekdayLookup[n[0].toLowerCase()], i + n[0].length) : -1;
+  }
+
+  function parseWeekday(d, string, i) {
+    var n = weekdayRe.exec(string.slice(i));
+    return n ? (d.w = weekdayLookup[n[0].toLowerCase()], i + n[0].length) : -1;
+  }
+
+  function parseShortMonth(d, string, i) {
+    var n = shortMonthRe.exec(string.slice(i));
+    return n ? (d.m = shortMonthLookup[n[0].toLowerCase()], i + n[0].length) : -1;
+  }
+
+  function parseMonth(d, string, i) {
+    var n = monthRe.exec(string.slice(i));
+    return n ? (d.m = monthLookup[n[0].toLowerCase()], i + n[0].length) : -1;
+  }
+
+  function parseLocaleDateTime(d, string, i) {
+    return parseSpecifier(d, locale_dateTime, string, i);
+  }
+
+  function parseLocaleDate(d, string, i) {
+    return parseSpecifier(d, locale_date, string, i);
+  }
+
+  function parseLocaleTime(d, string, i) {
+    return parseSpecifier(d, locale_time, string, i);
+  }
+
+  function formatShortWeekday(d) {
+    return locale_shortWeekdays[d.getDay()];
+  }
+
+  function formatWeekday(d) {
+    return locale_weekdays[d.getDay()];
+  }
+
+  function formatShortMonth(d) {
+    return locale_shortMonths[d.getMonth()];
+  }
+
+  function formatMonth(d) {
+    return locale_months[d.getMonth()];
+  }
+
+  function formatPeriod(d) {
+    return locale_periods[+(d.getHours() >= 12)];
+  }
+
+  function formatUTCShortWeekday(d) {
+    return locale_shortWeekdays[d.getUTCDay()];
+  }
+
+  function formatUTCWeekday(d) {
+    return locale_weekdays[d.getUTCDay()];
+  }
+
+  function formatUTCShortMonth(d) {
+    return locale_shortMonths[d.getUTCMonth()];
+  }
+
+  function formatUTCMonth(d) {
+    return locale_months[d.getUTCMonth()];
+  }
+
+  function formatUTCPeriod(d) {
+    return locale_periods[+(d.getUTCHours() >= 12)];
+  }
+
+  return {
+    format: function(specifier) {
+      var f = newFormat(specifier += "", formats);
+      f.toString = function() { return specifier; };
+      return f;
+    },
+    parse: function(specifier) {
+      var p = newParse(specifier += "", localDate);
+      p.toString = function() { return specifier; };
+      return p;
+    },
+    utcFormat: function(specifier) {
+      var f = newFormat(specifier += "", utcFormats);
+      f.toString = function() { return specifier; };
+      return f;
+    },
+    utcParse: function(specifier) {
+      var p = newParse(specifier, utcDate);
+      p.toString = function() { return specifier; };
+      return p;
+    }
+  };
+}
+
+var pads = {"-": "", "_": " ", "0": "0"},
+    numberRe = /^\s*\d+/, // note: ignores next directive
+    percentRe = /^%/,
+    requoteRe = /[\\^$*+?|[\]().{}]/g;
+
+function pad(value, fill, width) {
+  var sign = value < 0 ? "-" : "",
+      string = (sign ? -value : value) + "",
+      length = string.length;
+  return sign + (length < width ? new Array(width - length + 1).join(fill) + string : string);
+}
+
+function requote(s) {
+  return s.replace(requoteRe, "\\$&");
+}
+
+function formatRe(names) {
+  return new RegExp("^(?:" + names.map(requote).join("|") + ")", "i");
+}
+
+function formatLookup(names) {
+  var map = {}, i = -1, n = names.length;
+  while (++i < n) map[names[i].toLowerCase()] = i;
+  return map;
+}
+
+function parseWeekdayNumberSunday(d, string, i) {
+  var n = numberRe.exec(string.slice(i, i + 1));
+  return n ? (d.w = +n[0], i + n[0].length) : -1;
+}
+
+function parseWeekdayNumberMonday(d, string, i) {
+  var n = numberRe.exec(string.slice(i, i + 1));
+  return n ? (d.u = +n[0], i + n[0].length) : -1;
+}
+
+function parseWeekNumberSunday(d, string, i) {
+  var n = numberRe.exec(string.slice(i, i + 2));
+  return n ? (d.U = +n[0], i + n[0].length) : -1;
+}
+
+function parseWeekNumberISO(d, string, i) {
+  var n = numberRe.exec(string.slice(i, i + 2));
+  return n ? (d.V = +n[0], i + n[0].length) : -1;
+}
+
+function parseWeekNumberMonday(d, string, i) {
+  var n = numberRe.exec(string.slice(i, i + 2));
+  return n ? (d.W = +n[0], i + n[0].length) : -1;
+}
+
+function parseFullYear(d, string, i) {
+  var n = numberRe.exec(string.slice(i, i + 4));
+  return n ? (d.y = +n[0], i + n[0].length) : -1;
+}
+
+function parseYear(d, string, i) {
+  var n = numberRe.exec(string.slice(i, i + 2));
+  return n ? (d.y = +n[0] + (+n[0] > 68 ? 1900 : 2000), i + n[0].length) : -1;
+}
+
+function parseZone(d, string, i) {
+  var n = /^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(string.slice(i, i + 6));
+  return n ? (d.Z = n[1] ? 0 : -(n[2] + (n[3] || "00")), i + n[0].length) : -1;
+}
+
+function parseMonthNumber(d, string, i) {
+  var n = numberRe.exec(string.slice(i, i + 2));
+  return n ? (d.m = n[0] - 1, i + n[0].length) : -1;
+}
+
+function parseDayOfMonth(d, string, i) {
+  var n = numberRe.exec(string.slice(i, i + 2));
+  return n ? (d.d = +n[0], i + n[0].length) : -1;
+}
+
+function parseDayOfYear(d, string, i) {
+  var n = numberRe.exec(string.slice(i, i + 3));
+  return n ? (d.m = 0, d.d = +n[0], i + n[0].length) : -1;
+}
+
+function parseHour24(d, string, i) {
+  var n = numberRe.exec(string.slice(i, i + 2));
+  return n ? (d.H = +n[0], i + n[0].length) : -1;
+}
+
+function parseMinutes(d, string, i) {
+  var n = numberRe.exec(string.slice(i, i + 2));
+  return n ? (d.M = +n[0], i + n[0].length) : -1;
+}
+
+function parseSeconds(d, string, i) {
+  var n = numberRe.exec(string.slice(i, i + 2));
+  return n ? (d.S = +n[0], i + n[0].length) : -1;
+}
+
+function parseMilliseconds(d, string, i) {
+  var n = numberRe.exec(string.slice(i, i + 3));
+  return n ? (d.L = +n[0], i + n[0].length) : -1;
+}
+
+function parseMicroseconds(d, string, i) {
+  var n = numberRe.exec(string.slice(i, i + 6));
+  return n ? (d.L = Math.floor(n[0] / 1000), i + n[0].length) : -1;
+}
+
+function parseLiteralPercent(d, string, i) {
+  var n = percentRe.exec(string.slice(i, i + 1));
+  return n ? i + n[0].length : -1;
+}
+
+function parseUnixTimestamp(d, string, i) {
+  var n = numberRe.exec(string.slice(i));
+  return n ? (d.Q = +n[0], i + n[0].length) : -1;
+}
+
+function parseUnixTimestampSeconds(d, string, i) {
+  var n = numberRe.exec(string.slice(i));
+  return n ? (d.Q = (+n[0]) * 1000, i + n[0].length) : -1;
+}
+
+function formatDayOfMonth(d, p) {
+  return pad(d.getDate(), p, 2);
+}
+
+function formatHour24(d, p) {
+  return pad(d.getHours(), p, 2);
+}
+
+function formatHour12(d, p) {
+  return pad(d.getHours() % 12 || 12, p, 2);
+}
+
+function formatDayOfYear(d, p) {
+  return pad(1 + __WEBPACK_IMPORTED_MODULE_0_d3_time__["a" /* timeDay */].count(Object(__WEBPACK_IMPORTED_MODULE_0_d3_time__["k" /* timeYear */])(d), d), p, 3);
+}
+
+function formatMilliseconds(d, p) {
+  return pad(d.getMilliseconds(), p, 3);
+}
+
+function formatMicroseconds(d, p) {
+  return formatMilliseconds(d, p) + "000";
+}
+
+function formatMonthNumber(d, p) {
+  return pad(d.getMonth() + 1, p, 2);
+}
+
+function formatMinutes(d, p) {
+  return pad(d.getMinutes(), p, 2);
+}
+
+function formatSeconds(d, p) {
+  return pad(d.getSeconds(), p, 2);
+}
+
+function formatWeekdayNumberMonday(d) {
+  var day = d.getDay();
+  return day === 0 ? 7 : day;
+}
+
+function formatWeekNumberSunday(d, p) {
+  return pad(__WEBPACK_IMPORTED_MODULE_0_d3_time__["h" /* timeSunday */].count(Object(__WEBPACK_IMPORTED_MODULE_0_d3_time__["k" /* timeYear */])(d), d), p, 2);
+}
+
+function formatWeekNumberISO(d, p) {
+  var day = d.getDay();
+  d = (day >= 4 || day === 0) ? Object(__WEBPACK_IMPORTED_MODULE_0_d3_time__["i" /* timeThursday */])(d) : __WEBPACK_IMPORTED_MODULE_0_d3_time__["i" /* timeThursday */].ceil(d);
+  return pad(__WEBPACK_IMPORTED_MODULE_0_d3_time__["i" /* timeThursday */].count(Object(__WEBPACK_IMPORTED_MODULE_0_d3_time__["k" /* timeYear */])(d), d) + (Object(__WEBPACK_IMPORTED_MODULE_0_d3_time__["k" /* timeYear */])(d).getDay() === 4), p, 2);
+}
+
+function formatWeekdayNumberSunday(d) {
+  return d.getDay();
+}
+
+function formatWeekNumberMonday(d, p) {
+  return pad(__WEBPACK_IMPORTED_MODULE_0_d3_time__["e" /* timeMonday */].count(Object(__WEBPACK_IMPORTED_MODULE_0_d3_time__["k" /* timeYear */])(d), d), p, 2);
+}
+
+function formatYear(d, p) {
+  return pad(d.getFullYear() % 100, p, 2);
+}
+
+function formatFullYear(d, p) {
+  return pad(d.getFullYear() % 10000, p, 4);
+}
+
+function formatZone(d) {
+  var z = d.getTimezoneOffset();
+  return (z > 0 ? "-" : (z *= -1, "+"))
+      + pad(z / 60 | 0, "0", 2)
+      + pad(z % 60, "0", 2);
+}
+
+function formatUTCDayOfMonth(d, p) {
+  return pad(d.getUTCDate(), p, 2);
+}
+
+function formatUTCHour24(d, p) {
+  return pad(d.getUTCHours(), p, 2);
+}
+
+function formatUTCHour12(d, p) {
+  return pad(d.getUTCHours() % 12 || 12, p, 2);
+}
+
+function formatUTCDayOfYear(d, p) {
+  return pad(1 + __WEBPACK_IMPORTED_MODULE_0_d3_time__["l" /* utcDay */].count(Object(__WEBPACK_IMPORTED_MODULE_0_d3_time__["v" /* utcYear */])(d), d), p, 3);
+}
+
+function formatUTCMilliseconds(d, p) {
+  return pad(d.getUTCMilliseconds(), p, 3);
+}
+
+function formatUTCMicroseconds(d, p) {
+  return formatUTCMilliseconds(d, p) + "000";
+}
+
+function formatUTCMonthNumber(d, p) {
+  return pad(d.getUTCMonth() + 1, p, 2);
+}
+
+function formatUTCMinutes(d, p) {
+  return pad(d.getUTCMinutes(), p, 2);
+}
+
+function formatUTCSeconds(d, p) {
+  return pad(d.getUTCSeconds(), p, 2);
+}
+
+function formatUTCWeekdayNumberMonday(d) {
+  var dow = d.getUTCDay();
+  return dow === 0 ? 7 : dow;
+}
+
+function formatUTCWeekNumberSunday(d, p) {
+  return pad(__WEBPACK_IMPORTED_MODULE_0_d3_time__["s" /* utcSunday */].count(Object(__WEBPACK_IMPORTED_MODULE_0_d3_time__["v" /* utcYear */])(d), d), p, 2);
+}
+
+function formatUTCWeekNumberISO(d, p) {
+  var day = d.getUTCDay();
+  d = (day >= 4 || day === 0) ? Object(__WEBPACK_IMPORTED_MODULE_0_d3_time__["t" /* utcThursday */])(d) : __WEBPACK_IMPORTED_MODULE_0_d3_time__["t" /* utcThursday */].ceil(d);
+  return pad(__WEBPACK_IMPORTED_MODULE_0_d3_time__["t" /* utcThursday */].count(Object(__WEBPACK_IMPORTED_MODULE_0_d3_time__["v" /* utcYear */])(d), d) + (Object(__WEBPACK_IMPORTED_MODULE_0_d3_time__["v" /* utcYear */])(d).getUTCDay() === 4), p, 2);
+}
+
+function formatUTCWeekdayNumberSunday(d) {
+  return d.getUTCDay();
+}
+
+function formatUTCWeekNumberMonday(d, p) {
+  return pad(__WEBPACK_IMPORTED_MODULE_0_d3_time__["p" /* utcMonday */].count(Object(__WEBPACK_IMPORTED_MODULE_0_d3_time__["v" /* utcYear */])(d), d), p, 2);
+}
+
+function formatUTCYear(d, p) {
+  return pad(d.getUTCFullYear() % 100, p, 2);
+}
+
+function formatUTCFullYear(d, p) {
+  return pad(d.getUTCFullYear() % 10000, p, 4);
+}
+
+function formatUTCZone() {
+  return "+0000";
+}
+
+function formatLiteralPercent() {
+  return "%";
+}
+
+function formatUnixTimestamp(d) {
+  return +d;
+}
+
+function formatUnixTimestampSeconds(d) {
+  return Math.floor(+d / 1000);
+}
+
+
+/***/ }),
+/* 53 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return isoSpecifier; });
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__defaultLocale__ = __webpack_require__(22);
+
+
+var isoSpecifier = "%Y-%m-%dT%H:%M:%S.%LZ";
+
+function formatIsoNative(date) {
+  return date.toISOString();
+}
+
+var formatIso = Date.prototype.toISOString
+    ? formatIsoNative
+    : Object(__WEBPACK_IMPORTED_MODULE_0__defaultLocale__["b" /* utcFormat */])(isoSpecifier);
+
+/* unused harmony default export */ var _unused_webpack_default_export = (formatIso);
+
+
+/***/ }),
+/* 54 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+module.exports = {
+	"aliceblue": [240, 248, 255],
+	"antiquewhite": [250, 235, 215],
+	"aqua": [0, 255, 255],
+	"aquamarine": [127, 255, 212],
+	"azure": [240, 255, 255],
+	"beige": [245, 245, 220],
+	"bisque": [255, 228, 196],
+	"black": [0, 0, 0],
+	"blanchedalmond": [255, 235, 205],
+	"blue": [0, 0, 255],
+	"blueviolet": [138, 43, 226],
+	"brown": [165, 42, 42],
+	"burlywood": [222, 184, 135],
+	"cadetblue": [95, 158, 160],
+	"chartreuse": [127, 255, 0],
+	"chocolate": [210, 105, 30],
+	"coral": [255, 127, 80],
+	"cornflowerblue": [100, 149, 237],
+	"cornsilk": [255, 248, 220],
+	"crimson": [220, 20, 60],
+	"cyan": [0, 255, 255],
+	"darkblue": [0, 0, 139],
+	"darkcyan": [0, 139, 139],
+	"darkgoldenrod": [184, 134, 11],
+	"darkgray": [169, 169, 169],
+	"darkgreen": [0, 100, 0],
+	"darkgrey": [169, 169, 169],
+	"darkkhaki": [189, 183, 107],
+	"darkmagenta": [139, 0, 139],
+	"darkolivegreen": [85, 107, 47],
+	"darkorange": [255, 140, 0],
+	"darkorchid": [153, 50, 204],
+	"darkred": [139, 0, 0],
+	"darksalmon": [233, 150, 122],
+	"darkseagreen": [143, 188, 143],
+	"darkslateblue": [72, 61, 139],
+	"darkslategray": [47, 79, 79],
+	"darkslategrey": [47, 79, 79],
+	"darkturquoise": [0, 206, 209],
+	"darkviolet": [148, 0, 211],
+	"deeppink": [255, 20, 147],
+	"deepskyblue": [0, 191, 255],
+	"dimgray": [105, 105, 105],
+	"dimgrey": [105, 105, 105],
+	"dodgerblue": [30, 144, 255],
+	"firebrick": [178, 34, 34],
+	"floralwhite": [255, 250, 240],
+	"forestgreen": [34, 139, 34],
+	"fuchsia": [255, 0, 255],
+	"gainsboro": [220, 220, 220],
+	"ghostwhite": [248, 248, 255],
+	"gold": [255, 215, 0],
+	"goldenrod": [218, 165, 32],
+	"gray": [128, 128, 128],
+	"green": [0, 128, 0],
+	"greenyellow": [173, 255, 47],
+	"grey": [128, 128, 128],
+	"honeydew": [240, 255, 240],
+	"hotpink": [255, 105, 180],
+	"indianred": [205, 92, 92],
+	"indigo": [75, 0, 130],
+	"ivory": [255, 255, 240],
+	"khaki": [240, 230, 140],
+	"lavender": [230, 230, 250],
+	"lavenderblush": [255, 240, 245],
+	"lawngreen": [124, 252, 0],
+	"lemonchiffon": [255, 250, 205],
+	"lightblue": [173, 216, 230],
+	"lightcoral": [240, 128, 128],
+	"lightcyan": [224, 255, 255],
+	"lightgoldenrodyellow": [250, 250, 210],
+	"lightgray": [211, 211, 211],
+	"lightgreen": [144, 238, 144],
+	"lightgrey": [211, 211, 211],
+	"lightpink": [255, 182, 193],
+	"lightsalmon": [255, 160, 122],
+	"lightseagreen": [32, 178, 170],
+	"lightskyblue": [135, 206, 250],
+	"lightslategray": [119, 136, 153],
+	"lightslategrey": [119, 136, 153],
+	"lightsteelblue": [176, 196, 222],
+	"lightyellow": [255, 255, 224],
+	"lime": [0, 255, 0],
+	"limegreen": [50, 205, 50],
+	"linen": [250, 240, 230],
+	"magenta": [255, 0, 255],
+	"maroon": [128, 0, 0],
+	"mediumaquamarine": [102, 205, 170],
+	"mediumblue": [0, 0, 205],
+	"mediumorchid": [186, 85, 211],
+	"mediumpurple": [147, 112, 219],
+	"mediumseagreen": [60, 179, 113],
+	"mediumslateblue": [123, 104, 238],
+	"mediumspringgreen": [0, 250, 154],
+	"mediumturquoise": [72, 209, 204],
+	"mediumvioletred": [199, 21, 133],
+	"midnightblue": [25, 25, 112],
+	"mintcream": [245, 255, 250],
+	"mistyrose": [255, 228, 225],
+	"moccasin": [255, 228, 181],
+	"navajowhite": [255, 222, 173],
+	"navy": [0, 0, 128],
+	"oldlace": [253, 245, 230],
+	"olive": [128, 128, 0],
+	"olivedrab": [107, 142, 35],
+	"orange": [255, 165, 0],
+	"orangered": [255, 69, 0],
+	"orchid": [218, 112, 214],
+	"palegoldenrod": [238, 232, 170],
+	"palegreen": [152, 251, 152],
+	"paleturquoise": [175, 238, 238],
+	"palevioletred": [219, 112, 147],
+	"papayawhip": [255, 239, 213],
+	"peachpuff": [255, 218, 185],
+	"peru": [205, 133, 63],
+	"pink": [255, 192, 203],
+	"plum": [221, 160, 221],
+	"powderblue": [176, 224, 230],
+	"purple": [128, 0, 128],
+	"rebeccapurple": [102, 51, 153],
+	"red": [255, 0, 0],
+	"rosybrown": [188, 143, 143],
+	"royalblue": [65, 105, 225],
+	"saddlebrown": [139, 69, 19],
+	"salmon": [250, 128, 114],
+	"sandybrown": [244, 164, 96],
+	"seagreen": [46, 139, 87],
+	"seashell": [255, 245, 238],
+	"sienna": [160, 82, 45],
+	"silver": [192, 192, 192],
+	"skyblue": [135, 206, 235],
+	"slateblue": [106, 90, 205],
+	"slategray": [112, 128, 144],
+	"slategrey": [112, 128, 144],
+	"snow": [255, 250, 250],
+	"springgreen": [0, 255, 127],
+	"steelblue": [70, 130, 180],
+	"tan": [210, 180, 140],
+	"teal": [0, 128, 128],
+	"thistle": [216, 191, 216],
+	"tomato": [255, 99, 71],
+	"turquoise": [64, 224, 208],
+	"violet": [238, 130, 238],
+	"wheat": [245, 222, 179],
+	"white": [255, 255, 255],
+	"whitesmoke": [245, 245, 245],
+	"yellow": [255, 255, 0],
+	"yellowgreen": [154, 205, 50]
+};
+
+
+/***/ }),
+/* 55 */
+/***/ (function(module, exports, __webpack_require__) {
+
+/* MIT license */
+var cssKeywords = __webpack_require__(54);
+
+// NOTE: conversions should only return primitive values (i.e. arrays, or
+//       values that give correct `typeof` results).
+//       do not use box values types (i.e. Number(), String(), etc.)
+
+var reverseKeywords = {};
+for (var key in cssKeywords) {
+	if (cssKeywords.hasOwnProperty(key)) {
+		reverseKeywords[cssKeywords[key]] = key;
+	}
+}
+
+var convert = module.exports = {
+	rgb: {channels: 3, labels: 'rgb'},
+	hsl: {channels: 3, labels: 'hsl'},
+	hsv: {channels: 3, labels: 'hsv'},
+	hwb: {channels: 3, labels: 'hwb'},
+	cmyk: {channels: 4, labels: 'cmyk'},
+	xyz: {channels: 3, labels: 'xyz'},
+	lab: {channels: 3, labels: 'lab'},
+	lch: {channels: 3, labels: 'lch'},
+	hex: {channels: 1, labels: ['hex']},
+	keyword: {channels: 1, labels: ['keyword']},
+	ansi16: {channels: 1, labels: ['ansi16']},
+	ansi256: {channels: 1, labels: ['ansi256']},
+	hcg: {channels: 3, labels: ['h', 'c', 'g']},
+	apple: {channels: 3, labels: ['r16', 'g16', 'b16']},
+	gray: {channels: 1, labels: ['gray']}
+};
+
+// hide .channels and .labels properties
+for (var model in convert) {
+	if (convert.hasOwnProperty(model)) {
+		if (!('channels' in convert[model])) {
+			throw new Error('missing channels property: ' + model);
+		}
+
+		if (!('labels' in convert[model])) {
+			throw new Error('missing channel labels property: ' + model);
+		}
+
+		if (convert[model].labels.length !== convert[model].channels) {
+			throw new Error('channel and label counts mismatch: ' + model);
+		}
+
+		var channels = convert[model].channels;
+		var labels = convert[model].labels;
+		delete convert[model].channels;
+		delete convert[model].labels;
+		Object.defineProperty(convert[model], 'channels', {value: channels});
+		Object.defineProperty(convert[model], 'labels', {value: labels});
+	}
+}
+
+convert.rgb.hsl = function (rgb) {
+	var r = rgb[0] / 255;
+	var g = rgb[1] / 255;
+	var b = rgb[2] / 255;
+	var min = Math.min(r, g, b);
+	var max = Math.max(r, g, b);
+	var delta = max - min;
+	var h;
+	var s;
+	var l;
+
+	if (max === min) {
+		h = 0;
+	} else if (r === max) {
+		h = (g - b) / delta;
+	} else if (g === max) {
+		h = 2 + (b - r) / delta;
+	} else if (b === max) {
+		h = 4 + (r - g) / delta;
+	}
+
+	h = Math.min(h * 60, 360);
+
+	if (h < 0) {
+		h += 360;
+	}
+
+	l = (min + max) / 2;
+
+	if (max === min) {
+		s = 0;
+	} else if (l <= 0.5) {
+		s = delta / (max + min);
+	} else {
+		s = delta / (2 - max - min);
+	}
+
+	return [h, s * 100, l * 100];
+};
+
+convert.rgb.hsv = function (rgb) {
+	var r = rgb[0];
+	var g = rgb[1];
+	var b = rgb[2];
+	var min = Math.min(r, g, b);
+	var max = Math.max(r, g, b);
+	var delta = max - min;
+	var h;
+	var s;
+	var v;
+
+	if (max === 0) {
+		s = 0;
+	} else {
+		s = (delta / max * 1000) / 10;
+	}
+
+	if (max === min) {
+		h = 0;
+	} else if (r === max) {
+		h = (g - b) / delta;
+	} else if (g === max) {
+		h = 2 + (b - r) / delta;
+	} else if (b === max) {
+		h = 4 + (r - g) / delta;
+	}
+
+	h = Math.min(h * 60, 360);
+
+	if (h < 0) {
+		h += 360;
+	}
+
+	v = ((max / 255) * 1000) / 10;
+
+	return [h, s, v];
+};
+
+convert.rgb.hwb = function (rgb) {
+	var r = rgb[0];
+	var g = rgb[1];
+	var b = rgb[2];
+	var h = convert.rgb.hsl(rgb)[0];
+	var w = 1 / 255 * Math.min(r, Math.min(g, b));
+
+	b = 1 - 1 / 255 * Math.max(r, Math.max(g, b));
+
+	return [h, w * 100, b * 100];
+};
+
+convert.rgb.cmyk = function (rgb) {
+	var r = rgb[0] / 255;
+	var g = rgb[1] / 255;
+	var b = rgb[2] / 255;
+	var c;
+	var m;
+	var y;
+	var k;
+
+	k = Math.min(1 - r, 1 - g, 1 - b);
+	c = (1 - r - k) / (1 - k) || 0;
+	m = (1 - g - k) / (1 - k) || 0;
+	y = (1 - b - k) / (1 - k) || 0;
+
+	return [c * 100, m * 100, y * 100, k * 100];
+};
+
+/**
+ * See https://en.m.wikipedia.org/wiki/Euclidean_distance#Squared_Euclidean_distance
+ * */
+function comparativeDistance(x, y) {
+	return (
+		Math.pow(x[0] - y[0], 2) +
+		Math.pow(x[1] - y[1], 2) +
+		Math.pow(x[2] - y[2], 2)
+	);
+}
+
+convert.rgb.keyword = function (rgb) {
+	var reversed = reverseKeywords[rgb];
+	if (reversed) {
+		return reversed;
+	}
+
+	var currentClosestDistance = Infinity;
+	var currentClosestKeyword;
+
+	for (var keyword in cssKeywords) {
+		if (cssKeywords.hasOwnProperty(keyword)) {
+			var value = cssKeywords[keyword];
+
+			// Compute comparative distance
+			var distance = comparativeDistance(rgb, value);
+
+			// Check if its less, if so set as closest
+			if (distance < currentClosestDistance) {
+				currentClosestDistance = distance;
+				currentClosestKeyword = keyword;
+			}
+		}
+	}
+
+	return currentClosestKeyword;
+};
+
+convert.keyword.rgb = function (keyword) {
+	return cssKeywords[keyword];
+};
+
+convert.rgb.xyz = function (rgb) {
+	var r = rgb[0] / 255;
+	var g = rgb[1] / 255;
+	var b = rgb[2] / 255;
+
+	// assume sRGB
+	r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92);
+	g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92);
+	b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92);
+
+	var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805);
+	var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722);
+	var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505);
+
+	return [x * 100, y * 100, z * 100];
+};
+
+convert.rgb.lab = function (rgb) {
+	var xyz = convert.rgb.xyz(rgb);
+	var x = xyz[0];
+	var y = xyz[1];
+	var z = xyz[2];
+	var l;
+	var a;
+	var b;
+
+	x /= 95.047;
+	y /= 100;
+	z /= 108.883;
+
+	x = x > 0.008856 ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116);
+	y = y > 0.008856 ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116);
+	z = z > 0.008856 ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116);
+
+	l = (116 * y) - 16;
+	a = 500 * (x - y);
+	b = 200 * (y - z);
+
+	return [l, a, b];
+};
+
+convert.hsl.rgb = function (hsl) {
+	var h = hsl[0] / 360;
+	var s = hsl[1] / 100;
+	var l = hsl[2] / 100;
+	var t1;
+	var t2;
+	var t3;
+	var rgb;
+	var val;
+
+	if (s === 0) {
+		val = l * 255;
+		return [val, val, val];
+	}
+
+	if (l < 0.5) {
+		t2 = l * (1 + s);
+	} else {
+		t2 = l + s - l * s;
+	}
+
+	t1 = 2 * l - t2;
+
+	rgb = [0, 0, 0];
+	for (var i = 0; i < 3; i++) {
+		t3 = h + 1 / 3 * -(i - 1);
+		if (t3 < 0) {
+			t3++;
+		}
+		if (t3 > 1) {
+			t3--;
+		}
+
+		if (6 * t3 < 1) {
+			val = t1 + (t2 - t1) * 6 * t3;
+		} else if (2 * t3 < 1) {
+			val = t2;
+		} else if (3 * t3 < 2) {
+			val = t1 + (t2 - t1) * (2 / 3 - t3) * 6;
+		} else {
+			val = t1;
+		}
+
+		rgb[i] = val * 255;
+	}
+
+	return rgb;
+};
+
+convert.hsl.hsv = function (hsl) {
+	var h = hsl[0];
+	var s = hsl[1] / 100;
+	var l = hsl[2] / 100;
+	var smin = s;
+	var lmin = Math.max(l, 0.01);
+	var sv;
+	var v;
+
+	l *= 2;
+	s *= (l <= 1) ? l : 2 - l;
+	smin *= lmin <= 1 ? lmin : 2 - lmin;
+	v = (l + s) / 2;
+	sv = l === 0 ? (2 * smin) / (lmin + smin) : (2 * s) / (l + s);
+
+	return [h, sv * 100, v * 100];
+};
+
+convert.hsv.rgb = function (hsv) {
+	var h = hsv[0] / 60;
+	var s = hsv[1] / 100;
+	var v = hsv[2] / 100;
+	var hi = Math.floor(h) % 6;
+
+	var f = h - Math.floor(h);
+	var p = 255 * v * (1 - s);
+	var q = 255 * v * (1 - (s * f));
+	var t = 255 * v * (1 - (s * (1 - f)));
+	v *= 255;
+
+	switch (hi) {
+		case 0:
+			return [v, t, p];
+		case 1:
+			return [q, v, p];
+		case 2:
+			return [p, v, t];
+		case 3:
+			return [p, q, v];
+		case 4:
+			return [t, p, v];
+		case 5:
+			return [v, p, q];
+	}
+};
+
+convert.hsv.hsl = function (hsv) {
+	var h = hsv[0];
+	var s = hsv[1] / 100;
+	var v = hsv[2] / 100;
+	var vmin = Math.max(v, 0.01);
+	var lmin;
+	var sl;
+	var l;
+
+	l = (2 - s) * v;
+	lmin = (2 - s) * vmin;
+	sl = s * vmin;
+	sl /= (lmin <= 1) ? lmin : 2 - lmin;
+	sl = sl || 0;
+	l /= 2;
+
+	return [h, sl * 100, l * 100];
+};
+
+// http://dev.w3.org/csswg/css-color/#hwb-to-rgb
+convert.hwb.rgb = function (hwb) {
+	var h = hwb[0] / 360;
+	var wh = hwb[1] / 100;
+	var bl = hwb[2] / 100;
+	var ratio = wh + bl;
+	var i;
+	var v;
+	var f;
+	var n;
+
+	// wh + bl cant be > 1
+	if (ratio > 1) {
+		wh /= ratio;
+		bl /= ratio;
+	}
+
+	i = Math.floor(6 * h);
+	v = 1 - bl;
+	f = 6 * h - i;
+
+	if ((i & 0x01) !== 0) {
+		f = 1 - f;
+	}
+
+	n = wh + f * (v - wh); // linear interpolation
+
+	var r;
+	var g;
+	var b;
+	switch (i) {
+		default:
+		case 6:
+		case 0: r = v; g = n; b = wh; break;
+		case 1: r = n; g = v; b = wh; break;
+		case 2: r = wh; g = v; b = n; break;
+		case 3: r = wh; g = n; b = v; break;
+		case 4: r = n; g = wh; b = v; break;
+		case 5: r = v; g = wh; b = n; break;
+	}
+
+	return [r * 255, g * 255, b * 255];
+};
+
+convert.cmyk.rgb = function (cmyk) {
+	var c = cmyk[0] / 100;
+	var m = cmyk[1] / 100;
+	var y = cmyk[2] / 100;
+	var k = cmyk[3] / 100;
+	var r;
+	var g;
+	var b;
+
+	r = 1 - Math.min(1, c * (1 - k) + k);
+	g = 1 - Math.min(1, m * (1 - k) + k);
+	b = 1 - Math.min(1, y * (1 - k) + k);
+
+	return [r * 255, g * 255, b * 255];
+};
+
+convert.xyz.rgb = function (xyz) {
+	var x = xyz[0] / 100;
+	var y = xyz[1] / 100;
+	var z = xyz[2] / 100;
+	var r;
+	var g;
+	var b;
+
+	r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986);
+	g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415);
+	b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570);
+
+	// assume sRGB
+	r = r > 0.0031308
+		? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055)
+		: r * 12.92;
+
+	g = g > 0.0031308
+		? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055)
+		: g * 12.92;
+
+	b = b > 0.0031308
+		? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055)
+		: b * 12.92;
+
+	r = Math.min(Math.max(0, r), 1);
+	g = Math.min(Math.max(0, g), 1);
+	b = Math.min(Math.max(0, b), 1);
+
+	return [r * 255, g * 255, b * 255];
+};
+
+convert.xyz.lab = function (xyz) {
+	var x = xyz[0];
+	var y = xyz[1];
+	var z = xyz[2];
+	var l;
+	var a;
+	var b;
+
+	x /= 95.047;
+	y /= 100;
+	z /= 108.883;
+
+	x = x > 0.008856 ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116);
+	y = y > 0.008856 ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116);
+	z = z > 0.008856 ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116);
+
+	l = (116 * y) - 16;
+	a = 500 * (x - y);
+	b = 200 * (y - z);
+
+	return [l, a, b];
+};
+
+convert.lab.xyz = function (lab) {
+	var l = lab[0];
+	var a = lab[1];
+	var b = lab[2];
+	var x;
+	var y;
+	var z;
+
+	y = (l + 16) / 116;
+	x = a / 500 + y;
+	z = y - b / 200;
+
+	var y2 = Math.pow(y, 3);
+	var x2 = Math.pow(x, 3);
+	var z2 = Math.pow(z, 3);
+	y = y2 > 0.008856 ? y2 : (y - 16 / 116) / 7.787;
+	x = x2 > 0.008856 ? x2 : (x - 16 / 116) / 7.787;
+	z = z2 > 0.008856 ? z2 : (z - 16 / 116) / 7.787;
+
+	x *= 95.047;
+	y *= 100;
+	z *= 108.883;
+
+	return [x, y, z];
+};
+
+convert.lab.lch = function (lab) {
+	var l = lab[0];
+	var a = lab[1];
+	var b = lab[2];
+	var hr;
+	var h;
+	var c;
+
+	hr = Math.atan2(b, a);
+	h = hr * 360 / 2 / Math.PI;
+
+	if (h < 0) {
+		h += 360;
+	}
+
+	c = Math.sqrt(a * a + b * b);
+
+	return [l, c, h];
+};
+
+convert.lch.lab = function (lch) {
+	var l = lch[0];
+	var c = lch[1];
+	var h = lch[2];
+	var a;
+	var b;
+	var hr;
+
+	hr = h / 360 * 2 * Math.PI;
+	a = c * Math.cos(hr);
+	b = c * Math.sin(hr);
+
+	return [l, a, b];
+};
+
+convert.rgb.ansi16 = function (args) {
+	var r = args[0];
+	var g = args[1];
+	var b = args[2];
+	var value = 1 in arguments ? arguments[1] : convert.rgb.hsv(args)[2]; // hsv -> ansi16 optimization
+
+	value = Math.round(value / 50);
+
+	if (value === 0) {
+		return 30;
+	}
+
+	var ansi = 30
+		+ ((Math.round(b / 255) << 2)
+		| (Math.round(g / 255) << 1)
+		| Math.round(r / 255));
+
+	if (value === 2) {
+		ansi += 60;
+	}
+
+	return ansi;
+};
+
+convert.hsv.ansi16 = function (args) {
+	// optimization here; we already know the value and don't need to get
+	// it converted for us.
+	return convert.rgb.ansi16(convert.hsv.rgb(args), args[2]);
+};
+
+convert.rgb.ansi256 = function (args) {
+	var r = args[0];
+	var g = args[1];
+	var b = args[2];
+
+	// we use the extended greyscale palette here, with the exception of
+	// black and white. normal palette only has 4 greyscale shades.
+	if (r === g && g === b) {
+		if (r < 8) {
+			return 16;
+		}
+
+		if (r > 248) {
+			return 231;
+		}
+
+		return Math.round(((r - 8) / 247) * 24) + 232;
+	}
+
+	var ansi = 16
+		+ (36 * Math.round(r / 255 * 5))
+		+ (6 * Math.round(g / 255 * 5))
+		+ Math.round(b / 255 * 5);
+
+	return ansi;
+};
+
+convert.ansi16.rgb = function (args) {
+	var color = args % 10;
+
+	// handle greyscale
+	if (color === 0 || color === 7) {
+		if (args > 50) {
+			color += 3.5;
+		}
+
+		color = color / 10.5 * 255;
+
+		return [color, color, color];
+	}
+
+	var mult = (~~(args > 50) + 1) * 0.5;
+	var r = ((color & 1) * mult) * 255;
+	var g = (((color >> 1) & 1) * mult) * 255;
+	var b = (((color >> 2) & 1) * mult) * 255;
+
+	return [r, g, b];
+};
+
+convert.ansi256.rgb = function (args) {
+	// handle greyscale
+	if (args >= 232) {
+		var c = (args - 232) * 10 + 8;
+		return [c, c, c];
+	}
+
+	args -= 16;
+
+	var rem;
+	var r = Math.floor(args / 36) / 5 * 255;
+	var g = Math.floor((rem = args % 36) / 6) / 5 * 255;
+	var b = (rem % 6) / 5 * 255;
+
+	return [r, g, b];
+};
+
+convert.rgb.hex = function (args) {
+	var integer = ((Math.round(args[0]) & 0xFF) << 16)
+		+ ((Math.round(args[1]) & 0xFF) << 8)
+		+ (Math.round(args[2]) & 0xFF);
+
+	var string = integer.toString(16).toUpperCase();
+	return '000000'.substring(string.length) + string;
+};
+
+convert.hex.rgb = function (args) {
+	var match = args.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i);
+	if (!match) {
+		return [0, 0, 0];
+	}
+
+	var colorString = match[0];
+
+	if (match[0].length === 3) {
+		colorString = colorString.split('').map(function (char) {
+			return char + char;
+		}).join('');
+	}
+
+	var integer = parseInt(colorString, 16);
+	var r = (integer >> 16) & 0xFF;
+	var g = (integer >> 8) & 0xFF;
+	var b = integer & 0xFF;
+
+	return [r, g, b];
+};
+
+convert.rgb.hcg = function (rgb) {
+	var r = rgb[0] / 255;
+	var g = rgb[1] / 255;
+	var b = rgb[2] / 255;
+	var max = Math.max(Math.max(r, g), b);
+	var min = Math.min(Math.min(r, g), b);
+	var chroma = (max - min);
+	var grayscale;
+	var hue;
+
+	if (chroma < 1) {
+		grayscale = min / (1 - chroma);
+	} else {
+		grayscale = 0;
+	}
+
+	if (chroma <= 0) {
+		hue = 0;
+	} else
+	if (max === r) {
+		hue = ((g - b) / chroma) % 6;
+	} else
+	if (max === g) {
+		hue = 2 + (b - r) / chroma;
+	} else {
+		hue = 4 + (r - g) / chroma + 4;
+	}
+
+	hue /= 6;
+	hue %= 1;
+
+	return [hue * 360, chroma * 100, grayscale * 100];
+};
+
+convert.hsl.hcg = function (hsl) {
+	var s = hsl[1] / 100;
+	var l = hsl[2] / 100;
+	var c = 1;
+	var f = 0;
+
+	if (l < 0.5) {
+		c = 2.0 * s * l;
+	} else {
+		c = 2.0 * s * (1.0 - l);
+	}
+
+	if (c < 1.0) {
+		f = (l - 0.5 * c) / (1.0 - c);
+	}
+
+	return [hsl[0], c * 100, f * 100];
+};
+
+convert.hsv.hcg = function (hsv) {
+	var s = hsv[1] / 100;
+	var v = hsv[2] / 100;
+
+	var c = s * v;
+	var f = 0;
+
+	if (c < 1.0) {
+		f = (v - c) / (1 - c);
+	}
+
+	return [hsv[0], c * 100, f * 100];
+};
+
+convert.hcg.rgb = function (hcg) {
+	var h = hcg[0] / 360;
+	var c = hcg[1] / 100;
+	var g = hcg[2] / 100;
+
+	if (c === 0.0) {
+		return [g * 255, g * 255, g * 255];
+	}
+
+	var pure = [0, 0, 0];
+	var hi = (h % 1) * 6;
+	var v = hi % 1;
+	var w = 1 - v;
+	var mg = 0;
+
+	switch (Math.floor(hi)) {
+		case 0:
+			pure[0] = 1; pure[1] = v; pure[2] = 0; break;
+		case 1:
+			pure[0] = w; pure[1] = 1; pure[2] = 0; break;
+		case 2:
+			pure[0] = 0; pure[1] = 1; pure[2] = v; break;
+		case 3:
+			pure[0] = 0; pure[1] = w; pure[2] = 1; break;
+		case 4:
+			pure[0] = v; pure[1] = 0; pure[2] = 1; break;
+		default:
+			pure[0] = 1; pure[1] = 0; pure[2] = w;
+	}
+
+	mg = (1.0 - c) * g;
+
+	return [
+		(c * pure[0] + mg) * 255,
+		(c * pure[1] + mg) * 255,
+		(c * pure[2] + mg) * 255
+	];
+};
+
+convert.hcg.hsv = function (hcg) {
+	var c = hcg[1] / 100;
+	var g = hcg[2] / 100;
+
+	var v = c + g * (1.0 - c);
+	var f = 0;
+
+	if (v > 0.0) {
+		f = c / v;
+	}
+
+	return [hcg[0], f * 100, v * 100];
+};
+
+convert.hcg.hsl = function (hcg) {
+	var c = hcg[1] / 100;
+	var g = hcg[2] / 100;
+
+	var l = g * (1.0 - c) + 0.5 * c;
+	var s = 0;
+
+	if (l > 0.0 && l < 0.5) {
+		s = c / (2 * l);
+	} else
+	if (l >= 0.5 && l < 1.0) {
+		s = c / (2 * (1 - l));
+	}
+
+	return [hcg[0], s * 100, l * 100];
+};
+
+convert.hcg.hwb = function (hcg) {
+	var c = hcg[1] / 100;
+	var g = hcg[2] / 100;
+	var v = c + g * (1.0 - c);
+	return [hcg[0], (v - c) * 100, (1 - v) * 100];
+};
+
+convert.hwb.hcg = function (hwb) {
+	var w = hwb[1] / 100;
+	var b = hwb[2] / 100;
+	var v = 1 - b;
+	var c = v - w;
+	var g = 0;
+
+	if (c < 1) {
+		g = (v - c) / (1 - c);
+	}
+
+	return [hwb[0], c * 100, g * 100];
+};
+
+convert.apple.rgb = function (apple) {
+	return [(apple[0] / 65535) * 255, (apple[1] / 65535) * 255, (apple[2] / 65535) * 255];
+};
+
+convert.rgb.apple = function (rgb) {
+	return [(rgb[0] / 255) * 65535, (rgb[1] / 255) * 65535, (rgb[2] / 255) * 65535];
+};
+
+convert.gray.rgb = function (args) {
+	return [args[0] / 100 * 255, args[0] / 100 * 255, args[0] / 100 * 255];
+};
+
+convert.gray.hsl = convert.gray.hsv = function (args) {
+	return [0, 0, args[0]];
+};
+
+convert.gray.hwb = function (gray) {
+	return [0, 100, gray[0]];
+};
+
+convert.gray.cmyk = function (gray) {
+	return [0, 0, 0, gray[0]];
+};
+
+convert.gray.lab = function (gray) {
+	return [gray[0], 0, 0];
+};
+
+convert.gray.hex = function (gray) {
+	var val = Math.round(gray[0] / 100 * 255) & 0xFF;
+	var integer = (val << 16) + (val << 8) + val;
+
+	var string = integer.toString(16).toUpperCase();
+	return '000000'.substring(string.length) + string;
+};
+
+convert.rgb.gray = function (rgb) {
+	var val = (rgb[0] + rgb[1] + rgb[2]) / 3;
+	return [val / 255 * 100];
+};
+
+
+/***/ }),
+/* 56 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+/* WEBPACK VAR INJECTION */(function(global) {
+
+var _table_format = __webpack_require__(58);
+
+global.console = {
+  log: print,
+  warn: print,
+  error: print
+}; // This runs in the Nashorn JavaScript engine and there are some limitations
+//
+// 1. This is not currently automatically built with the rest of the application, please run `yarn build-shared` after modifying
+// 2. Avoid including unecessary libraries as the JS engine takes a long time to parse and execute them
+// 3. Related to #2, we aren't currently including `babel-polyfill` so don't use features that require it, e.x. iterables / for-of
+
+global.makeCellBackgroundGetter = function (rowsJavaList, colsJSON, settingsJSON) {
+  var rows = rowsJavaList;
+  var cols = JSON.parse(colsJSON);
+  var settings = JSON.parse(settingsJSON);
+  try {
+    var getter = (0, _table_format.makeCellBackgroundGetter)(rows, cols, settings);
+    return function (value, rowIndex, colName) {
+      var color = getter(value, rowIndex, colName);
+      if (color) {
+        return roundColor(color);
+      }
+      return null;
+    };
+  } catch (e) {
+    print("ERROR", e);
+    return function () {
+      return null;
+    };
+  }
+};
+
+// HACK: d3 may return rgb values with decimals but the rendering engine used for pulses doesn't support that
+function roundColor(color) {
+  return color.replace(/rgba\((\d+(?:\.\d+)),\s*(\d+(?:\.\d+)),\s*(\d+(?:\.\d+)),\s*(\d+\.\d+)\)/, function (_, r, g, b, a) {
+    return "rgba(" + Math.round(r) + "," + Math.round(g) + "," + Math.round(b) + "," + a + ")";
+  });
+}
+/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(57)))
+
+/***/ }),
+/* 57 */
+/***/ (function(module, exports) {
+
+var g;
+
+// This works in non-strict mode
+g = (function() {
+	return this;
+})();
+
+try {
+	// This works if eval is allowed (see CSP)
+	g = g || Function("return this")() || (1,eval)("this");
+} catch(e) {
+	// This works if the window reference is available
+	if(typeof window === "object")
+		g = window;
+}
+
+// g can still be undefined, but nothing to do about it...
+// We return undefined, instead of nothing here, so it's
+// easier to handle this case. if(!global) { ...}
+
+module.exports = g;
+
+
+/***/ }),
+/* 58 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.makeCellBackgroundGetter = makeCellBackgroundGetter;
+
+var _colors = __webpack_require__(59);
+
+function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
+
+// NOTE: this file is used on the frontend and backend and there are some
+// limitations. See frontend/src/metabase-shared/color_selector for details
+
+var CELL_ALPHA = 0.65;
+var ROW_ALPHA = 0.2;
+var GRADIENT_ALPHA = 0.75;
+// for simplicity wheb typing assume all values are numbers, since you can only pick numeric columns
+function makeCellBackgroundGetter(rows, cols, settings) {
+  var formats = settings["table.column_formatting"];
+  var pivot = settings["table.pivot"];
+  var formatters = {};
+  var rowFormatters = [];
+  var colIndexes = getColumnIndexesByName(cols);
+  try {
+    var columnExtents = computeColumnExtents(formats, rows, colIndexes);
+    formatters = compileFormatters(formats, columnExtents);
+    rowFormatters = compileRowFormatters(formats, columnExtents);
+  } catch (e) {
+    console.error(e);
+  }
+  if (Object.keys(formatters).length === 0 && rowFormatters.length === 0) {
+    return function () {
+      return null;
+    };
+  } else {
+    return function (value, rowIndex, colName) {
+      if (formatters[colName]) {
+        // const value = rows[rowIndex][colIndexes[colName]];
+        for (var i = 0; i < formatters[colName].length; i++) {
+          var formatter = formatters[colName][i];
+          var _color = formatter(value);
+          if (_color != null) {
+            return _color;
+          }
+        }
+      }
+      // don't highlight row for pivoted tables
+      if (!pivot) {
+        for (var _i = 0; _i < rowFormatters.length; _i++) {
+          var rowFormatter = rowFormatters[_i];
+          var _color2 = rowFormatter(rows[rowIndex], colIndexes);
+          if (_color2 != null) {
+            return _color2;
+          }
+        }
+      }
+    };
+  }
+}
+
+function getColumnIndexesByName(cols) {
+  var colIndexes = {};
+  for (var i = 0; i < cols.length; i++) {
+    colIndexes[cols[i].name] = i;
+  }
+  return colIndexes;
+}
+
+function compileFormatter(format, columnName, columnExtents) {
+  var isRowFormatter = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
+
+  if (format.type === "single") {
+    var _operator = format.operator,
+        _value = format.value,
+        _color3 = format.color;
+
+    if (isRowFormatter) {
+      _color3 = (0, _colors.alpha)(_color3, ROW_ALPHA);
+    } else {
+      _color3 = (0, _colors.alpha)(_color3, CELL_ALPHA);
+    }
+    switch (_operator) {
+      case "<":
+        return function (v) {
+          return v < _value ? _color3 : null;
+        };
+      case "<=":
+        return function (v) {
+          return v <= _value ? _color3 : null;
+        };
+      case ">=":
+        return function (v) {
+          return v >= _value ? _color3 : null;
+        };
+      case ">":
+        return function (v) {
+          return v > _value ? _color3 : null;
+        };
+      case "=":
+        return function (v) {
+          return v === _value ? _color3 : null;
+        };
+      case "!=":
+        return function (v) {
+          return v !== _value ? _color3 : null;
+        };
+    }
+  } else if (format.type === "range") {
+    var columnMin = function columnMin(name) {
+      return (
+        // $FlowFixMe
+        columnExtents && columnExtents[name] && columnExtents[name][0]
+      );
+    };
+    var columnMax = function columnMax(name) {
+      return (
+        // $FlowFixMe
+        columnExtents && columnExtents[name] && columnExtents[name][1]
+      );
+    };
+
+    var min = format.min_type === "custom" ? format.min_value : format.min_type === "all" ? // $FlowFixMe
+    Math.min.apply(Math, _toConsumableArray(format.columns.map(columnMin))) : columnMin(columnName);
+    var max = format.max_type === "custom" ? format.max_value : format.max_type === "all" ? // $FlowFixMe
+    Math.max.apply(Math, _toConsumableArray(format.columns.map(columnMax))) : columnMax(columnName);
+
+    if (typeof max !== "number" || typeof min !== "number") {
+      console.warn("Invalid range min/max", min, max);
+      return function () {
+        return null;
+      };
+    }
+
+    return (0, _colors.getColorScale)([min, max], format.colors.map(function (c) {
+      return (0, _colors.alpha)(c, GRADIENT_ALPHA);
+    })).clamp(true);
+  } else {
+    console.warn("Unknown format type", format.type);
+    return function () {
+      return null;
+    };
+  }
+}
+
+// NOTE: implement `extent` like this rather than using d3.extent since rows may
+// be a Java `List` rather than a JavaScript Array when used in Pulse formatting
+function extent(rows, colIndex) {
+  var min = Infinity;
+  var max = -Infinity;
+  var length = rows.length;
+  for (var i = 0; i < length; i++) {
+    var _value2 = rows[i][colIndex];
+    if (_value2 < min) {
+      min = _value2;
+    }
+    if (_value2 > max) {
+      max = _value2;
+    }
+  }
+  return [min, max];
+}
+
+function computeColumnExtents(formats, rows, colIndexes) {
+  var columnExtents = {};
+  formats.forEach(function (format) {
+    format.columns.forEach(function (columnName) {
+      if (!columnExtents[columnName]) {
+        var colIndex = colIndexes[columnName];
+        columnExtents[columnName] = extent(rows, colIndex);
+      }
+    });
+  });
+  return columnExtents;
+}
+
+function compileFormatters(formats, columnExtents) {
+  var formatters = {};
+  formats.forEach(function (format) {
+    format.columns.forEach(function (columnName) {
+      formatters[columnName] = formatters[columnName] || [];
+      formatters[columnName].push(compileFormatter(format, columnName, columnExtents, false));
+    });
+  });
+  return formatters;
+}
+
+function compileRowFormatters(formats) {
+  var rowFormatters = [];
+  formats.filter(function (format) {
+    return format.type === "single" && format.highlight_row;
+  }).forEach(function (format) {
+    var formatter = compileFormatter(format, null, null, true);
+    if (formatter) {
+      format.columns.forEach(function (columnName) {
+        rowFormatters.push(function (row, colIndexes) {
+          return formatter(row[colIndexes[columnName]]);
+        });
+      });
+    }
+  });
+  return rowFormatters;
+}
+
+/***/ }),
+/* 59 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.darken = exports.alpha = exports.getColorScale = exports.getRandomColor = exports.desaturated = exports.saturated = exports.normal = exports.harmony = undefined;
+
+var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
+
+exports.syncColors = syncColors;
+
+var _d2 = __webpack_require__(60);
+
+var _d3 = _interopRequireDefault(_d2);
+
+var _color = __webpack_require__(133);
+
+var _color2 = _interopRequireDefault(_color);
+
+var _colorHarmony = __webpack_require__(139);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+// NOTE: DO NOT ADD COLORS WITHOUT EXTREMELY GOOD REASON AND DESIGN REVIEW
+// NOTE: KEEP SYNCRONIZED WITH COLORS.CSS
+/* eslint-disable no-color-literals */
+var colors = {
+  brand: "#509EE3",
+  accent1: "#9CC177",
+  accent2: "#A989C5",
+  accent3: "#EF8C8C",
+  accent4: "#F9D45C",
+  accent5: "#F1B556",
+  accent6: "#A6E7F3",
+  accent7: "#7172AD",
+  white: "#FFFFFF",
+  black: "#2E353B",
+  success: "#84BB4C",
+  error: "#ED6E6E",
+  warning: "#F9CF48",
+  "text-dark": "#2E353B",
+  "text-medium": "#74838F",
+  "text-light": "#C7CFD4",
+  "text-white": "#FFFFFF",
+  "bg-black": "#2E353B",
+  "bg-dark": "#93A1AB",
+  "bg-medium": "#EDF2F5",
+  "bg-light": "#F9FBFC",
+  "bg-white": "#FFFFFF",
+  shadow: "rgba(0,0,0,0.08)",
+  border: "#D7DBDE"
+};
+/* eslint-enable no-color-literals */
+exports.default = colors;
+var harmony = exports.harmony = [];
+
+// DEPRECATED: we should remove these and use `colors` directly
+// compute satured/desaturated variants using "color" lib if absolutely required
+var normal = exports.normal = {};
+var saturated = exports.saturated = {};
+var desaturated = exports.desaturated = {};
+
+// make sure to do the initial "sync"
+syncColors();
+
+function syncColors() {
+  syncHarmony();
+  syncDeprecatedColorFamilies();
+}
+
+function syncHarmony() {
+  var harmonizer = new _colorHarmony.Harmonizer();
+  var initialColors = [colors["brand"], colors["accent1"], colors["accent2"], colors["accent3"], colors["accent4"], colors["accent5"], colors["accent6"], colors["accent7"]];
+  harmony.splice(0, harmony.length);
+  // round 0 includes brand and all accents
+  harmony.push.apply(harmony, initialColors);
+  // rounds 1-4 generated harmony
+  // only harmonize brand and accents 1 through 4
+  var initialColorHarmonies = initialColors.slice(0, 5).map(function (color) {
+    return harmonizer.harmonize(color, "fiveToneD");
+  });
+  for (var roundIndex = 1; roundIndex < 5; roundIndex++) {
+    for (var colorIndex = 0; colorIndex < initialColorHarmonies.length; colorIndex++) {
+      harmony.push(initialColorHarmonies[colorIndex][roundIndex]);
+    }
+  }
+}
+
+// syncs deprecated color families for legacy code
+function syncDeprecatedColorFamilies() {
+  // normal + saturated + desaturated
+  normal.blue = saturated.blue = desaturated.blue = colors["brand"];
+  normal.green = saturated.green = desaturated.green = colors["accent1"];
+  normal.purple = saturated.purple = desaturated.purple = colors["accent2"];
+  normal.red = saturated.red = desaturated.red = colors["accent3"];
+  normal.yellow = saturated.yellow = desaturated.yellow = colors["accent4"];
+  normal.orange = colors["accent5"];
+  normal.teal = colors["accent6"];
+  normal.indigo = colors["accent7"];
+  normal.gray = colors["text-medium"];
+  normal.grey1 = colors["text-light"];
+  normal.grey2 = colors["text-medium"];
+  normal.grey3 = colors["text-dark"];
+  normal.text = colors["text-dark"];
+}
+
+var getRandomColor = exports.getRandomColor = function getRandomColor(family) {
+  // $FlowFixMe: Object.values doesn't preserve the type :-/
+  var colors = Object.values(family);
+  return colors[Math.floor(Math.random() * colors.length)];
+};
+
+var getColorScale = exports.getColorScale = function getColorScale(extent, colors) {
+  var _extent = _slicedToArray(extent, 2),
+      start = _extent[0],
+      end = _extent[1];
+
+  return _d3.default.scale.linear().domain(colors.length === 3 ? [start, start + (end - start) / 2, end] : [start, end]).range(colors);
+};
+
+var alpha = exports.alpha = function alpha(color, _alpha) {
+  return (0, _color2.default)(color).alpha(_alpha).string();
+};
+
+var darken = exports.darken = function darken(color, factor) {
+  return (0, _color2.default)(color).darken(factor).string();
+};
+
+/***/ }),
+/* 60 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _d3Scale = __webpack_require__(61);
+
+exports.default = {
+  scale: {
+    linear: _d3Scale.scaleLinear
+  }
+}; // minimal set of d3 functions needed for color_selector.js
+
+/***/ }),
+/* 61 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__src_band__ = __webpack_require__(62);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "scaleBand", function() { return __WEBPACK_IMPORTED_MODULE_0__src_band__["a"]; });
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "scalePoint", function() { return __WEBPACK_IMPORTED_MODULE_0__src_band__["b"]; });
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__src_identity__ = __webpack_require__(85);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "scaleIdentity", function() { return __WEBPACK_IMPORTED_MODULE_1__src_identity__["a"]; });
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__src_linear__ = __webpack_require__(5);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "scaleLinear", function() { return __WEBPACK_IMPORTED_MODULE_2__src_linear__["a"]; });
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__src_log__ = __webpack_require__(110);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "scaleLog", function() { return __WEBPACK_IMPORTED_MODULE_3__src_log__["a"]; });
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__src_ordinal__ = __webpack_require__(35);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "scaleOrdinal", function() { return __WEBPACK_IMPORTED_MODULE_4__src_ordinal__["a"]; });
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "scaleImplicit", function() { return __WEBPACK_IMPORTED_MODULE_4__src_ordinal__["b"]; });
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__src_pow__ = __webpack_require__(111);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "scalePow", function() { return __WEBPACK_IMPORTED_MODULE_5__src_pow__["a"]; });
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "scaleSqrt", function() { return __WEBPACK_IMPORTED_MODULE_5__src_pow__["b"]; });
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_6__src_quantile__ = __webpack_require__(112);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "scaleQuantile", function() { return __WEBPACK_IMPORTED_MODULE_6__src_quantile__["a"]; });
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_7__src_quantize__ = __webpack_require__(113);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "scaleQuantize", function() { return __WEBPACK_IMPORTED_MODULE_7__src_quantize__["a"]; });
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_8__src_threshold__ = __webpack_require__(114);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "scaleThreshold", function() { return __WEBPACK_IMPORTED_MODULE_8__src_threshold__["a"]; });
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_9__src_time__ = __webpack_require__(50);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "scaleTime", function() { return __WEBPACK_IMPORTED_MODULE_9__src_time__["b"]; });
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_10__src_utcTime__ = __webpack_require__(130);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "scaleUtc", function() { return __WEBPACK_IMPORTED_MODULE_10__src_utcTime__["a"]; });
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_11__src_sequential__ = __webpack_require__(131);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "scaleSequential", function() { return __WEBPACK_IMPORTED_MODULE_11__src_sequential__["a"]; });
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_12__src_diverging__ = __webpack_require__(132);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "scaleDiverging", function() { return __WEBPACK_IMPORTED_MODULE_12__src_diverging__["a"]; });
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/***/ }),
+/* 62 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (immutable) */ __webpack_exports__["a"] = band;
+/* harmony export (immutable) */ __webpack_exports__["b"] = point;
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_d3_array__ = __webpack_require__(1);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__ordinal__ = __webpack_require__(35);
+
+
+
+function band() {
+  var scale = Object(__WEBPACK_IMPORTED_MODULE_1__ordinal__["a" /* default */])().unknown(undefined),
+      domain = scale.domain,
+      ordinalRange = scale.range,
+      range = [0, 1],
+      step,
+      bandwidth,
+      round = false,
+      paddingInner = 0,
+      paddingOuter = 0,
+      align = 0.5;
+
+  delete scale.unknown;
+
+  function rescale() {
+    var n = domain().length,
+        reverse = range[1] < range[0],
+        start = range[reverse - 0],
+        stop = range[1 - reverse];
+    step = (stop - start) / Math.max(1, n - paddingInner + paddingOuter * 2);
+    if (round) step = Math.floor(step);
+    start += (stop - start - step * (n - paddingInner)) * align;
+    bandwidth = step * (1 - paddingInner);
+    if (round) start = Math.round(start), bandwidth = Math.round(bandwidth);
+    var values = Object(__WEBPACK_IMPORTED_MODULE_0_d3_array__["e" /* range */])(n).map(function(i) { return start + step * i; });
+    return ordinalRange(reverse ? values.reverse() : values);
+  }
+
+  scale.domain = function(_) {
+    return arguments.length ? (domain(_), rescale()) : domain();
+  };
+
+  scale.range = function(_) {
+    return arguments.length ? (range = [+_[0], +_[1]], rescale()) : range.slice();
+  };
+
+  scale.rangeRound = function(_) {
+    return range = [+_[0], +_[1]], round = true, rescale();
+  };
+
+  scale.bandwidth = function() {
+    return bandwidth;
+  };
+
+  scale.step = function() {
+    return step;
+  };
+
+  scale.round = function(_) {
+    return arguments.length ? (round = !!_, rescale()) : round;
+  };
+
+  scale.padding = function(_) {
+    return arguments.length ? (paddingInner = paddingOuter = Math.max(0, Math.min(1, _)), rescale()) : paddingInner;
+  };
+
+  scale.paddingInner = function(_) {
+    return arguments.length ? (paddingInner = Math.max(0, Math.min(1, _)), rescale()) : paddingInner;
+  };
+
+  scale.paddingOuter = function(_) {
+    return arguments.length ? (paddingOuter = Math.max(0, Math.min(1, _)), rescale()) : paddingOuter;
+  };
+
+  scale.align = function(_) {
+    return arguments.length ? (align = Math.max(0, Math.min(1, _)), rescale()) : align;
+  };
+
+  scale.copy = function() {
+    return band()
+        .domain(domain())
+        .range(range)
+        .round(round)
+        .paddingInner(paddingInner)
+        .paddingOuter(paddingOuter)
+        .align(align);
+  };
+
+  return rescale();
+}
+
+function pointish(scale) {
+  var copy = scale.copy;
+
+  scale.padding = scale.paddingOuter;
+  delete scale.paddingInner;
+  delete scale.paddingOuter;
+
+  scale.copy = function() {
+    return pointish(copy());
+  };
+
+  return scale;
+}
+
+function point() {
+  return pointish(band().paddingInner(1));
+}
+
+
+/***/ }),
+/* 63 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__pairs__ = __webpack_require__(25);
+
+
+/* unused harmony default export */ var _unused_webpack_default_export = (function(values0, values1, reduce) {
+  var n0 = values0.length,
+      n1 = values1.length,
+      values = new Array(n0 * n1),
+      i0,
+      i1,
+      i,
+      value0;
+
+  if (reduce == null) reduce = __WEBPACK_IMPORTED_MODULE_0__pairs__["a" /* pair */];
+
+  for (i0 = i = 0; i0 < n0; ++i0) {
+    for (value0 = values0[i0], i1 = 0; i1 < n1; ++i1, ++i) {
+      values[i] = reduce(value0, values1[i1]);
+    }
+  }
+
+  return values;
+});
+
+
+/***/ }),
+/* 64 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* unused harmony default export */ var _unused_webpack_default_export = (function(a, b) {
+  return b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN;
+});
+
+
+/***/ }),
+/* 65 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__array__ = __webpack_require__(29);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__bisect__ = __webpack_require__(23);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__constant__ = __webpack_require__(66);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__extent__ = __webpack_require__(28);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__identity__ = __webpack_require__(67);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__range__ = __webpack_require__(30);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_6__ticks__ = __webpack_require__(31);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_7__threshold_sturges__ = __webpack_require__(32);
+
+
+
+
+
+
+
+
+
+/* unused harmony default export */ var _unused_webpack_default_export = (function() {
+  var value = __WEBPACK_IMPORTED_MODULE_4__identity__["a" /* default */],
+      domain = __WEBPACK_IMPORTED_MODULE_3__extent__["a" /* default */],
+      threshold = __WEBPACK_IMPORTED_MODULE_7__threshold_sturges__["a" /* default */];
+
+  function histogram(data) {
+    var i,
+        n = data.length,
+        x,
+        values = new Array(n);
+
+    for (i = 0; i < n; ++i) {
+      values[i] = value(data[i], i, data);
+    }
+
+    var xz = domain(values),
+        x0 = xz[0],
+        x1 = xz[1],
+        tz = threshold(values, x0, x1);
+
+    // Convert number of thresholds into uniform thresholds.
+    if (!Array.isArray(tz)) {
+      tz = Object(__WEBPACK_IMPORTED_MODULE_6__ticks__["c" /* tickStep */])(x0, x1, tz);
+      tz = Object(__WEBPACK_IMPORTED_MODULE_5__range__["a" /* default */])(Math.ceil(x0 / tz) * tz, Math.floor(x1 / tz) * tz, tz); // exclusive
+    }
+
+    // Remove any thresholds outside the domain.
+    var m = tz.length;
+    while (tz[0] <= x0) tz.shift(), --m;
+    while (tz[m - 1] > x1) tz.pop(), --m;
+
+    var bins = new Array(m + 1),
+        bin;
+
+    // Initialize bins.
+    for (i = 0; i <= m; ++i) {
+      bin = bins[i] = [];
+      bin.x0 = i > 0 ? tz[i - 1] : x0;
+      bin.x1 = i < m ? tz[i] : x1;
+    }
+
+    // Assign data to bins by value, ignoring any outside the domain.
+    for (i = 0; i < n; ++i) {
+      x = values[i];
+      if (x0 <= x && x <= x1) {
+        bins[Object(__WEBPACK_IMPORTED_MODULE_1__bisect__["a" /* default */])(tz, x, 0, m)].push(data[i]);
+      }
+    }
+
+    return bins;
+  }
+
+  histogram.value = function(_) {
+    return arguments.length ? (value = typeof _ === "function" ? _ : Object(__WEBPACK_IMPORTED_MODULE_2__constant__["a" /* default */])(_), histogram) : value;
+  };
+
+  histogram.domain = function(_) {
+    return arguments.length ? (domain = typeof _ === "function" ? _ : Object(__WEBPACK_IMPORTED_MODULE_2__constant__["a" /* default */])([_[0], _[1]]), histogram) : domain;
+  };
+
+  histogram.thresholds = function(_) {
+    return arguments.length ? (threshold = typeof _ === "function" ? _ : Array.isArray(_) ? Object(__WEBPACK_IMPORTED_MODULE_2__constant__["a" /* default */])(__WEBPACK_IMPORTED_MODULE_0__array__["b" /* slice */].call(_)) : Object(__WEBPACK_IMPORTED_MODULE_2__constant__["a" /* default */])(_), histogram) : threshold;
+  };
+
+  return histogram;
+});
+
+
+/***/ }),
+/* 66 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony default export */ __webpack_exports__["a"] = (function(x) {
+  return function() {
+    return x;
+  };
+});
+
+
+/***/ }),
+/* 67 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony default export */ __webpack_exports__["a"] = (function(x) {
+  return x;
+});
+
+
+/***/ }),
+/* 68 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__array__ = __webpack_require__(29);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__ascending__ = __webpack_require__(4);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__number__ = __webpack_require__(7);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__quantile__ = __webpack_require__(12);
+
+
+
+
+
+/* unused harmony default export */ var _unused_webpack_default_export = (function(values, min, max) {
+  values = __WEBPACK_IMPORTED_MODULE_0__array__["a" /* map */].call(values, __WEBPACK_IMPORTED_MODULE_2__number__["a" /* default */]).sort(__WEBPACK_IMPORTED_MODULE_1__ascending__["a" /* default */]);
+  return Math.ceil((max - min) / (2 * (Object(__WEBPACK_IMPORTED_MODULE_3__quantile__["a" /* default */])(values, 0.75) - Object(__WEBPACK_IMPORTED_MODULE_3__quantile__["a" /* default */])(values, 0.25)) * Math.pow(values.length, -1 / 3)));
+});
+
+
+/***/ }),
+/* 69 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__deviation__ = __webpack_require__(26);
+
+
+/* unused harmony default export */ var _unused_webpack_default_export = (function(values, min, max) {
+  return Math.ceil((max - min) / (3.5 * Object(__WEBPACK_IMPORTED_MODULE_0__deviation__["a" /* default */])(values) * Math.pow(values.length, -1 / 3)));
+});
+
+
+/***/ }),
+/* 70 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* unused harmony default export */ var _unused_webpack_default_export = (function(values, valueof) {
+  var n = values.length,
+      i = -1,
+      value,
+      max;
+
+  if (valueof == null) {
+    while (++i < n) { // Find the first comparable value.
+      if ((value = values[i]) != null && value >= value) {
+        max = value;
+        while (++i < n) { // Compare the remaining values.
+          if ((value = values[i]) != null && value > max) {
+            max = value;
+          }
+        }
+      }
+    }
+  }
+
+  else {
+    while (++i < n) { // Find the first comparable value.
+      if ((value = valueof(values[i], i, values)) != null && value >= value) {
+        max = value;
+        while (++i < n) { // Compare the remaining values.
+          if ((value = valueof(values[i], i, values)) != null && value > max) {
+            max = value;
+          }
+        }
+      }
+    }
+  }
+
+  return max;
+});
+
+
+/***/ }),
+/* 71 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__number__ = __webpack_require__(7);
+
+
+/* unused harmony default export */ var _unused_webpack_default_export = (function(values, valueof) {
+  var n = values.length,
+      m = n,
+      i = -1,
+      value,
+      sum = 0;
+
+  if (valueof == null) {
+    while (++i < n) {
+      if (!isNaN(value = Object(__WEBPACK_IMPORTED_MODULE_0__number__["a" /* default */])(values[i]))) sum += value;
+      else --m;
+    }
+  }
+
+  else {
+    while (++i < n) {
+      if (!isNaN(value = Object(__WEBPACK_IMPORTED_MODULE_0__number__["a" /* default */])(valueof(values[i], i, values)))) sum += value;
+      else --m;
+    }
+  }
+
+  if (m) return sum / m;
+});
+
+
+/***/ }),
+/* 72 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__ascending__ = __webpack_require__(4);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__number__ = __webpack_require__(7);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__quantile__ = __webpack_require__(12);
+
+
+
+
+/* unused harmony default export */ var _unused_webpack_default_export = (function(values, valueof) {
+  var n = values.length,
+      i = -1,
+      value,
+      numbers = [];
+
+  if (valueof == null) {
+    while (++i < n) {
+      if (!isNaN(value = Object(__WEBPACK_IMPORTED_MODULE_1__number__["a" /* default */])(values[i]))) {
+        numbers.push(value);
+      }
+    }
+  }
+
+  else {
+    while (++i < n) {
+      if (!isNaN(value = Object(__WEBPACK_IMPORTED_MODULE_1__number__["a" /* default */])(valueof(values[i], i, values)))) {
+        numbers.push(value);
+      }
+    }
+  }
+
+  return Object(__WEBPACK_IMPORTED_MODULE_2__quantile__["a" /* default */])(numbers.sort(__WEBPACK_IMPORTED_MODULE_0__ascending__["a" /* default */]), 0.5);
+});
+
+
+/***/ }),
+/* 73 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* unused harmony default export */ var _unused_webpack_default_export = (function(arrays) {
+  var n = arrays.length,
+      m,
+      i = -1,
+      j = 0,
+      merged,
+      array;
+
+  while (++i < n) j += arrays[i].length;
+  merged = new Array(j);
+
+  while (--n >= 0) {
+    array = arrays[n];
+    m = array.length;
+    while (--m >= 0) {
+      merged[--j] = array[m];
+    }
+  }
+
+  return merged;
+});
+
+
+/***/ }),
+/* 74 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* unused harmony default export */ var _unused_webpack_default_export = (function(array, indexes) {
+  var i = indexes.length, permutes = new Array(i);
+  while (i--) permutes[i] = array[indexes[i]];
+  return permutes;
+});
+
+
+/***/ }),
+/* 75 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__ascending__ = __webpack_require__(4);
+
+
+/* unused harmony default export */ var _unused_webpack_default_export = (function(values, compare) {
+  if (!(n = values.length)) return;
+  var n,
+      i = 0,
+      j = 0,
+      xi,
+      xj = values[j];
+
+  if (compare == null) compare = __WEBPACK_IMPORTED_MODULE_0__ascending__["a" /* default */];
+
+  while (++i < n) {
+    if (compare(xi = values[i], xj) < 0 || compare(xj, xj) !== 0) {
+      xj = xi, j = i;
+    }
+  }
+
+  if (compare(xj, xj) === 0) return j;
+});
+
+
+/***/ }),
+/* 76 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* unused harmony default export */ var _unused_webpack_default_export = (function(array, i0, i1) {
+  var m = (i1 == null ? array.length : i1) - (i0 = i0 == null ? 0 : +i0),
+      t,
+      i;
+
+  while (m) {
+    i = Math.random() * m-- | 0;
+    t = array[m + i0];
+    array[m + i0] = array[i + i0];
+    array[i + i0] = t;
+  }
+
+  return array;
+});
+
+
+/***/ }),
+/* 77 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* unused harmony default export */ var _unused_webpack_default_export = (function(values, valueof) {
+  var n = values.length,
+      i = -1,
+      value,
+      sum = 0;
+
+  if (valueof == null) {
+    while (++i < n) {
+      if (value = +values[i]) sum += value; // Note: zero and null are equivalent.
+    }
+  }
+
+  else {
+    while (++i < n) {
+      if (value = +valueof(values[i], i, values)) sum += value;
+    }
+  }
+
+  return sum;
+});
+
+
+/***/ }),
+/* 78 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__transpose__ = __webpack_require__(34);
+
+
+/* unused harmony default export */ var _unused_webpack_default_export = (function() {
+  return Object(__WEBPACK_IMPORTED_MODULE_0__transpose__["a" /* default */])(arguments);
+});
+
+
+/***/ }),
+/* 79 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__src_nest__ = __webpack_require__(80);
+/* unused harmony reexport nest */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__src_set__ = __webpack_require__(81);
+/* unused harmony reexport set */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__src_map__ = __webpack_require__(13);
+/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return __WEBPACK_IMPORTED_MODULE_2__src_map__["a"]; });
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__src_keys__ = __webpack_require__(82);
+/* unused harmony reexport keys */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__src_values__ = __webpack_require__(83);
+/* unused harmony reexport values */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__src_entries__ = __webpack_require__(84);
+/* unused harmony reexport entries */
+
+
+
+
+
+
+
+
+/***/ }),
+/* 80 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__map__ = __webpack_require__(13);
+
+
+/* unused harmony default export */ var _unused_webpack_default_export = (function() {
+  var keys = [],
+      sortKeys = [],
+      sortValues,
+      rollup,
+      nest;
+
+  function apply(array, depth, createResult, setResult) {
+    if (depth >= keys.length) {
+      if (sortValues != null) array.sort(sortValues);
+      return rollup != null ? rollup(array) : array;
+    }
+
+    var i = -1,
+        n = array.length,
+        key = keys[depth++],
+        keyValue,
+        value,
+        valuesByKey = Object(__WEBPACK_IMPORTED_MODULE_0__map__["a" /* default */])(),
+        values,
+        result = createResult();
+
+    while (++i < n) {
+      if (values = valuesByKey.get(keyValue = key(value = array[i]) + "")) {
+        values.push(value);
+      } else {
+        valuesByKey.set(keyValue, [value]);
+      }
+    }
+
+    valuesByKey.each(function(values, key) {
+      setResult(result, key, apply(values, depth, createResult, setResult));
+    });
+
+    return result;
+  }
+
+  function entries(map, depth) {
+    if (++depth > keys.length) return map;
+    var array, sortKey = sortKeys[depth - 1];
+    if (rollup != null && depth >= keys.length) array = map.entries();
+    else array = [], map.each(function(v, k) { array.push({key: k, values: entries(v, depth)}); });
+    return sortKey != null ? array.sort(function(a, b) { return sortKey(a.key, b.key); }) : array;
+  }
+
+  return nest = {
+    object: function(array) { return apply(array, 0, createObject, setObject); },
+    map: function(array) { return apply(array, 0, createMap, setMap); },
+    entries: function(array) { return entries(apply(array, 0, createMap, setMap), 0); },
+    key: function(d) { keys.push(d); return nest; },
+    sortKeys: function(order) { sortKeys[keys.length - 1] = order; return nest; },
+    sortValues: function(order) { sortValues = order; return nest; },
+    rollup: function(f) { rollup = f; return nest; }
+  };
+});
+
+function createObject() {
+  return {};
+}
+
+function setObject(object, key, value) {
+  object[key] = value;
+}
+
+function createMap() {
+  return Object(__WEBPACK_IMPORTED_MODULE_0__map__["a" /* default */])();
+}
+
+function setMap(map, key, value) {
+  map.set(key, value);
+}
+
+
+/***/ }),
+/* 81 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__map__ = __webpack_require__(13);
+
+
+function Set() {}
+
+var proto = __WEBPACK_IMPORTED_MODULE_0__map__["a" /* default */].prototype;
+
+Set.prototype = set.prototype = {
+  constructor: Set,
+  has: proto.has,
+  add: function(value) {
+    value += "";
+    this[__WEBPACK_IMPORTED_MODULE_0__map__["b" /* prefix */] + value] = value;
+    return this;
+  },
+  remove: proto.remove,
+  clear: proto.clear,
+  values: proto.keys,
+  size: proto.size,
+  empty: proto.empty,
+  each: proto.each
+};
+
+function set(object, f) {
+  var set = new Set;
+
+  // Copy constructor.
+  if (object instanceof Set) object.each(function(value) { set.add(value); });
+
+  // Otherwise, assume it’s an array.
+  else if (object) {
+    var i = -1, n = object.length;
+    if (f == null) while (++i < n) set.add(object[i]);
+    else while (++i < n) set.add(f(object[i], i, object));
+  }
+
+  return set;
+}
+
+/* unused harmony default export */ var _unused_webpack_default_export = (set);
+
+
+/***/ }),
+/* 82 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* unused harmony default export */ var _unused_webpack_default_export = (function(map) {
+  var keys = [];
+  for (var key in map) keys.push(key);
+  return keys;
+});
+
+
+/***/ }),
+/* 83 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* unused harmony default export */ var _unused_webpack_default_export = (function(map) {
+  var values = [];
+  for (var key in map) values.push(map[key]);
+  return values;
+});
+
+
+/***/ }),
+/* 84 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* unused harmony default export */ var _unused_webpack_default_export = (function(map) {
+  var entries = [];
+  for (var key in map) entries.push({key: key, value: map[key]});
+  return entries;
+});
+
+
+/***/ }),
+/* 85 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (immutable) */ __webpack_exports__["a"] = identity;
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__array__ = __webpack_require__(3);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__linear__ = __webpack_require__(5);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__number__ = __webpack_require__(44);
+
+
+
+
+function identity() {
+  var domain = [0, 1];
+
+  function scale(x) {
+    return +x;
+  }
+
+  scale.invert = scale;
+
+  scale.domain = scale.range = function(_) {
+    return arguments.length ? (domain = __WEBPACK_IMPORTED_MODULE_0__array__["a" /* map */].call(_, __WEBPACK_IMPORTED_MODULE_2__number__["a" /* default */]), scale) : domain.slice();
+  };
+
+  scale.copy = function() {
+    return identity().domain(domain);
+  };
+
+  return Object(__WEBPACK_IMPORTED_MODULE_1__linear__["b" /* linearish */])(scale);
+}
+
+
+/***/ }),
+/* 86 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* unused harmony export gray */
+/* harmony export (immutable) */ __webpack_exports__["a"] = lab;
+/* unused harmony export Lab */
+/* unused harmony export lch */
+/* harmony export (immutable) */ __webpack_exports__["b"] = hcl;
+/* unused harmony export Hcl */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__define__ = __webpack_require__(17);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__color__ = __webpack_require__(16);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__math__ = __webpack_require__(36);
+
+
+
+
+// https://beta.observablehq.com/@mbostock/lab-and-rgb
+var K = 18,
+    Xn = 0.96422,
+    Yn = 1,
+    Zn = 0.82521,
+    t0 = 4 / 29,
+    t1 = 6 / 29,
+    t2 = 3 * t1 * t1,
+    t3 = t1 * t1 * t1;
+
+function labConvert(o) {
+  if (o instanceof Lab) return new Lab(o.l, o.a, o.b, o.opacity);
+  if (o instanceof Hcl) {
+    if (isNaN(o.h)) return new Lab(o.l, 0, 0, o.opacity);
+    var h = o.h * __WEBPACK_IMPORTED_MODULE_2__math__["a" /* deg2rad */];
+    return new Lab(o.l, Math.cos(h) * o.c, Math.sin(h) * o.c, o.opacity);
+  }
+  if (!(o instanceof __WEBPACK_IMPORTED_MODULE_1__color__["b" /* Rgb */])) o = Object(__WEBPACK_IMPORTED_MODULE_1__color__["h" /* rgbConvert */])(o);
+  var r = rgb2lrgb(o.r),
+      g = rgb2lrgb(o.g),
+      b = rgb2lrgb(o.b),
+      y = xyz2lab((0.2225045 * r + 0.7168786 * g + 0.0606169 * b) / Yn), x, z;
+  if (r === g && g === b) x = z = y; else {
+    x = xyz2lab((0.4360747 * r + 0.3850649 * g + 0.1430804 * b) / Xn);
+    z = xyz2lab((0.0139322 * r + 0.0971045 * g + 0.7141733 * b) / Zn);
+  }
+  return new Lab(116 * y - 16, 500 * (x - y), 200 * (y - z), o.opacity);
+}
+
+function gray(l, opacity) {
+  return new Lab(l, 0, 0, opacity == null ? 1 : opacity);
+}
+
+function lab(l, a, b, opacity) {
+  return arguments.length === 1 ? labConvert(l) : new Lab(l, a, b, opacity == null ? 1 : opacity);
+}
+
+function Lab(l, a, b, opacity) {
+  this.l = +l;
+  this.a = +a;
+  this.b = +b;
+  this.opacity = +opacity;
+}
+
+Object(__WEBPACK_IMPORTED_MODULE_0__define__["a" /* default */])(Lab, lab, Object(__WEBPACK_IMPORTED_MODULE_0__define__["b" /* extend */])(__WEBPACK_IMPORTED_MODULE_1__color__["a" /* Color */], {
+  brighter: function(k) {
+    return new Lab(this.l + K * (k == null ? 1 : k), this.a, this.b, this.opacity);
+  },
+  darker: function(k) {
+    return new Lab(this.l - K * (k == null ? 1 : k), this.a, this.b, this.opacity);
+  },
+  rgb: function() {
+    var y = (this.l + 16) / 116,
+        x = isNaN(this.a) ? y : y + this.a / 500,
+        z = isNaN(this.b) ? y : y - this.b / 200;
+    x = Xn * lab2xyz(x);
+    y = Yn * lab2xyz(y);
+    z = Zn * lab2xyz(z);
+    return new __WEBPACK_IMPORTED_MODULE_1__color__["b" /* Rgb */](
+      lrgb2rgb( 3.1338561 * x - 1.6168667 * y - 0.4906146 * z),
+      lrgb2rgb(-0.9787684 * x + 1.9161415 * y + 0.0334540 * z),
+      lrgb2rgb( 0.0719453 * x - 0.2289914 * y + 1.4052427 * z),
+      this.opacity
+    );
+  }
+}));
+
+function xyz2lab(t) {
+  return t > t3 ? Math.pow(t, 1 / 3) : t / t2 + t0;
+}
+
+function lab2xyz(t) {
+  return t > t1 ? t * t * t : t2 * (t - t0);
+}
+
+function lrgb2rgb(x) {
+  return 255 * (x <= 0.0031308 ? 12.92 * x : 1.055 * Math.pow(x, 1 / 2.4) - 0.055);
+}
+
+function rgb2lrgb(x) {
+  return (x /= 255) <= 0.04045 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4);
+}
+
+function hclConvert(o) {
+  if (o instanceof Hcl) return new Hcl(o.h, o.c, o.l, o.opacity);
+  if (!(o instanceof Lab)) o = labConvert(o);
+  if (o.a === 0 && o.b === 0) return new Hcl(NaN, 0, o.l, o.opacity);
+  var h = Math.atan2(o.b, o.a) * __WEBPACK_IMPORTED_MODULE_2__math__["b" /* rad2deg */];
+  return new Hcl(h < 0 ? h + 360 : h, Math.sqrt(o.a * o.a + o.b * o.b), o.l, o.opacity);
+}
+
+function lch(l, c, h, opacity) {
+  return arguments.length === 1 ? hclConvert(l) : new Hcl(h, c, l, opacity == null ? 1 : opacity);
+}
+
+function hcl(h, c, l, opacity) {
+  return arguments.length === 1 ? hclConvert(h) : new Hcl(h, c, l, opacity == null ? 1 : opacity);
+}
+
+function Hcl(h, c, l, opacity) {
+  this.h = +h;
+  this.c = +c;
+  this.l = +l;
+  this.opacity = +opacity;
+}
+
+Object(__WEBPACK_IMPORTED_MODULE_0__define__["a" /* default */])(Hcl, hcl, Object(__WEBPACK_IMPORTED_MODULE_0__define__["b" /* extend */])(__WEBPACK_IMPORTED_MODULE_1__color__["a" /* Color */], {
+  brighter: function(k) {
+    return new Hcl(this.h, this.c, this.l + K * (k == null ? 1 : k), this.opacity);
+  },
+  darker: function(k) {
+    return new Hcl(this.h, this.c, this.l - K * (k == null ? 1 : k), this.opacity);
+  },
+  rgb: function() {
+    return labConvert(this).rgb();
+  }
+}));
+
+
+/***/ }),
+/* 87 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (immutable) */ __webpack_exports__["a"] = cubehelix;
+/* unused harmony export Cubehelix */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__define__ = __webpack_require__(17);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__color__ = __webpack_require__(16);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__math__ = __webpack_require__(36);
+
+
+
+
+var A = -0.14861,
+    B = +1.78277,
+    C = -0.29227,
+    D = -0.90649,
+    E = +1.97294,
+    ED = E * D,
+    EB = E * B,
+    BC_DA = B * C - D * A;
+
+function cubehelixConvert(o) {
+  if (o instanceof Cubehelix) return new Cubehelix(o.h, o.s, o.l, o.opacity);
+  if (!(o instanceof __WEBPACK_IMPORTED_MODULE_1__color__["b" /* Rgb */])) o = Object(__WEBPACK_IMPORTED_MODULE_1__color__["h" /* rgbConvert */])(o);
+  var r = o.r / 255,
+      g = o.g / 255,
+      b = o.b / 255,
+      l = (BC_DA * b + ED * r - EB * g) / (BC_DA + ED - EB),
+      bl = b - l,
+      k = (E * (g - l) - C * bl) / D,
+      s = Math.sqrt(k * k + bl * bl) / (E * l * (1 - l)), // NaN if l=0 or l=1
+      h = s ? Math.atan2(k, bl) * __WEBPACK_IMPORTED_MODULE_2__math__["b" /* rad2deg */] - 120 : NaN;
+  return new Cubehelix(h < 0 ? h + 360 : h, s, l, o.opacity);
+}
+
+function cubehelix(h, s, l, opacity) {
+  return arguments.length === 1 ? cubehelixConvert(h) : new Cubehelix(h, s, l, opacity == null ? 1 : opacity);
+}
+
+function Cubehelix(h, s, l, opacity) {
+  this.h = +h;
+  this.s = +s;
+  this.l = +l;
+  this.opacity = +opacity;
+}
+
+Object(__WEBPACK_IMPORTED_MODULE_0__define__["a" /* default */])(Cubehelix, cubehelix, Object(__WEBPACK_IMPORTED_MODULE_0__define__["b" /* extend */])(__WEBPACK_IMPORTED_MODULE_1__color__["a" /* Color */], {
+  brighter: function(k) {
+    k = k == null ? __WEBPACK_IMPORTED_MODULE_1__color__["c" /* brighter */] : Math.pow(__WEBPACK_IMPORTED_MODULE_1__color__["c" /* brighter */], k);
+    return new Cubehelix(this.h, this.s, this.l * k, this.opacity);
+  },
+  darker: function(k) {
+    k = k == null ? __WEBPACK_IMPORTED_MODULE_1__color__["d" /* darker */] : Math.pow(__WEBPACK_IMPORTED_MODULE_1__color__["d" /* darker */], k);
+    return new Cubehelix(this.h, this.s, this.l * k, this.opacity);
+  },
+  rgb: function() {
+    var h = isNaN(this.h) ? 0 : (this.h + 120) * __WEBPACK_IMPORTED_MODULE_2__math__["a" /* deg2rad */],
+        l = +this.l,
+        a = isNaN(this.s) ? 0 : this.s * l * (1 - l),
+        cosh = Math.cos(h),
+        sinh = Math.sin(h);
+    return new __WEBPACK_IMPORTED_MODULE_1__color__["b" /* Rgb */](
+      255 * (l + a * (A * cosh + B * sinh)),
+      255 * (l + a * (C * cosh + D * sinh)),
+      255 * (l + a * (E * cosh)),
+      this.opacity
+    );
+  }
+}));
+
+
+/***/ }),
+/* 88 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony default export */ __webpack_exports__["a"] = (function(a, b) {
+  return a = +a, b -= a, function(t) {
+    return Math.round(a + b * t);
+  };
+});
+
+
+/***/ }),
+/* 89 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* unused harmony export interpolateTransformCss */
+/* unused harmony export interpolateTransformSvg */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__number__ = __webpack_require__(9);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__parse__ = __webpack_require__(90);
+
+
+
+function interpolateTransform(parse, pxComma, pxParen, degParen) {
+
+  function pop(s) {
+    return s.length ? s.pop() + " " : "";
+  }
+
+  function translate(xa, ya, xb, yb, s, q) {
+    if (xa !== xb || ya !== yb) {
+      var i = s.push("translate(", null, pxComma, null, pxParen);
+      q.push({i: i - 4, x: Object(__WEBPACK_IMPORTED_MODULE_0__number__["a" /* default */])(xa, xb)}, {i: i - 2, x: Object(__WEBPACK_IMPORTED_MODULE_0__number__["a" /* default */])(ya, yb)});
+    } else if (xb || yb) {
+      s.push("translate(" + xb + pxComma + yb + pxParen);
+    }
+  }
+
+  function rotate(a, b, s, q) {
+    if (a !== b) {
+      if (a - b > 180) b += 360; else if (b - a > 180) a += 360; // shortest path
+      q.push({i: s.push(pop(s) + "rotate(", null, degParen) - 2, x: Object(__WEBPACK_IMPORTED_MODULE_0__number__["a" /* default */])(a, b)});
+    } else if (b) {
+      s.push(pop(s) + "rotate(" + b + degParen);
+    }
+  }
+
+  function skewX(a, b, s, q) {
+    if (a !== b) {
+      q.push({i: s.push(pop(s) + "skewX(", null, degParen) - 2, x: Object(__WEBPACK_IMPORTED_MODULE_0__number__["a" /* default */])(a, b)});
+    } else if (b) {
+      s.push(pop(s) + "skewX(" + b + degParen);
+    }
+  }
+
+  function scale(xa, ya, xb, yb, s, q) {
+    if (xa !== xb || ya !== yb) {
+      var i = s.push(pop(s) + "scale(", null, ",", null, ")");
+      q.push({i: i - 4, x: Object(__WEBPACK_IMPORTED_MODULE_0__number__["a" /* default */])(xa, xb)}, {i: i - 2, x: Object(__WEBPACK_IMPORTED_MODULE_0__number__["a" /* default */])(ya, yb)});
+    } else if (xb !== 1 || yb !== 1) {
+      s.push(pop(s) + "scale(" + xb + "," + yb + ")");
+    }
+  }
+
+  return function(a, b) {
+    var s = [], // string constants and placeholders
+        q = []; // number interpolators
+    a = parse(a), b = parse(b);
+    translate(a.translateX, a.translateY, b.translateX, b.translateY, s, q);
+    rotate(a.rotate, b.rotate, s, q);
+    skewX(a.skewX, b.skewX, s, q);
+    scale(a.scaleX, a.scaleY, b.scaleX, b.scaleY, s, q);
+    a = b = null; // gc
+    return function(t) {
+      var i = -1, n = q.length, o;
+      while (++i < n) s[(o = q[i]).i] = o.x(t);
+      return s.join("");
+    };
+  };
+}
+
+var interpolateTransformCss = interpolateTransform(__WEBPACK_IMPORTED_MODULE_1__parse__["a" /* parseCss */], "px, ", "px)", "deg)");
+var interpolateTransformSvg = interpolateTransform(__WEBPACK_IMPORTED_MODULE_1__parse__["b" /* parseSvg */], ", ", ")", ")");
+
+
+/***/ }),
+/* 90 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (immutable) */ __webpack_exports__["a"] = parseCss;
+/* harmony export (immutable) */ __webpack_exports__["b"] = parseSvg;
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__decompose__ = __webpack_require__(91);
+
+
+var cssNode,
+    cssRoot,
+    cssView,
+    svgNode;
+
+function parseCss(value) {
+  if (value === "none") return __WEBPACK_IMPORTED_MODULE_0__decompose__["b" /* identity */];
+  if (!cssNode) cssNode = document.createElement("DIV"), cssRoot = document.documentElement, cssView = document.defaultView;
+  cssNode.style.transform = value;
+  value = cssView.getComputedStyle(cssRoot.appendChild(cssNode), null).getPropertyValue("transform");
+  cssRoot.removeChild(cssNode);
+  value = value.slice(7, -1).split(",");
+  return Object(__WEBPACK_IMPORTED_MODULE_0__decompose__["a" /* default */])(+value[0], +value[1], +value[2], +value[3], +value[4], +value[5]);
+}
+
+function parseSvg(value) {
+  if (value == null) return __WEBPACK_IMPORTED_MODULE_0__decompose__["b" /* identity */];
+  if (!svgNode) svgNode = document.createElementNS("http://www.w3.org/2000/svg", "g");
+  svgNode.setAttribute("transform", value);
+  if (!(value = svgNode.transform.baseVal.consolidate())) return __WEBPACK_IMPORTED_MODULE_0__decompose__["b" /* identity */];
+  value = value.matrix;
+  return Object(__WEBPACK_IMPORTED_MODULE_0__decompose__["a" /* default */])(value.a, value.b, value.c, value.d, value.e, value.f);
+}
+
+
+/***/ }),
+/* 91 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return identity; });
+var degrees = 180 / Math.PI;
+
+var identity = {
+  translateX: 0,
+  translateY: 0,
+  rotate: 0,
+  skewX: 0,
+  scaleX: 1,
+  scaleY: 1
+};
+
+/* harmony default export */ __webpack_exports__["a"] = (function(a, b, c, d, e, f) {
+  var scaleX, scaleY, skewX;
+  if (scaleX = Math.sqrt(a * a + b * b)) a /= scaleX, b /= scaleX;
+  if (skewX = a * c + b * d) c -= a * skewX, d -= b * skewX;
+  if (scaleY = Math.sqrt(c * c + d * d)) c /= scaleY, d /= scaleY, skewX /= scaleY;
+  if (a * d < b * c) a = -a, b = -b, skewX = -skewX, scaleX = -scaleX;
+  return {
+    translateX: e,
+    translateY: f,
+    rotate: Math.atan2(b, a) * degrees,
+    skewX: Math.atan(skewX) * degrees,
+    scaleX: scaleX,
+    scaleY: scaleY
+  };
+});
+
+
+/***/ }),
+/* 92 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+var rho = Math.SQRT2,
+    rho2 = 2,
+    rho4 = 4,
+    epsilon2 = 1e-12;
+
+function cosh(x) {
+  return ((x = Math.exp(x)) + 1 / x) / 2;
+}
+
+function sinh(x) {
+  return ((x = Math.exp(x)) - 1 / x) / 2;
+}
+
+function tanh(x) {
+  return ((x = Math.exp(2 * x)) - 1) / (x + 1);
+}
+
+// p0 = [ux0, uy0, w0]
+// p1 = [ux1, uy1, w1]
+/* unused harmony default export */ var _unused_webpack_default_export = (function(p0, p1) {
+  var ux0 = p0[0], uy0 = p0[1], w0 = p0[2],
+      ux1 = p1[0], uy1 = p1[1], w1 = p1[2],
+      dx = ux1 - ux0,
+      dy = uy1 - uy0,
+      d2 = dx * dx + dy * dy,
+      i,
+      S;
+
+  // Special case for u0 ≅ u1.
+  if (d2 < epsilon2) {
+    S = Math.log(w1 / w0) / rho;
+    i = function(t) {
+      return [
+        ux0 + t * dx,
+        uy0 + t * dy,
+        w0 * Math.exp(rho * t * S)
+      ];
+    }
+  }
+
+  // General case.
+  else {
+    var d1 = Math.sqrt(d2),
+        b0 = (w1 * w1 - w0 * w0 + rho4 * d2) / (2 * w0 * rho2 * d1),
+        b1 = (w1 * w1 - w0 * w0 - rho4 * d2) / (2 * w1 * rho2 * d1),
+        r0 = Math.log(Math.sqrt(b0 * b0 + 1) - b0),
+        r1 = Math.log(Math.sqrt(b1 * b1 + 1) - b1);
+    S = (r1 - r0) / rho;
+    i = function(t) {
+      var s = t * S,
+          coshr0 = cosh(r0),
+          u = w0 / (rho2 * d1) * (coshr0 * tanh(rho * s + r0) - sinh(r0));
+      return [
+        ux0 + u * dx,
+        uy0 + u * dy,
+        w0 * coshr0 / cosh(rho * s + r0)
+      ];
+    }
+  }
+
+  i.duration = S * 1000;
+
+  return i;
+});
+
+
+/***/ }),
+/* 93 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* unused harmony export hslLong */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_d3_color__ = __webpack_require__(6);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__color__ = __webpack_require__(8);
+
+
+
+function hsl(hue) {
+  return function(start, end) {
+    var h = hue((start = Object(__WEBPACK_IMPORTED_MODULE_0_d3_color__["d" /* hsl */])(start)).h, (end = Object(__WEBPACK_IMPORTED_MODULE_0_d3_color__["d" /* hsl */])(end)).h),
+        s = Object(__WEBPACK_IMPORTED_MODULE_1__color__["a" /* default */])(start.s, end.s),
+        l = Object(__WEBPACK_IMPORTED_MODULE_1__color__["a" /* default */])(start.l, end.l),
+        opacity = Object(__WEBPACK_IMPORTED_MODULE_1__color__["a" /* default */])(start.opacity, end.opacity);
+    return function(t) {
+      start.h = h(t);
+      start.s = s(t);
+      start.l = l(t);
+      start.opacity = opacity(t);
+      return start + "";
+    };
+  }
+}
+
+/* unused harmony default export */ var _unused_webpack_default_export = (hsl(__WEBPACK_IMPORTED_MODULE_1__color__["c" /* hue */]));
+var hslLong = hsl(__WEBPACK_IMPORTED_MODULE_1__color__["a" /* default */]);
+
+
+/***/ }),
+/* 94 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* unused harmony export default */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_d3_color__ = __webpack_require__(6);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__color__ = __webpack_require__(8);
+
+
+
+function lab(start, end) {
+  var l = Object(__WEBPACK_IMPORTED_MODULE_1__color__["a" /* default */])((start = Object(__WEBPACK_IMPORTED_MODULE_0_d3_color__["e" /* lab */])(start)).l, (end = Object(__WEBPACK_IMPORTED_MODULE_0_d3_color__["e" /* lab */])(end)).l),
+      a = Object(__WEBPACK_IMPORTED_MODULE_1__color__["a" /* default */])(start.a, end.a),
+      b = Object(__WEBPACK_IMPORTED_MODULE_1__color__["a" /* default */])(start.b, end.b),
+      opacity = Object(__WEBPACK_IMPORTED_MODULE_1__color__["a" /* default */])(start.opacity, end.opacity);
+  return function(t) {
+    start.l = l(t);
+    start.a = a(t);
+    start.b = b(t);
+    start.opacity = opacity(t);
+    return start + "";
+  };
+}
+
+
+/***/ }),
+/* 95 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* unused harmony export hclLong */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_d3_color__ = __webpack_require__(6);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__color__ = __webpack_require__(8);
+
+
+
+function hcl(hue) {
+  return function(start, end) {
+    var h = hue((start = Object(__WEBPACK_IMPORTED_MODULE_0_d3_color__["c" /* hcl */])(start)).h, (end = Object(__WEBPACK_IMPORTED_MODULE_0_d3_color__["c" /* hcl */])(end)).h),
+        c = Object(__WEBPACK_IMPORTED_MODULE_1__color__["a" /* default */])(start.c, end.c),
+        l = Object(__WEBPACK_IMPORTED_MODULE_1__color__["a" /* default */])(start.l, end.l),
+        opacity = Object(__WEBPACK_IMPORTED_MODULE_1__color__["a" /* default */])(start.opacity, end.opacity);
+    return function(t) {
+      start.h = h(t);
+      start.c = c(t);
+      start.l = l(t);
+      start.opacity = opacity(t);
+      return start + "";
+    };
+  }
+}
+
+/* unused harmony default export */ var _unused_webpack_default_export = (hcl(__WEBPACK_IMPORTED_MODULE_1__color__["c" /* hue */]));
+var hclLong = hcl(__WEBPACK_IMPORTED_MODULE_1__color__["a" /* default */]);
+
+
+/***/ }),
+/* 96 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* unused harmony export cubehelixLong */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_d3_color__ = __webpack_require__(6);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__color__ = __webpack_require__(8);
+
+
+
+function cubehelix(hue) {
+  return (function cubehelixGamma(y) {
+    y = +y;
+
+    function cubehelix(start, end) {
+      var h = hue((start = Object(__WEBPACK_IMPORTED_MODULE_0_d3_color__["b" /* cubehelix */])(start)).h, (end = Object(__WEBPACK_IMPORTED_MODULE_0_d3_color__["b" /* cubehelix */])(end)).h),
+          s = Object(__WEBPACK_IMPORTED_MODULE_1__color__["a" /* default */])(start.s, end.s),
+          l = Object(__WEBPACK_IMPORTED_MODULE_1__color__["a" /* default */])(start.l, end.l),
+          opacity = Object(__WEBPACK_IMPORTED_MODULE_1__color__["a" /* default */])(start.opacity, end.opacity);
+      return function(t) {
+        start.h = h(t);
+        start.s = s(t);
+        start.l = l(Math.pow(t, y));
+        start.opacity = opacity(t);
+        return start + "";
+      };
+    }
+
+    cubehelix.gamma = cubehelixGamma;
+
+    return cubehelix;
+  })(1);
+}
+
+/* unused harmony default export */ var _unused_webpack_default_export = (cubehelix(__WEBPACK_IMPORTED_MODULE_1__color__["c" /* hue */]));
+var cubehelixLong = cubehelix(__WEBPACK_IMPORTED_MODULE_1__color__["a" /* default */]);
+
+
+/***/ }),
+/* 97 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* unused harmony export default */
+function piecewise(interpolate, values) {
+  var i = 0, n = values.length - 1, v = values[0], I = new Array(n < 0 ? 0 : n);
+  while (i < n) I[i] = interpolate(v, v = values[++i]);
+  return function(t) {
+    var i = Math.max(0, Math.min(n - 1, Math.floor(t *= n)));
+    return I[i](t - i);
+  };
+}
+
+
+/***/ }),
+/* 98 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* unused harmony default export */ var _unused_webpack_default_export = (function(interpolator, n) {
+  var samples = new Array(n);
+  for (var i = 0; i < n; ++i) samples[i] = interpolator(i / (n - 1));
+  return samples;
+});
+
+
+/***/ }),
+/* 99 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_d3_array__ = __webpack_require__(1);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_d3_format__ = __webpack_require__(45);
+
+
+
+/* harmony default export */ __webpack_exports__["a"] = (function(domain, count, specifier) {
+  var start = domain[0],
+      stop = domain[domain.length - 1],
+      step = Object(__WEBPACK_IMPORTED_MODULE_0_d3_array__["g" /* tickStep */])(start, stop, count == null ? 10 : count),
+      precision;
+  specifier = Object(__WEBPACK_IMPORTED_MODULE_1_d3_format__["c" /* formatSpecifier */])(specifier == null ? ",f" : specifier);
+  switch (specifier.type) {
+    case "s": {
+      var value = Math.max(Math.abs(start), Math.abs(stop));
+      if (specifier.precision == null && !isNaN(precision = Object(__WEBPACK_IMPORTED_MODULE_1_d3_format__["e" /* precisionPrefix */])(step, value))) specifier.precision = precision;
+      return Object(__WEBPACK_IMPORTED_MODULE_1_d3_format__["b" /* formatPrefix */])(specifier, value);
+    }
+    case "":
+    case "e":
+    case "g":
+    case "p":
+    case "r": {
+      if (specifier.precision == null && !isNaN(precision = Object(__WEBPACK_IMPORTED_MODULE_1_d3_format__["f" /* precisionRound */])(step, Math.max(Math.abs(start), Math.abs(stop))))) specifier.precision = precision - (specifier.type === "e");
+      break;
+    }
+    case "f":
+    case "%": {
+      if (specifier.precision == null && !isNaN(precision = Object(__WEBPACK_IMPORTED_MODULE_1_d3_format__["d" /* precisionFixed */])(step))) specifier.precision = precision - (specifier.type === "%") * 2;
+      break;
+    }
+  }
+  return Object(__WEBPACK_IMPORTED_MODULE_1_d3_format__["a" /* format */])(specifier);
+});
+
+
+/***/ }),
+/* 100 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return format; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return formatPrefix; });
+/* unused harmony export default */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__locale__ = __webpack_require__(46);
+
+
+var locale;
+var format;
+var formatPrefix;
+
+defaultLocale({
+  decimal: ".",
+  thousands: ",",
+  grouping: [3],
+  currency: ["$", ""]
+});
+
+function defaultLocale(definition) {
+  locale = Object(__WEBPACK_IMPORTED_MODULE_0__locale__["a" /* default */])(definition);
+  format = locale.format;
+  formatPrefix = locale.formatPrefix;
+  return locale;
+}
+
+
+/***/ }),
+/* 101 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony default export */ __webpack_exports__["a"] = (function(grouping, thousands) {
+  return function(value, width) {
+    var i = value.length,
+        t = [],
+        j = 0,
+        g = grouping[0],
+        length = 0;
+
+    while (i > 0 && g > 0) {
+      if (length + g + 1 > width) g = Math.max(1, width - length);
+      t.push(value.substring(i -= g, i + g));
+      if ((length += g + 1) > width) break;
+      g = grouping[j = (j + 1) % grouping.length];
+    }
+
+    return t.reverse().join(thousands);
+  };
+});
+
+
+/***/ }),
+/* 102 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony default export */ __webpack_exports__["a"] = (function(numerals) {
+  return function(value) {
+    return value.replace(/[0-9]/g, function(i) {
+      return numerals[+i];
+    });
+  };
+});
+
+
+/***/ }),
+/* 103 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+// Trims insignificant zeros, e.g., replaces 1.2000k with 1.2k.
+/* harmony default export */ __webpack_exports__["a"] = (function(s) {
+  out: for (var n = s.length, i = 1, i0 = -1, i1; i < n; ++i) {
+    switch (s[i]) {
+      case ".": i0 = i1 = i; break;
+      case "0": if (i0 === 0) i0 = i; i1 = i; break;
+      default: if (i0 > 0) { if (!+s[i]) break out; i0 = 0; } break;
+    }
+  }
+  return i0 > 0 ? s.slice(0, i0) + s.slice(i1 + 1) : s;
+});
+
+
+/***/ }),
+/* 104 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__formatPrefixAuto__ = __webpack_require__(48);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__formatRounded__ = __webpack_require__(105);
+
+
+
+/* harmony default export */ __webpack_exports__["a"] = ({
+  "%": function(x, p) { return (x * 100).toFixed(p); },
+  "b": function(x) { return Math.round(x).toString(2); },
+  "c": function(x) { return x + ""; },
+  "d": function(x) { return Math.round(x).toString(10); },
+  "e": function(x, p) { return x.toExponential(p); },
+  "f": function(x, p) { return x.toFixed(p); },
+  "g": function(x, p) { return x.toPrecision(p); },
+  "o": function(x) { return Math.round(x).toString(8); },
+  "p": function(x, p) { return Object(__WEBPACK_IMPORTED_MODULE_1__formatRounded__["a" /* default */])(x * 100, p); },
+  "r": __WEBPACK_IMPORTED_MODULE_1__formatRounded__["a" /* default */],
+  "s": __WEBPACK_IMPORTED_MODULE_0__formatPrefixAuto__["a" /* default */],
+  "X": function(x) { return Math.round(x).toString(16).toUpperCase(); },
+  "x": function(x) { return Math.round(x).toString(16); }
+});
+
+
+/***/ }),
+/* 105 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__formatDecimal__ = __webpack_require__(20);
+
+
+/* harmony default export */ __webpack_exports__["a"] = (function(x, p) {
+  var d = Object(__WEBPACK_IMPORTED_MODULE_0__formatDecimal__["a" /* default */])(x, p);
+  if (!d) return x + "";
+  var coefficient = d[0],
+      exponent = d[1];
+  return exponent < 0 ? "0." + new Array(-exponent).join("0") + coefficient
+      : coefficient.length > exponent + 1 ? coefficient.slice(0, exponent + 1) + "." + coefficient.slice(exponent + 1)
+      : coefficient + new Array(exponent - coefficient.length + 2).join("0");
+});
+
+
+/***/ }),
+/* 106 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony default export */ __webpack_exports__["a"] = (function(x) {
+  return x;
+});
+
+
+/***/ }),
+/* 107 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__exponent__ = __webpack_require__(11);
+
+
+/* harmony default export */ __webpack_exports__["a"] = (function(step) {
+  return Math.max(0, -Object(__WEBPACK_IMPORTED_MODULE_0__exponent__["a" /* default */])(Math.abs(step)));
+});
+
+
+/***/ }),
+/* 108 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__exponent__ = __webpack_require__(11);
+
+
+/* harmony default export */ __webpack_exports__["a"] = (function(step, value) {
+  return Math.max(0, Math.max(-8, Math.min(8, Math.floor(Object(__WEBPACK_IMPORTED_MODULE_0__exponent__["a" /* default */])(value) / 3))) * 3 - Object(__WEBPACK_IMPORTED_MODULE_0__exponent__["a" /* default */])(Math.abs(step)));
+});
+
+
+/***/ }),
+/* 109 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__exponent__ = __webpack_require__(11);
+
+
+/* harmony default export */ __webpack_exports__["a"] = (function(step, max) {
+  step = Math.abs(step), max = Math.abs(max) - step;
+  return Math.max(0, Object(__WEBPACK_IMPORTED_MODULE_0__exponent__["a" /* default */])(max) - Object(__WEBPACK_IMPORTED_MODULE_0__exponent__["a" /* default */])(step)) + 1;
+});
+
+
+/***/ }),
+/* 110 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (immutable) */ __webpack_exports__["a"] = log;
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_d3_array__ = __webpack_require__(1);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_d3_format__ = __webpack_require__(45);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__constant__ = __webpack_require__(19);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__nice__ = __webpack_require__(49);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__continuous__ = __webpack_require__(10);
+
+
+
+
+
+
+function deinterpolate(a, b) {
+  return (b = Math.log(b / a))
+      ? function(x) { return Math.log(x / a) / b; }
+      : Object(__WEBPACK_IMPORTED_MODULE_2__constant__["a" /* default */])(b);
+}
+
+function reinterpolate(a, b) {
+  return a < 0
+      ? function(t) { return -Math.pow(-b, t) * Math.pow(-a, 1 - t); }
+      : function(t) { return Math.pow(b, t) * Math.pow(a, 1 - t); };
+}
+
+function pow10(x) {
+  return isFinite(x) ? +("1e" + x) : x < 0 ? 0 : x;
+}
+
+function powp(base) {
+  return base === 10 ? pow10
+      : base === Math.E ? Math.exp
+      : function(x) { return Math.pow(base, x); };
+}
+
+function logp(base) {
+  return base === Math.E ? Math.log
+      : base === 10 && Math.log10
+      || base === 2 && Math.log2
+      || (base = Math.log(base), function(x) { return Math.log(x) / base; });
+}
+
+function reflect(f) {
+  return function(x) {
+    return -f(-x);
+  };
+}
+
+function log() {
+  var scale = Object(__WEBPACK_IMPORTED_MODULE_4__continuous__["b" /* default */])(deinterpolate, reinterpolate).domain([1, 10]),
+      domain = scale.domain,
+      base = 10,
+      logs = logp(10),
+      pows = powp(10);
+
+  function rescale() {
+    logs = logp(base), pows = powp(base);
+    if (domain()[0] < 0) logs = reflect(logs), pows = reflect(pows);
+    return scale;
+  }
+
+  scale.base = function(_) {
+    return arguments.length ? (base = +_, rescale()) : base;
+  };
+
+  scale.domain = function(_) {
+    return arguments.length ? (domain(_), rescale()) : domain();
+  };
+
+  scale.ticks = function(count) {
+    var d = domain(),
+        u = d[0],
+        v = d[d.length - 1],
+        r;
+
+    if (r = v < u) i = u, u = v, v = i;
+
+    var i = logs(u),
+        j = logs(v),
+        p,
+        k,
+        t,
+        n = count == null ? 10 : +count,
+        z = [];
+
+    if (!(base % 1) && j - i < n) {
+      i = Math.round(i) - 1, j = Math.round(j) + 1;
+      if (u > 0) for (; i < j; ++i) {
+        for (k = 1, p = pows(i); k < base; ++k) {
+          t = p * k;
+          if (t < u) continue;
+          if (t > v) break;
+          z.push(t);
+        }
+      } else for (; i < j; ++i) {
+        for (k = base - 1, p = pows(i); k >= 1; --k) {
+          t = p * k;
+          if (t < u) continue;
+          if (t > v) break;
+          z.push(t);
+        }
+      }
+    } else {
+      z = Object(__WEBPACK_IMPORTED_MODULE_0_d3_array__["h" /* ticks */])(i, j, Math.min(j - i, n)).map(pows);
+    }
+
+    return r ? z.reverse() : z;
+  };
+
+  scale.tickFormat = function(count, specifier) {
+    if (specifier == null) specifier = base === 10 ? ".0e" : ",";
+    if (typeof specifier !== "function") specifier = Object(__WEBPACK_IMPORTED_MODULE_1_d3_format__["a" /* format */])(specifier);
+    if (count === Infinity) return specifier;
+    if (count == null) count = 10;
+    var k = Math.max(1, base * count / scale.ticks().length); // TODO fast estimate?
+    return function(d) {
+      var i = d / pows(Math.round(logs(d)));
+      if (i * base < base - 0.5) i *= base;
+      return i <= k ? specifier(d) : "";
+    };
+  };
+
+  scale.nice = function() {
+    return domain(Object(__WEBPACK_IMPORTED_MODULE_3__nice__["a" /* default */])(domain(), {
+      floor: function(x) { return pows(Math.floor(logs(x))); },
+      ceil: function(x) { return pows(Math.ceil(logs(x))); }
+    }));
+  };
+
+  scale.copy = function() {
+    return Object(__WEBPACK_IMPORTED_MODULE_4__continuous__["a" /* copy */])(scale, log().base(base));
+  };
+
+  return scale;
+}
+
+
+/***/ }),
+/* 111 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (immutable) */ __webpack_exports__["a"] = pow;
+/* harmony export (immutable) */ __webpack_exports__["b"] = sqrt;
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__constant__ = __webpack_require__(19);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__linear__ = __webpack_require__(5);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__continuous__ = __webpack_require__(10);
+
+
+
+
+function raise(x, exponent) {
+  return x < 0 ? -Math.pow(-x, exponent) : Math.pow(x, exponent);
+}
+
+function pow() {
+  var exponent = 1,
+      scale = Object(__WEBPACK_IMPORTED_MODULE_2__continuous__["b" /* default */])(deinterpolate, reinterpolate),
+      domain = scale.domain;
+
+  function deinterpolate(a, b) {
+    return (b = raise(b, exponent) - (a = raise(a, exponent)))
+        ? function(x) { return (raise(x, exponent) - a) / b; }
+        : Object(__WEBPACK_IMPORTED_MODULE_0__constant__["a" /* default */])(b);
+  }
+
+  function reinterpolate(a, b) {
+    b = raise(b, exponent) - (a = raise(a, exponent));
+    return function(t) { return raise(a + b * t, 1 / exponent); };
+  }
+
+  scale.exponent = function(_) {
+    return arguments.length ? (exponent = +_, domain(domain())) : exponent;
+  };
+
+  scale.copy = function() {
+    return Object(__WEBPACK_IMPORTED_MODULE_2__continuous__["a" /* copy */])(scale, pow().exponent(exponent));
+  };
+
+  return Object(__WEBPACK_IMPORTED_MODULE_1__linear__["b" /* linearish */])(scale);
+}
+
+function sqrt() {
+  return pow().exponent(0.5);
+}
+
+
+/***/ }),
+/* 112 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (immutable) */ __webpack_exports__["a"] = quantile;
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_d3_array__ = __webpack_require__(1);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__array__ = __webpack_require__(3);
+
+
+
+function quantile() {
+  var domain = [],
+      range = [],
+      thresholds = [];
+
+  function rescale() {
+    var i = 0, n = Math.max(1, range.length);
+    thresholds = new Array(n - 1);
+    while (++i < n) thresholds[i - 1] = Object(__WEBPACK_IMPORTED_MODULE_0_d3_array__["d" /* quantile */])(domain, i / n);
+    return scale;
+  }
+
+  function scale(x) {
+    if (!isNaN(x = +x)) return range[Object(__WEBPACK_IMPORTED_MODULE_0_d3_array__["b" /* bisect */])(thresholds, x)];
+  }
+
+  scale.invertExtent = function(y) {
+    var i = range.indexOf(y);
+    return i < 0 ? [NaN, NaN] : [
+      i > 0 ? thresholds[i - 1] : domain[0],
+      i < thresholds.length ? thresholds[i] : domain[domain.length - 1]
+    ];
+  };
+
+  scale.domain = function(_) {
+    if (!arguments.length) return domain.slice();
+    domain = [];
+    for (var i = 0, n = _.length, d; i < n; ++i) if (d = _[i], d != null && !isNaN(d = +d)) domain.push(d);
+    domain.sort(__WEBPACK_IMPORTED_MODULE_0_d3_array__["a" /* ascending */]);
+    return rescale();
+  };
+
+  scale.range = function(_) {
+    return arguments.length ? (range = __WEBPACK_IMPORTED_MODULE_1__array__["b" /* slice */].call(_), rescale()) : range.slice();
+  };
+
+  scale.quantiles = function() {
+    return thresholds.slice();
+  };
+
+  scale.copy = function() {
+    return quantile()
+        .domain(domain)
+        .range(range);
+  };
+
+  return scale;
+}
+
+
+/***/ }),
+/* 113 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (immutable) */ __webpack_exports__["a"] = quantize;
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_d3_array__ = __webpack_require__(1);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__array__ = __webpack_require__(3);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__linear__ = __webpack_require__(5);
+
+
+
+
+function quantize() {
+  var x0 = 0,
+      x1 = 1,
+      n = 1,
+      domain = [0.5],
+      range = [0, 1];
+
+  function scale(x) {
+    if (x <= x) return range[Object(__WEBPACK_IMPORTED_MODULE_0_d3_array__["b" /* bisect */])(domain, x, 0, n)];
+  }
+
+  function rescale() {
+    var i = -1;
+    domain = new Array(n);
+    while (++i < n) domain[i] = ((i + 1) * x1 - (i - n) * x0) / (n + 1);
+    return scale;
+  }
+
+  scale.domain = function(_) {
+    return arguments.length ? (x0 = +_[0], x1 = +_[1], rescale()) : [x0, x1];
+  };
+
+  scale.range = function(_) {
+    return arguments.length ? (n = (range = __WEBPACK_IMPORTED_MODULE_1__array__["b" /* slice */].call(_)).length - 1, rescale()) : range.slice();
+  };
+
+  scale.invertExtent = function(y) {
+    var i = range.indexOf(y);
+    return i < 0 ? [NaN, NaN]
+        : i < 1 ? [x0, domain[0]]
+        : i >= n ? [domain[n - 1], x1]
+        : [domain[i - 1], domain[i]];
+  };
+
+  scale.copy = function() {
+    return quantize()
+        .domain([x0, x1])
+        .range(range);
+  };
+
+  return Object(__WEBPACK_IMPORTED_MODULE_2__linear__["b" /* linearish */])(scale);
+}
+
+
+/***/ }),
+/* 114 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (immutable) */ __webpack_exports__["a"] = threshold;
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_d3_array__ = __webpack_require__(1);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__array__ = __webpack_require__(3);
+
+
+
+function threshold() {
+  var domain = [0.5],
+      range = [0, 1],
+      n = 1;
+
+  function scale(x) {
+    if (x <= x) return range[Object(__WEBPACK_IMPORTED_MODULE_0_d3_array__["b" /* bisect */])(domain, x, 0, n)];
+  }
+
+  scale.domain = function(_) {
+    return arguments.length ? (domain = __WEBPACK_IMPORTED_MODULE_1__array__["b" /* slice */].call(_), n = Math.min(domain.length, range.length - 1), scale) : domain.slice();
+  };
+
+  scale.range = function(_) {
+    return arguments.length ? (range = __WEBPACK_IMPORTED_MODULE_1__array__["b" /* slice */].call(_), n = Math.min(domain.length, range.length - 1), scale) : range.slice();
+  };
+
+  scale.invertExtent = function(y) {
+    var i = range.indexOf(y);
+    return [domain[i - 1], domain[i]];
+  };
+
+  scale.copy = function() {
+    return threshold()
+        .domain(domain)
+        .range(range);
+  };
+
+  return scale;
+}
+
+
+/***/ }),
+/* 115 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* unused harmony export milliseconds */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__interval__ = __webpack_require__(0);
+
+
+var millisecond = Object(__WEBPACK_IMPORTED_MODULE_0__interval__["a" /* default */])(function() {
+  // noop
+}, function(date, step) {
+  date.setTime(+date + step);
+}, function(start, end) {
+  return end - start;
+});
+
+// An optimized implementation for this simple case.
+millisecond.every = function(k) {
+  k = Math.floor(k);
+  if (!isFinite(k) || !(k > 0)) return null;
+  if (!(k > 1)) return millisecond;
+  return Object(__WEBPACK_IMPORTED_MODULE_0__interval__["a" /* default */])(function(date) {
+    date.setTime(Math.floor(date / k) * k);
+  }, function(date, step) {
+    date.setTime(+date + step * k);
+  }, function(start, end) {
+    return (end - start) / k;
+  });
+};
+
+/* harmony default export */ __webpack_exports__["a"] = (millisecond);
+var milliseconds = millisecond.range;
+
+
+/***/ }),
+/* 116 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* unused harmony export seconds */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__interval__ = __webpack_require__(0);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__duration__ = __webpack_require__(2);
+
+
+
+var second = Object(__WEBPACK_IMPORTED_MODULE_0__interval__["a" /* default */])(function(date) {
+  date.setTime(Math.floor(date / __WEBPACK_IMPORTED_MODULE_1__duration__["d" /* durationSecond */]) * __WEBPACK_IMPORTED_MODULE_1__duration__["d" /* durationSecond */]);
+}, function(date, step) {
+  date.setTime(+date + step * __WEBPACK_IMPORTED_MODULE_1__duration__["d" /* durationSecond */]);
+}, function(start, end) {
+  return (end - start) / __WEBPACK_IMPORTED_MODULE_1__duration__["d" /* durationSecond */];
+}, function(date) {
+  return date.getUTCSeconds();
+});
+
+/* harmony default export */ __webpack_exports__["a"] = (second);
+var seconds = second.range;
+
+
+/***/ }),
+/* 117 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* unused harmony export minutes */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__interval__ = __webpack_require__(0);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__duration__ = __webpack_require__(2);
+
+
+
+var minute = Object(__WEBPACK_IMPORTED_MODULE_0__interval__["a" /* default */])(function(date) {
+  date.setTime(Math.floor(date / __WEBPACK_IMPORTED_MODULE_1__duration__["c" /* durationMinute */]) * __WEBPACK_IMPORTED_MODULE_1__duration__["c" /* durationMinute */]);
+}, function(date, step) {
+  date.setTime(+date + step * __WEBPACK_IMPORTED_MODULE_1__duration__["c" /* durationMinute */]);
+}, function(start, end) {
+  return (end - start) / __WEBPACK_IMPORTED_MODULE_1__duration__["c" /* durationMinute */];
+}, function(date) {
+  return date.getMinutes();
+});
+
+/* harmony default export */ __webpack_exports__["a"] = (minute);
+var minutes = minute.range;
+
+
+/***/ }),
+/* 118 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* unused harmony export hours */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__interval__ = __webpack_require__(0);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__duration__ = __webpack_require__(2);
+
+
+
+var hour = Object(__WEBPACK_IMPORTED_MODULE_0__interval__["a" /* default */])(function(date) {
+  var offset = date.getTimezoneOffset() * __WEBPACK_IMPORTED_MODULE_1__duration__["c" /* durationMinute */] % __WEBPACK_IMPORTED_MODULE_1__duration__["b" /* durationHour */];
+  if (offset < 0) offset += __WEBPACK_IMPORTED_MODULE_1__duration__["b" /* durationHour */];
+  date.setTime(Math.floor((+date - offset) / __WEBPACK_IMPORTED_MODULE_1__duration__["b" /* durationHour */]) * __WEBPACK_IMPORTED_MODULE_1__duration__["b" /* durationHour */] + offset);
+}, function(date, step) {
+  date.setTime(+date + step * __WEBPACK_IMPORTED_MODULE_1__duration__["b" /* durationHour */]);
+}, function(start, end) {
+  return (end - start) / __WEBPACK_IMPORTED_MODULE_1__duration__["b" /* durationHour */];
+}, function(date) {
+  return date.getHours();
+});
+
+/* harmony default export */ __webpack_exports__["a"] = (hour);
+var hours = hour.range;
+
+
+/***/ }),
+/* 119 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* unused harmony export days */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__interval__ = __webpack_require__(0);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__duration__ = __webpack_require__(2);
+
+
+
+var day = Object(__WEBPACK_IMPORTED_MODULE_0__interval__["a" /* default */])(function(date) {
+  date.setHours(0, 0, 0, 0);
+}, function(date, step) {
+  date.setDate(date.getDate() + step);
+}, function(start, end) {
+  return (end - start - (end.getTimezoneOffset() - start.getTimezoneOffset()) * __WEBPACK_IMPORTED_MODULE_1__duration__["c" /* durationMinute */]) / __WEBPACK_IMPORTED_MODULE_1__duration__["a" /* durationDay */];
+}, function(date) {
+  return date.getDate() - 1;
+});
+
+/* harmony default export */ __webpack_exports__["a"] = (day);
+var days = day.range;
+
+
+/***/ }),
+/* 120 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return sunday; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return monday; });
+/* unused harmony export tuesday */
+/* unused harmony export wednesday */
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "c", function() { return thursday; });
+/* unused harmony export friday */
+/* unused harmony export saturday */
+/* unused harmony export sundays */
+/* unused harmony export mondays */
+/* unused harmony export tuesdays */
+/* unused harmony export wednesdays */
+/* unused harmony export thursdays */
+/* unused harmony export fridays */
+/* unused harmony export saturdays */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__interval__ = __webpack_require__(0);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__duration__ = __webpack_require__(2);
+
+
+
+function weekday(i) {
+  return Object(__WEBPACK_IMPORTED_MODULE_0__interval__["a" /* default */])(function(date) {
+    date.setDate(date.getDate() - (date.getDay() + 7 - i) % 7);
+    date.setHours(0, 0, 0, 0);
+  }, function(date, step) {
+    date.setDate(date.getDate() + step * 7);
+  }, function(start, end) {
+    return (end - start - (end.getTimezoneOffset() - start.getTimezoneOffset()) * __WEBPACK_IMPORTED_MODULE_1__duration__["c" /* durationMinute */]) / __WEBPACK_IMPORTED_MODULE_1__duration__["e" /* durationWeek */];
+  });
+}
+
+var sunday = weekday(0);
+var monday = weekday(1);
+var tuesday = weekday(2);
+var wednesday = weekday(3);
+var thursday = weekday(4);
+var friday = weekday(5);
+var saturday = weekday(6);
+
+var sundays = sunday.range;
+var mondays = monday.range;
+var tuesdays = tuesday.range;
+var wednesdays = wednesday.range;
+var thursdays = thursday.range;
+var fridays = friday.range;
+var saturdays = saturday.range;
+
+
+/***/ }),
+/* 121 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* unused harmony export months */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__interval__ = __webpack_require__(0);
+
+
+var month = Object(__WEBPACK_IMPORTED_MODULE_0__interval__["a" /* default */])(function(date) {
+  date.setDate(1);
+  date.setHours(0, 0, 0, 0);
+}, function(date, step) {
+  date.setMonth(date.getMonth() + step);
+}, function(start, end) {
+  return end.getMonth() - start.getMonth() + (end.getFullYear() - start.getFullYear()) * 12;
+}, function(date) {
+  return date.getMonth();
+});
+
+/* harmony default export */ __webpack_exports__["a"] = (month);
+var months = month.range;
+
+
+/***/ }),
+/* 122 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* unused harmony export years */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__interval__ = __webpack_require__(0);
+
+
+var year = Object(__WEBPACK_IMPORTED_MODULE_0__interval__["a" /* default */])(function(date) {
+  date.setMonth(0, 1);
+  date.setHours(0, 0, 0, 0);
+}, function(date, step) {
+  date.setFullYear(date.getFullYear() + step);
+}, function(start, end) {
+  return end.getFullYear() - start.getFullYear();
+}, function(date) {
+  return date.getFullYear();
+});
+
+// An optimized implementation for this simple case.
+year.every = function(k) {
+  return !isFinite(k = Math.floor(k)) || !(k > 0) ? null : Object(__WEBPACK_IMPORTED_MODULE_0__interval__["a" /* default */])(function(date) {
+    date.setFullYear(Math.floor(date.getFullYear() / k) * k);
+    date.setMonth(0, 1);
+    date.setHours(0, 0, 0, 0);
+  }, function(date, step) {
+    date.setFullYear(date.getFullYear() + step * k);
+  });
+};
+
+/* harmony default export */ __webpack_exports__["a"] = (year);
+var years = year.range;
+
+
+/***/ }),
+/* 123 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* unused harmony export utcMinutes */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__interval__ = __webpack_require__(0);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__duration__ = __webpack_require__(2);
+
+
+
+var utcMinute = Object(__WEBPACK_IMPORTED_MODULE_0__interval__["a" /* default */])(function(date) {
+  date.setUTCSeconds(0, 0);
+}, function(date, step) {
+  date.setTime(+date + step * __WEBPACK_IMPORTED_MODULE_1__duration__["c" /* durationMinute */]);
+}, function(start, end) {
+  return (end - start) / __WEBPACK_IMPORTED_MODULE_1__duration__["c" /* durationMinute */];
+}, function(date) {
+  return date.getUTCMinutes();
+});
+
+/* harmony default export */ __webpack_exports__["a"] = (utcMinute);
+var utcMinutes = utcMinute.range;
+
+
+/***/ }),
+/* 124 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* unused harmony export utcHours */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__interval__ = __webpack_require__(0);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__duration__ = __webpack_require__(2);
+
+
+
+var utcHour = Object(__WEBPACK_IMPORTED_MODULE_0__interval__["a" /* default */])(function(date) {
+  date.setUTCMinutes(0, 0, 0);
+}, function(date, step) {
+  date.setTime(+date + step * __WEBPACK_IMPORTED_MODULE_1__duration__["b" /* durationHour */]);
+}, function(start, end) {
+  return (end - start) / __WEBPACK_IMPORTED_MODULE_1__duration__["b" /* durationHour */];
+}, function(date) {
+  return date.getUTCHours();
+});
+
+/* harmony default export */ __webpack_exports__["a"] = (utcHour);
+var utcHours = utcHour.range;
+
+
+/***/ }),
+/* 125 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* unused harmony export utcDays */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__interval__ = __webpack_require__(0);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__duration__ = __webpack_require__(2);
+
+
+
+var utcDay = Object(__WEBPACK_IMPORTED_MODULE_0__interval__["a" /* default */])(function(date) {
+  date.setUTCHours(0, 0, 0, 0);
+}, function(date, step) {
+  date.setUTCDate(date.getUTCDate() + step);
+}, function(start, end) {
+  return (end - start) / __WEBPACK_IMPORTED_MODULE_1__duration__["a" /* durationDay */];
+}, function(date) {
+  return date.getUTCDate() - 1;
+});
+
+/* harmony default export */ __webpack_exports__["a"] = (utcDay);
+var utcDays = utcDay.range;
+
+
+/***/ }),
+/* 126 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return utcSunday; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return utcMonday; });
+/* unused harmony export utcTuesday */
+/* unused harmony export utcWednesday */
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "c", function() { return utcThursday; });
+/* unused harmony export utcFriday */
+/* unused harmony export utcSaturday */
+/* unused harmony export utcSundays */
+/* unused harmony export utcMondays */
+/* unused harmony export utcTuesdays */
+/* unused harmony export utcWednesdays */
+/* unused harmony export utcThursdays */
+/* unused harmony export utcFridays */
+/* unused harmony export utcSaturdays */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__interval__ = __webpack_require__(0);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__duration__ = __webpack_require__(2);
+
+
+
+function utcWeekday(i) {
+  return Object(__WEBPACK_IMPORTED_MODULE_0__interval__["a" /* default */])(function(date) {
+    date.setUTCDate(date.getUTCDate() - (date.getUTCDay() + 7 - i) % 7);
+    date.setUTCHours(0, 0, 0, 0);
+  }, function(date, step) {
+    date.setUTCDate(date.getUTCDate() + step * 7);
+  }, function(start, end) {
+    return (end - start) / __WEBPACK_IMPORTED_MODULE_1__duration__["e" /* durationWeek */];
+  });
+}
+
+var utcSunday = utcWeekday(0);
+var utcMonday = utcWeekday(1);
+var utcTuesday = utcWeekday(2);
+var utcWednesday = utcWeekday(3);
+var utcThursday = utcWeekday(4);
+var utcFriday = utcWeekday(5);
+var utcSaturday = utcWeekday(6);
+
+var utcSundays = utcSunday.range;
+var utcMondays = utcMonday.range;
+var utcTuesdays = utcTuesday.range;
+var utcWednesdays = utcWednesday.range;
+var utcThursdays = utcThursday.range;
+var utcFridays = utcFriday.range;
+var utcSaturdays = utcSaturday.range;
+
+
+/***/ }),
+/* 127 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* unused harmony export utcMonths */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__interval__ = __webpack_require__(0);
+
+
+var utcMonth = Object(__WEBPACK_IMPORTED_MODULE_0__interval__["a" /* default */])(function(date) {
+  date.setUTCDate(1);
+  date.setUTCHours(0, 0, 0, 0);
+}, function(date, step) {
+  date.setUTCMonth(date.getUTCMonth() + step);
+}, function(start, end) {
+  return end.getUTCMonth() - start.getUTCMonth() + (end.getUTCFullYear() - start.getUTCFullYear()) * 12;
+}, function(date) {
+  return date.getUTCMonth();
+});
+
+/* harmony default export */ __webpack_exports__["a"] = (utcMonth);
+var utcMonths = utcMonth.range;
+
+
+/***/ }),
+/* 128 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* unused harmony export utcYears */
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__interval__ = __webpack_require__(0);
+
+
+var utcYear = Object(__WEBPACK_IMPORTED_MODULE_0__interval__["a" /* default */])(function(date) {
+  date.setUTCMonth(0, 1);
+  date.setUTCHours(0, 0, 0, 0);
+}, function(date, step) {
+  date.setUTCFullYear(date.getUTCFullYear() + step);
+}, function(start, end) {
+  return end.getUTCFullYear() - start.getUTCFullYear();
+}, function(date) {
+  return date.getUTCFullYear();
+});
+
+// An optimized implementation for this simple case.
+utcYear.every = function(k) {
+  return !isFinite(k = Math.floor(k)) || !(k > 0) ? null : Object(__WEBPACK_IMPORTED_MODULE_0__interval__["a" /* default */])(function(date) {
+    date.setUTCFullYear(Math.floor(date.getUTCFullYear() / k) * k);
+    date.setUTCMonth(0, 1);
+    date.setUTCHours(0, 0, 0, 0);
+  }, function(date, step) {
+    date.setUTCFullYear(date.getUTCFullYear() + step * k);
+  });
+};
+
+/* harmony default export */ __webpack_exports__["a"] = (utcYear);
+var utcYears = utcYear.range;
+
+
+/***/ }),
+/* 129 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__isoFormat__ = __webpack_require__(53);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__defaultLocale__ = __webpack_require__(22);
+
+
+
+function parseIsoNative(string) {
+  var date = new Date(string);
+  return isNaN(date) ? null : date;
+}
+
+var parseIso = +new Date("2000-01-01T00:00:00.000Z")
+    ? parseIsoNative
+    : Object(__WEBPACK_IMPORTED_MODULE_1__defaultLocale__["c" /* utcParse */])(__WEBPACK_IMPORTED_MODULE_0__isoFormat__["a" /* isoSpecifier */]);
+
+/* unused harmony default export */ var _unused_webpack_default_export = (parseIso);
+
+
+/***/ }),
+/* 130 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__time__ = __webpack_require__(50);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_d3_time_format__ = __webpack_require__(51);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2_d3_time__ = __webpack_require__(21);
+
+
+
+
+/* harmony default export */ __webpack_exports__["a"] = (function() {
+  return Object(__WEBPACK_IMPORTED_MODULE_0__time__["a" /* calendar */])(__WEBPACK_IMPORTED_MODULE_2_d3_time__["v" /* utcYear */], __WEBPACK_IMPORTED_MODULE_2_d3_time__["q" /* utcMonth */], __WEBPACK_IMPORTED_MODULE_2_d3_time__["u" /* utcWeek */], __WEBPACK_IMPORTED_MODULE_2_d3_time__["l" /* utcDay */], __WEBPACK_IMPORTED_MODULE_2_d3_time__["m" /* utcHour */], __WEBPACK_IMPORTED_MODULE_2_d3_time__["o" /* utcMinute */], __WEBPACK_IMPORTED_MODULE_2_d3_time__["r" /* utcSecond */], __WEBPACK_IMPORTED_MODULE_2_d3_time__["n" /* utcMillisecond */], __WEBPACK_IMPORTED_MODULE_1_d3_time_format__["b" /* utcFormat */]).domain([Date.UTC(2000, 0, 1), Date.UTC(2000, 0, 2)]);
+});
+
+
+/***/ }),
+/* 131 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (immutable) */ __webpack_exports__["a"] = sequential;
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__linear__ = __webpack_require__(5);
+
+
+function sequential(interpolator) {
+  var x0 = 0,
+      x1 = 1,
+      k10 = 1,
+      clamp = false;
+
+  function scale(x) {
+    var t = (x - x0) * k10;
+    return interpolator(clamp ? Math.max(0, Math.min(1, t)) : t);
+  }
+
+  scale.domain = function(_) {
+    return arguments.length ? (x0 = +_[0], x1 = +_[1], k10 = x0 === x1 ? 0 : 1 / (x1 - x0), scale) : [x0, x1];
+  };
+
+  scale.clamp = function(_) {
+    return arguments.length ? (clamp = !!_, scale) : clamp;
+  };
+
+  scale.interpolator = function(_) {
+    return arguments.length ? (interpolator = _, scale) : interpolator;
+  };
+
+  scale.copy = function() {
+    return sequential(interpolator).domain([x0, x1]).clamp(clamp);
+  };
+
+  return Object(__WEBPACK_IMPORTED_MODULE_0__linear__["b" /* linearish */])(scale);
+}
+
+
+/***/ }),
+/* 132 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+/* harmony export (immutable) */ __webpack_exports__["a"] = diverging;
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__linear__ = __webpack_require__(5);
+
+
+function diverging(interpolator) {
+  var x0 = 0,
+      x1 = 0.5,
+      x2 = 1,
+      k10 = 1,
+      k21 = 1,
+      clamp = false;
+
+  function scale(x) {
+    var t = 0.5 + ((x = +x) - x1) * (x < x1 ? k10 : k21);
+    return interpolator(clamp ? Math.max(0, Math.min(1, t)) : t);
+  }
+
+  scale.domain = function(_) {
+    return arguments.length ? (x0 = +_[0], x1 = +_[1], x2 = +_[2], k10 = x0 === x1 ? 0 : 0.5 / (x1 - x0), k21 = x1 === x2 ? 0 : 0.5 / (x2 - x1), scale) : [x0, x1, x2];
+  };
+
+  scale.clamp = function(_) {
+    return arguments.length ? (clamp = !!_, scale) : clamp;
+  };
+
+  scale.interpolator = function(_) {
+    return arguments.length ? (interpolator = _, scale) : interpolator;
+  };
+
+  scale.copy = function() {
+    return diverging(interpolator).domain([x0, x1, x2]).clamp(clamp);
+  };
+
+  return Object(__WEBPACK_IMPORTED_MODULE_0__linear__["b" /* linearish */])(scale);
+}
+
+
+/***/ }),
+/* 133 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+var colorString = __webpack_require__(134);
+var convert = __webpack_require__(137);
+
+var _slice = [].slice;
+
+var skippedModels = [
+	// to be honest, I don't really feel like keyword belongs in color convert, but eh.
+	'keyword',
+
+	// gray conflicts with some method names, and has its own method defined.
+	'gray',
+
+	// shouldn't really be in color-convert either...
+	'hex'
+];
+
+var hashedModelKeys = {};
+Object.keys(convert).forEach(function (model) {
+	hashedModelKeys[_slice.call(convert[model].labels).sort().join('')] = model;
+});
+
+var limiters = {};
+
+function Color(obj, model) {
+	if (!(this instanceof Color)) {
+		return new Color(obj, model);
+	}
+
+	if (model && model in skippedModels) {
+		model = null;
+	}
+
+	if (model && !(model in convert)) {
+		throw new Error('Unknown model: ' + model);
+	}
+
+	var i;
+	var channels;
+
+	if (!obj) {
+		this.model = 'rgb';
+		this.color = [0, 0, 0];
+		this.valpha = 1;
+	} else if (obj instanceof Color) {
+		this.model = obj.model;
+		this.color = obj.color.slice();
+		this.valpha = obj.valpha;
+	} else if (typeof obj === 'string') {
+		var result = colorString.get(obj);
+		if (result === null) {
+			throw new Error('Unable to parse color from string: ' + obj);
+		}
+
+		this.model = result.model;
+		channels = convert[this.model].channels;
+		this.color = result.value.slice(0, channels);
+		this.valpha = typeof result.value[channels] === 'number' ? result.value[channels] : 1;
+	} else if (obj.length) {
+		this.model = model || 'rgb';
+		channels = convert[this.model].channels;
+		var newArr = _slice.call(obj, 0, channels);
+		this.color = zeroArray(newArr, channels);
+		this.valpha = typeof obj[channels] === 'number' ? obj[channels] : 1;
+	} else if (typeof obj === 'number') {
+		// this is always RGB - can be converted later on.
+		obj &= 0xFFFFFF;
+		this.model = 'rgb';
+		this.color = [
+			(obj >> 16) & 0xFF,
+			(obj >> 8) & 0xFF,
+			obj & 0xFF
+		];
+		this.valpha = 1;
+	} else {
+		this.valpha = 1;
+
+		var keys = Object.keys(obj);
+		if ('alpha' in obj) {
+			keys.splice(keys.indexOf('alpha'), 1);
+			this.valpha = typeof obj.alpha === 'number' ? obj.alpha : 0;
+		}
+
+		var hashedKeys = keys.sort().join('');
+		if (!(hashedKeys in hashedModelKeys)) {
+			throw new Error('Unable to parse color from object: ' + JSON.stringify(obj));
+		}
+
+		this.model = hashedModelKeys[hashedKeys];
+
+		var labels = convert[this.model].labels;
+		var color = [];
+		for (i = 0; i < labels.length; i++) {
+			color.push(obj[labels[i]]);
+		}
+
+		this.color = zeroArray(color);
+	}
+
+	// perform limitations (clamping, etc.)
+	if (limiters[this.model]) {
+		channels = convert[this.model].channels;
+		for (i = 0; i < channels; i++) {
+			var limit = limiters[this.model][i];
+			if (limit) {
+				this.color[i] = limit(this.color[i]);
+			}
+		}
+	}
+
+	this.valpha = Math.max(0, Math.min(1, this.valpha));
+
+	if (Object.freeze) {
+		Object.freeze(this);
+	}
+}
+
+Color.prototype = {
+	toString: function () {
+		return this.string();
+	},
+
+	toJSON: function () {
+		return this[this.model]();
+	},
+
+	string: function (places) {
+		var self = this.model in colorString.to ? this : this.rgb();
+		self = self.round(typeof places === 'number' ? places : 1);
+		var args = self.valpha === 1 ? self.color : self.color.concat(this.valpha);
+		return colorString.to[self.model](args);
+	},
+
+	percentString: function (places) {
+		var self = this.rgb().round(typeof places === 'number' ? places : 1);
+		var args = self.valpha === 1 ? self.color : self.color.concat(this.valpha);
+		return colorString.to.rgb.percent(args);
+	},
+
+	array: function () {
+		return this.valpha === 1 ? this.color.slice() : this.color.concat(this.valpha);
+	},
+
+	object: function () {
+		var result = {};
+		var channels = convert[this.model].channels;
+		var labels = convert[this.model].labels;
+
+		for (var i = 0; i < channels; i++) {
+			result[labels[i]] = this.color[i];
+		}
+
+		if (this.valpha !== 1) {
+			result.alpha = this.valpha;
+		}
+
+		return result;
+	},
+
+	unitArray: function () {
+		var rgb = this.rgb().color;
+		rgb[0] /= 255;
+		rgb[1] /= 255;
+		rgb[2] /= 255;
+
+		if (this.valpha !== 1) {
+			rgb.push(this.valpha);
+		}
+
+		return rgb;
+	},
+
+	unitObject: function () {
+		var rgb = this.rgb().object();
+		rgb.r /= 255;
+		rgb.g /= 255;
+		rgb.b /= 255;
+
+		if (this.valpha !== 1) {
+			rgb.alpha = this.valpha;
+		}
+
+		return rgb;
+	},
+
+	round: function (places) {
+		places = Math.max(places || 0, 0);
+		return new Color(this.color.map(roundToPlace(places)).concat(this.valpha), this.model);
+	},
+
+	alpha: function (val) {
+		if (arguments.length) {
+			return new Color(this.color.concat(Math.max(0, Math.min(1, val))), this.model);
+		}
+
+		return this.valpha;
+	},
+
+	// rgb
+	red: getset('rgb', 0, maxfn(255)),
+	green: getset('rgb', 1, maxfn(255)),
+	blue: getset('rgb', 2, maxfn(255)),
+
+	hue: getset(['hsl', 'hsv', 'hsl', 'hwb', 'hcg'], 0, function (val) { return ((val % 360) + 360) % 360; }), // eslint-disable-line brace-style
+
+	saturationl: getset('hsl', 1, maxfn(100)),
+	lightness: getset('hsl', 2, maxfn(100)),
+
+	saturationv: getset('hsv', 1, maxfn(100)),
+	value: getset('hsv', 2, maxfn(100)),
+
+	chroma: getset('hcg', 1, maxfn(100)),
+	gray: getset('hcg', 2, maxfn(100)),
+
+	white: getset('hwb', 1, maxfn(100)),
+	wblack: getset('hwb', 2, maxfn(100)),
+
+	cyan: getset('cmyk', 0, maxfn(100)),
+	magenta: getset('cmyk', 1, maxfn(100)),
+	yellow: getset('cmyk', 2, maxfn(100)),
+	black: getset('cmyk', 3, maxfn(100)),
+
+	x: getset('xyz', 0, maxfn(100)),
+	y: getset('xyz', 1, maxfn(100)),
+	z: getset('xyz', 2, maxfn(100)),
+
+	l: getset('lab', 0, maxfn(100)),
+	a: getset('lab', 1),
+	b: getset('lab', 2),
+
+	keyword: function (val) {
+		if (arguments.length) {
+			return new Color(val);
+		}
+
+		return convert[this.model].keyword(this.color);
+	},
+
+	hex: function (val) {
+		if (arguments.length) {
+			return new Color(val);
+		}
+
+		return colorString.to.hex(this.rgb().round().color);
+	},
+
+	rgbNumber: function () {
+		var rgb = this.rgb().color;
+		return ((rgb[0] & 0xFF) << 16) | ((rgb[1] & 0xFF) << 8) | (rgb[2] & 0xFF);
+	},
+
+	luminosity: function () {
+		// http://www.w3.org/TR/WCAG20/#relativeluminancedef
+		var rgb = this.rgb().color;
+
+		var lum = [];
+		for (var i = 0; i < rgb.length; i++) {
+			var chan = rgb[i] / 255;
+			lum[i] = (chan <= 0.03928) ? chan / 12.92 : Math.pow(((chan + 0.055) / 1.055), 2.4);
+		}
+
+		return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2];
+	},
+
+	contrast: function (color2) {
+		// http://www.w3.org/TR/WCAG20/#contrast-ratiodef
+		var lum1 = this.luminosity();
+		var lum2 = color2.luminosity();
+
+		if (lum1 > lum2) {
+			return (lum1 + 0.05) / (lum2 + 0.05);
+		}
+
+		return (lum2 + 0.05) / (lum1 + 0.05);
+	},
+
+	level: function (color2) {
+		var contrastRatio = this.contrast(color2);
+		if (contrastRatio >= 7.1) {
+			return 'AAA';
+		}
+
+		return (contrastRatio >= 4.5) ? 'AA' : '';
+	},
+
+	isDark: function () {
+		// YIQ equation from http://24ways.org/2010/calculating-color-contrast
+		var rgb = this.rgb().color;
+		var yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000;
+		return yiq < 128;
+	},
+
+	isLight: function () {
+		return !this.isDark();
+	},
+
+	negate: function () {
+		var rgb = this.rgb();
+		for (var i = 0; i < 3; i++) {
+			rgb.color[i] = 255 - rgb.color[i];
+		}
+		return rgb;
+	},
+
+	lighten: function (ratio) {
+		var hsl = this.hsl();
+		hsl.color[2] += hsl.color[2] * ratio;
+		return hsl;
+	},
+
+	darken: function (ratio) {
+		var hsl = this.hsl();
+		hsl.color[2] -= hsl.color[2] * ratio;
+		return hsl;
+	},
+
+	saturate: function (ratio) {
+		var hsl = this.hsl();
+		hsl.color[1] += hsl.color[1] * ratio;
+		return hsl;
+	},
+
+	desaturate: function (ratio) {
+		var hsl = this.hsl();
+		hsl.color[1] -= hsl.color[1] * ratio;
+		return hsl;
+	},
+
+	whiten: function (ratio) {
+		var hwb = this.hwb();
+		hwb.color[1] += hwb.color[1] * ratio;
+		return hwb;
+	},
+
+	blacken: function (ratio) {
+		var hwb = this.hwb();
+		hwb.color[2] += hwb.color[2] * ratio;
+		return hwb;
+	},
+
+	grayscale: function () {
+		// http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
+		var rgb = this.rgb().color;
+		var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11;
+		return Color.rgb(val, val, val);
+	},
+
+	fade: function (ratio) {
+		return this.alpha(this.valpha - (this.valpha * ratio));
+	},
+
+	opaquer: function (ratio) {
+		return this.alpha(this.valpha + (this.valpha * ratio));
+	},
+
+	rotate: function (degrees) {
+		var hsl = this.hsl();
+		var hue = hsl.color[0];
+		hue = (hue + degrees) % 360;
+		hue = hue < 0 ? 360 + hue : hue;
+		hsl.color[0] = hue;
+		return hsl;
+	},
+
+	mix: function (mixinColor, weight) {
+		// ported from sass implementation in C
+		// https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209
+		var color1 = mixinColor.rgb();
+		var color2 = this.rgb();
+		var p = weight === undefined ? 0.5 : weight;
+
+		var w = 2 * p - 1;
+		var a = color1.alpha() - color2.alpha();
+
+		var w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0;
+		var w2 = 1 - w1;
+
+		return Color.rgb(
+				w1 * color1.red() + w2 * color2.red(),
+				w1 * color1.green() + w2 * color2.green(),
+				w1 * color1.blue() + w2 * color2.blue(),
+				color1.alpha() * p + color2.alpha() * (1 - p));
+	}
+};
+
+// model conversion methods and static constructors
+Object.keys(convert).forEach(function (model) {
+	if (skippedModels.indexOf(model) !== -1) {
+		return;
+	}
+
+	var channels = convert[model].channels;
+
+	// conversion methods
+	Color.prototype[model] = function () {
+		if (this.model === model) {
+			return new Color(this);
+		}
+
+		if (arguments.length) {
+			return new Color(arguments, model);
+		}
+
+		var newAlpha = typeof arguments[channels] === 'number' ? channels : this.valpha;
+		return new Color(assertArray(convert[this.model][model].raw(this.color)).concat(newAlpha), model);
+	};
+
+	// 'static' construction methods
+	Color[model] = function (color) {
+		if (typeof color === 'number') {
+			color = zeroArray(_slice.call(arguments), channels);
+		}
+		return new Color(color, model);
+	};
+});
+
+function roundTo(num, places) {
+	return Number(num.toFixed(places));
+}
+
+function roundToPlace(places) {
+	return function (num) {
+		return roundTo(num, places);
+	};
+}
+
+function getset(model, channel, modifier) {
+	model = Array.isArray(model) ? model : [model];
+
+	model.forEach(function (m) {
+		(limiters[m] || (limiters[m] = []))[channel] = modifier;
+	});
+
+	model = model[0];
+
+	return function (val) {
+		var result;
+
+		if (arguments.length) {
+			if (modifier) {
+				val = modifier(val);
+			}
+
+			result = this[model]();
+			result.color[channel] = val;
+			return result;
+		}
+
+		result = this[model]().color[channel];
+		if (modifier) {
+			result = modifier(result);
+		}
+
+		return result;
+	};
+}
+
+function maxfn(max) {
+	return function (v) {
+		return Math.max(0, Math.min(max, v));
+	};
+}
+
+function assertArray(val) {
+	return Array.isArray(val) ? val : [val];
+}
+
+function zeroArray(arr, length) {
+	for (var i = 0; i < length; i++) {
+		if (typeof arr[i] !== 'number') {
+			arr[i] = 0;
+		}
+	}
+
+	return arr;
+}
+
+module.exports = Color;
+
+
+/***/ }),
+/* 134 */
+/***/ (function(module, exports, __webpack_require__) {
+
+/* MIT license */
+var colorNames = __webpack_require__(54);
+var swizzle = __webpack_require__(135);
+
+var reverseNames = {};
+
+// create a list of reverse color names
+for (var name in colorNames) {
+	if (colorNames.hasOwnProperty(name)) {
+		reverseNames[colorNames[name]] = name;
+	}
+}
+
+var cs = module.exports = {
+	to: {}
+};
+
+cs.get = function (string) {
+	var prefix = string.substring(0, 3).toLowerCase();
+	var val;
+	var model;
+	switch (prefix) {
+		case 'hsl':
+			val = cs.get.hsl(string);
+			model = 'hsl';
+			break;
+		case 'hwb':
+			val = cs.get.hwb(string);
+			model = 'hwb';
+			break;
+		default:
+			val = cs.get.rgb(string);
+			model = 'rgb';
+			break;
+	}
+
+	if (!val) {
+		return null;
+	}
+
+	return {model: model, value: val};
+};
+
+cs.get.rgb = function (string) {
+	if (!string) {
+		return null;
+	}
+
+	var abbr = /^#([a-f0-9]{3,4})$/i;
+	var hex = /^#([a-f0-9]{6})([a-f0-9]{2})?$/i;
+	var rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/;
+	var per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/;
+	var keyword = /(\D+)/;
+
+	var rgb = [0, 0, 0, 1];
+	var match;
+	var i;
+	var hexAlpha;
+
+	if (match = string.match(hex)) {
+		hexAlpha = match[2];
+		match = match[1];
+
+		for (i = 0; i < 3; i++) {
+			// https://jsperf.com/slice-vs-substr-vs-substring-methods-long-string/19
+			var i2 = i * 2;
+			rgb[i] = parseInt(match.slice(i2, i2 + 2), 16);
+		}
+
+		if (hexAlpha) {
+			rgb[3] = Math.round((parseInt(hexAlpha, 16) / 255) * 100) / 100;
+		}
+	} else if (match = string.match(abbr)) {
+		match = match[1];
+		hexAlpha = match[3];
+
+		for (i = 0; i < 3; i++) {
+			rgb[i] = parseInt(match[i] + match[i], 16);
+		}
+
+		if (hexAlpha) {
+			rgb[3] = Math.round((parseInt(hexAlpha + hexAlpha, 16) / 255) * 100) / 100;
+		}
+	} else if (match = string.match(rgba)) {
+		for (i = 0; i < 3; i++) {
+			rgb[i] = parseInt(match[i + 1], 0);
+		}
+
+		if (match[4]) {
+			rgb[3] = parseFloat(match[4]);
+		}
+	} else if (match = string.match(per)) {
+		for (i = 0; i < 3; i++) {
+			rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55);
+		}
+
+		if (match[4]) {
+			rgb[3] = parseFloat(match[4]);
+		}
+	} else if (match = string.match(keyword)) {
+		if (match[1] === 'transparent') {
+			return [0, 0, 0, 0];
+		}
+
+		rgb = colorNames[match[1]];
+
+		if (!rgb) {
+			return null;
+		}
+
+		rgb[3] = 1;
+
+		return rgb;
+	} else {
+		return null;
+	}
+
+	for (i = 0; i < 3; i++) {
+		rgb[i] = clamp(rgb[i], 0, 255);
+	}
+	rgb[3] = clamp(rgb[3], 0, 1);
+
+	return rgb;
+};
+
+cs.get.hsl = function (string) {
+	if (!string) {
+		return null;
+	}
+
+	var hsl = /^hsla?\(\s*([+-]?\d*[\.]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/;
+	var match = string.match(hsl);
+
+	if (match) {
+		var alpha = parseFloat(match[4]);
+		var h = ((parseFloat(match[1]) % 360) + 360) % 360;
+		var s = clamp(parseFloat(match[2]), 0, 100);
+		var l = clamp(parseFloat(match[3]), 0, 100);
+		var a = clamp(isNaN(alpha) ? 1 : alpha, 0, 1);
+
+		return [h, s, l, a];
+	}
+
+	return null;
+};
+
+cs.get.hwb = function (string) {
+	if (!string) {
+		return null;
+	}
+
+	var hwb = /^hwb\(\s*([+-]?\d*[\.]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/;
+	var match = string.match(hwb);
+
+	if (match) {
+		var alpha = parseFloat(match[4]);
+		var h = ((parseFloat(match[1]) % 360) + 360) % 360;
+		var w = clamp(parseFloat(match[2]), 0, 100);
+		var b = clamp(parseFloat(match[3]), 0, 100);
+		var a = clamp(isNaN(alpha) ? 1 : alpha, 0, 1);
+		return [h, w, b, a];
+	}
+
+	return null;
+};
+
+cs.to.hex = function () {
+	var rgba = swizzle(arguments);
+
+	return (
+		'#' +
+		hexDouble(rgba[0]) +
+		hexDouble(rgba[1]) +
+		hexDouble(rgba[2]) +
+		(rgba[3] < 1
+			? (hexDouble(Math.round(rgba[3] * 255)))
+			: '')
+	);
+};
+
+cs.to.rgb = function () {
+	var rgba = swizzle(arguments);
+
+	return rgba.length < 4 || rgba[3] === 1
+		? 'rgb(' + Math.round(rgba[0]) + ', ' + Math.round(rgba[1]) + ', ' + Math.round(rgba[2]) + ')'
+		: 'rgba(' + Math.round(rgba[0]) + ', ' + Math.round(rgba[1]) + ', ' + Math.round(rgba[2]) + ', ' + rgba[3] + ')';
+};
+
+cs.to.rgb.percent = function () {
+	var rgba = swizzle(arguments);
+
+	var r = Math.round(rgba[0] / 255 * 100);
+	var g = Math.round(rgba[1] / 255 * 100);
+	var b = Math.round(rgba[2] / 255 * 100);
+
+	return rgba.length < 4 || rgba[3] === 1
+		? 'rgb(' + r + '%, ' + g + '%, ' + b + '%)'
+		: 'rgba(' + r + '%, ' + g + '%, ' + b + '%, ' + rgba[3] + ')';
+};
+
+cs.to.hsl = function () {
+	var hsla = swizzle(arguments);
+	return hsla.length < 4 || hsla[3] === 1
+		? 'hsl(' + hsla[0] + ', ' + hsla[1] + '%, ' + hsla[2] + '%)'
+		: 'hsla(' + hsla[0] + ', ' + hsla[1] + '%, ' + hsla[2] + '%, ' + hsla[3] + ')';
+};
+
+// hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax
+// (hwb have alpha optional & 1 is default value)
+cs.to.hwb = function () {
+	var hwba = swizzle(arguments);
+
+	var a = '';
+	if (hwba.length >= 4 && hwba[3] !== 1) {
+		a = ', ' + hwba[3];
+	}
+
+	return 'hwb(' + hwba[0] + ', ' + hwba[1] + '%, ' + hwba[2] + '%' + a + ')';
+};
+
+cs.to.keyword = function (rgb) {
+	return reverseNames[rgb.slice(0, 3)];
+};
+
+// helpers
+function clamp(num, min, max) {
+	return Math.min(Math.max(min, num), max);
+}
+
+function hexDouble(num) {
+	var str = num.toString(16).toUpperCase();
+	return (str.length < 2) ? '0' + str : str;
+}
+
+
+/***/ }),
+/* 135 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+var isArrayish = __webpack_require__(136);
+
+var concat = Array.prototype.concat;
+var slice = Array.prototype.slice;
+
+var swizzle = module.exports = function swizzle(args) {
+	var results = [];
+
+	for (var i = 0, len = args.length; i < len; i++) {
+		var arg = args[i];
+
+		if (isArrayish(arg)) {
+			// http://jsperf.com/javascript-array-concat-vs-push/98
+			results = concat.call(results, slice.call(arg));
+		} else {
+			results.push(arg);
+		}
+	}
+
+	return results;
+};
+
+swizzle.wrap = function (fn) {
+	return function () {
+		return fn(swizzle(arguments));
+	};
+};
+
+
+/***/ }),
+/* 136 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+module.exports = function isArrayish(obj) {
+	if (!obj || typeof obj === 'string') {
+		return false;
+	}
+
+	return obj instanceof Array || Array.isArray(obj) ||
+		(obj.length >= 0 && (obj.splice instanceof Function ||
+			(Object.getOwnPropertyDescriptor(obj, (obj.length - 1)) && obj.constructor.name !== 'String')));
+};
+
+
+/***/ }),
+/* 137 */
+/***/ (function(module, exports, __webpack_require__) {
+
+var conversions = __webpack_require__(55);
+var route = __webpack_require__(138);
+
+var convert = {};
+
+var models = Object.keys(conversions);
+
+function wrapRaw(fn) {
+	var wrappedFn = function (args) {
+		if (args === undefined || args === null) {
+			return args;
+		}
+
+		if (arguments.length > 1) {
+			args = Array.prototype.slice.call(arguments);
+		}
+
+		return fn(args);
+	};
+
+	// preserve .conversion property if there is one
+	if ('conversion' in fn) {
+		wrappedFn.conversion = fn.conversion;
+	}
+
+	return wrappedFn;
+}
+
+function wrapRounded(fn) {
+	var wrappedFn = function (args) {
+		if (args === undefined || args === null) {
+			return args;
+		}
+
+		if (arguments.length > 1) {
+			args = Array.prototype.slice.call(arguments);
+		}
+
+		var result = fn(args);
+
+		// we're assuming the result is an array here.
+		// see notice in conversions.js; don't use box types
+		// in conversion functions.
+		if (typeof result === 'object') {
+			for (var len = result.length, i = 0; i < len; i++) {
+				result[i] = Math.round(result[i]);
+			}
+		}
+
+		return result;
+	};
+
+	// preserve .conversion property if there is one
+	if ('conversion' in fn) {
+		wrappedFn.conversion = fn.conversion;
+	}
+
+	return wrappedFn;
+}
+
+models.forEach(function (fromModel) {
+	convert[fromModel] = {};
+
+	Object.defineProperty(convert[fromModel], 'channels', {value: conversions[fromModel].channels});
+	Object.defineProperty(convert[fromModel], 'labels', {value: conversions[fromModel].labels});
+
+	var routes = route(fromModel);
+	var routeModels = Object.keys(routes);
+
+	routeModels.forEach(function (toModel) {
+		var fn = routes[toModel];
+
+		convert[fromModel][toModel] = wrapRounded(fn);
+		convert[fromModel][toModel].raw = wrapRaw(fn);
+	});
+});
+
+module.exports = convert;
+
+
+/***/ }),
+/* 138 */
+/***/ (function(module, exports, __webpack_require__) {
+
+var conversions = __webpack_require__(55);
+
+/*
+	this function routes a model to all other models.
+
+	all functions that are routed have a property `.conversion` attached
+	to the returned synthetic function. This property is an array
+	of strings, each with the steps in between the 'from' and 'to'
+	color models (inclusive).
+
+	conversions that are not possible simply are not included.
+*/
+
+function buildGraph() {
+	var graph = {};
+	// https://jsperf.com/object-keys-vs-for-in-with-closure/3
+	var models = Object.keys(conversions);
+
+	for (var len = models.length, i = 0; i < len; i++) {
+		graph[models[i]] = {
+			// http://jsperf.com/1-vs-infinity
+			// micro-opt, but this is simple.
+			distance: -1,
+			parent: null
+		};
+	}
+
+	return graph;
+}
+
+// https://en.wikipedia.org/wiki/Breadth-first_search
+function deriveBFS(fromModel) {
+	var graph = buildGraph();
+	var queue = [fromModel]; // unshift -> queue -> pop
+
+	graph[fromModel].distance = 0;
+
+	while (queue.length) {
+		var current = queue.pop();
+		var adjacents = Object.keys(conversions[current]);
+
+		for (var len = adjacents.length, i = 0; i < len; i++) {
+			var adjacent = adjacents[i];
+			var node = graph[adjacent];
+
+			if (node.distance === -1) {
+				node.distance = graph[current].distance + 1;
+				node.parent = current;
+				queue.unshift(adjacent);
+			}
+		}
+	}
+
+	return graph;
+}
+
+function link(from, to) {
+	return function (args) {
+		return to(from(args));
+	};
+}
+
+function wrapConversion(toModel, graph) {
+	var path = [graph[toModel].parent, toModel];
+	var fn = conversions[graph[toModel].parent][toModel];
+
+	var cur = graph[toModel].parent;
+	while (graph[cur].parent) {
+		path.unshift(graph[cur].parent);
+		fn = link(conversions[graph[cur].parent][cur], fn);
+		cur = graph[cur].parent;
+	}
+
+	fn.conversion = path;
+	return fn;
+}
+
+module.exports = function (fromModel) {
+	var graph = deriveBFS(fromModel);
+	var conversion = {};
+
+	var models = Object.keys(graph);
+	for (var len = models.length, i = 0; i < len; i++) {
+		var toModel = models[i];
+		var node = graph[toModel];
+
+		if (node.parent === null) {
+			// no possible conversion, or this node is the source model.
+			continue;
+		}
+
+		conversion[toModel] = wrapConversion(toModel, graph);
+	}
+
+	return conversion;
+};
+
+
+
+/***/ }),
+/* 139 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+/*
+ * color-harmony
+ * https://github.com/skratchdot/color-harmony
+ *
+ * Copyright (c) 2014 skratchdot
+ * Licensed under the MIT license.
+ */
+
+
+
+var onecolor = __webpack_require__(140);
+
+var Harmonizer = function () {
+	var api = this;
+
+	/* degree arrays taken from: https://github.com/brehaut/color-js/ */
+	var harmonies = {
+		complementary: [0,180],
+		splitComplementary: [0,150,320],
+		splitComplementaryCW: [0,150,300],
+		splitComplementaryCCW: [0,60,210],
+		triadic: [0,120,240],
+		clash: [0,90,270],
+		tetradic: [0,90,180,270],
+		fourToneCW: [0,60,180,240],
+		fourToneCCW: [0,120,180,300],
+		fiveToneA: [0,115,155,205,245],
+		fiveToneB: [0,40,90,130,245],
+		fiveToneC: [0,50,90,205,320],
+		fiveToneD: [0,40,155,270,310],
+		fiveToneE: [0,115,230,270,320],
+		sixToneCW: [0,30,120,150,240,270],
+		sixToneCCW: [0,90,120,210,240,330],
+		neutral: [0,15,30,45,60,75],
+		analogous: [0,30,60,90,120,150]
+	};
+
+	var parseColor = function (colorString) {
+		var color = onecolor(colorString);
+		if (!color) {
+			color = onecolor('#000000');
+		}
+		return color;
+	};
+	
+	var harmonize = function (color, degrees) {
+		var ret = [], hsl, h, s, l, a, i, degree;
+		hsl = color.hsl();
+		h = hsl._hue;
+		s = hsl._saturation;
+		l = hsl._lightness;
+		a = hsl._alpha;
+		for (i = 0; i < degrees.length; i++) {
+			degree = degrees[i];
+			if (isFinite(degree) && typeof degree === 'number') {
+				ret.push(new onecolor.HSL((h + (1 / 360 * degree)) % 1, s, l, a).hex());
+			}
+		}
+		return ret;
+	};
+
+	var scaleTo = function (color, size, scale) {
+		var i, ret = [], r, g, b, a, scaleR, scaleG, scaleB;
+		if (!isFinite(size) || typeof size !== 'number') {
+			size = 10;
+		}
+		r = color.red();
+		g = color.green();
+		b = color.blue();
+		a = color.alpha();
+		scaleR = (scale - r) / size;
+		scaleG = (scale - g) / size;
+		scaleB = (scale - b) / size;
+		for (i = 0; i < size; i++) {
+			ret.push(new onecolor.RGB(r, g, b, a).hex());
+			r += scaleR;
+			g += scaleG;
+			b += scaleB;
+		}
+		return ret;
+	};
+
+	api.add = function (harmonyName, degreeArray) {
+		if (Array.isArray(degreeArray)) {
+			harmonies[harmonyName] = degreeArray;
+		}
+	};
+
+	api.harmonizeAll = function (colorString) {
+		var ret = {};
+		var color = parseColor(colorString);
+		for (var harmonyName in harmonies) {
+			if (harmonies.hasOwnProperty(harmonyName)) {
+				ret[harmonyName] = harmonize(color, harmonies[harmonyName]);
+			}
+		}
+		return ret;
+	};
+
+	api.harmonize = function (colorString, harmony) {
+		var color = parseColor(colorString);
+		if (harmonies.hasOwnProperty(harmony)) {
+			harmony = harmonies[harmony];
+		}
+		if (Array.isArray(harmony)) {
+			return harmonize(color, harmony);
+		} else {
+			return [];
+		}
+	};
+
+	// mix with black (#000000)
+	api.shades = function (colorString, size) {
+		return scaleTo(parseColor(colorString), size, 0);
+	};
+
+	// mix with white (#ffffff)
+	api.tints = function (colorString, size) {
+		return scaleTo(parseColor(colorString), size, 1);
+	};
+
+	// mix with middle gray (#777777)
+	api.tones = function (colorString, size) {
+		return scaleTo(parseColor(colorString), size, 0.5);
+	};
+
+	return api;
+};
+
+exports.Harmonizer = function () {
+	return new Harmonizer();
+};
+
+
+/***/ }),
+/* 140 */
+/***/ (function(module, exports, __webpack_require__) {
+
+var __WEBPACK_AMD_DEFINE_RESULT__;/*jshint evil:true, onevar:false*/
+/*global define*/
+var installedColorSpaces = [],
+    namedColors = {},
+    undef = function (obj) {
+        return typeof obj === 'undefined';
+    },
+    channelRegExp = /\s*(\.\d+|\d+(?:\.\d+)?)(%)?\s*/,
+    percentageChannelRegExp = /\s*(\.\d+|100|\d?\d(?:\.\d+)?)%\s*/,
+    alphaChannelRegExp = /\s*(\.\d+|\d+(?:\.\d+)?)\s*/,
+    cssColorRegExp = new RegExp(
+                         "^(rgb|hsl|hsv)a?" +
+                         "\\(" +
+                             channelRegExp.source + "," +
+                             channelRegExp.source + "," +
+                             channelRegExp.source +
+                             "(?:," + alphaChannelRegExp.source + ")?" +
+                         "\\)$", "i");
+
+function ONECOLOR(obj) {
+    if (Object.prototype.toString.apply(obj) === '[object Array]') {
+        if (typeof obj[0] === 'string' && typeof ONECOLOR[obj[0]] === 'function') {
+            // Assumed array from .toJSON()
+            return new ONECOLOR[obj[0]](obj.slice(1, obj.length));
+        } else if (obj.length === 4) {
+            // Assumed 4 element int RGB array from canvas with all channels [0;255]
+            return new ONECOLOR.RGB(obj[0] / 255, obj[1] / 255, obj[2] / 255, obj[3] / 255);
+        }
+    } else if (typeof obj === 'string') {
+        var lowerCased = obj.toLowerCase();
+        if (namedColors[lowerCased]) {
+            obj = '#' + namedColors[lowerCased];
+        }
+        if (lowerCased === 'transparent') {
+            obj = 'rgba(0,0,0,0)';
+        }
+        // Test for CSS rgb(....) string
+        var matchCssSyntax = obj.match(cssColorRegExp);
+        if (matchCssSyntax) {
+            var colorSpaceName = matchCssSyntax[1].toUpperCase(),
+                alpha = undef(matchCssSyntax[8]) ? matchCssSyntax[8] : parseFloat(matchCssSyntax[8]),
+                hasHue = colorSpaceName[0] === 'H',
+                firstChannelDivisor = matchCssSyntax[3] ? 100 : (hasHue ? 360 : 255),
+                secondChannelDivisor = (matchCssSyntax[5] || hasHue) ? 100 : 255,
+                thirdChannelDivisor = (matchCssSyntax[7] || hasHue) ? 100 : 255;
+            if (undef(ONECOLOR[colorSpaceName])) {
+                throw new Error("one.color." + colorSpaceName + " is not installed.");
+            }
+            return new ONECOLOR[colorSpaceName](
+                parseFloat(matchCssSyntax[2]) / firstChannelDivisor,
+                parseFloat(matchCssSyntax[4]) / secondChannelDivisor,
+                parseFloat(matchCssSyntax[6]) / thirdChannelDivisor,
+                alpha
+            );
+        }
+        // Assume hex syntax
+        if (obj.length < 6) {
+            // Allow CSS shorthand
+            obj = obj.replace(/^#?([0-9a-f])([0-9a-f])([0-9a-f])$/i, '$1$1$2$2$3$3');
+        }
+        // Split obj into red, green, and blue components
+        var hexMatch = obj.match(/^#?([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])$/i);
+        if (hexMatch) {
+            return new ONECOLOR.RGB(
+                parseInt(hexMatch[1], 16) / 255,
+                parseInt(hexMatch[2], 16) / 255,
+                parseInt(hexMatch[3], 16) / 255
+            );
+        }
+
+        // No match so far. Lets try the less likely ones
+        if (ONECOLOR.CMYK) {
+            var cmykMatch = obj.match(new RegExp(
+                             "^cmyk" +
+                             "\\(" +
+                                 percentageChannelRegExp.source + "," +
+                                 percentageChannelRegExp.source + "," +
+                                 percentageChannelRegExp.source + "," +
+                                 percentageChannelRegExp.source +
+                             "\\)$", "i"));
+            if (cmykMatch) {
+                return new ONECOLOR.CMYK(
+                    parseFloat(cmykMatch[1]) / 100,
+                    parseFloat(cmykMatch[2]) / 100,
+                    parseFloat(cmykMatch[3]) / 100,
+                    parseFloat(cmykMatch[4]) / 100
+                );
+            }
+        }
+    } else if (typeof obj === 'object' && obj.isColor) {
+        return obj;
+    }
+    return false;
+}
+
+function installColorSpace(colorSpaceName, propertyNames, config) {
+    ONECOLOR[colorSpaceName] = new Function(propertyNames.join(","),
+        // Allow passing an array to the constructor:
+        "if (Object.prototype.toString.apply(" + propertyNames[0] + ") === '[object Array]') {" +
+            propertyNames.map(function (propertyName, i) {
+                return propertyName + "=" + propertyNames[0] + "[" + i + "];";
+            }).reverse().join("") +
+        "}" +
+        "if (" + propertyNames.filter(function (propertyName) {
+            return propertyName !== 'alpha';
+        }).map(function (propertyName) {
+            return "isNaN(" + propertyName + ")";
+        }).join("||") + "){" + "throw new Error(\"[" + colorSpaceName + "]: Invalid color: (\"+" + propertyNames.join("+\",\"+") + "+\")\");}" +
+        propertyNames.map(function (propertyName) {
+            if (propertyName === 'hue') {
+                return "this._hue=hue<0?hue-Math.floor(hue):hue%1"; // Wrap
+            } else if (propertyName === 'alpha') {
+                return "this._alpha=(isNaN(alpha)||alpha>1)?1:(alpha<0?0:alpha);";
+            } else {
+                return "this._" + propertyName + "=" + propertyName + "<0?0:(" + propertyName + ">1?1:" + propertyName + ")";
+            }
+        }).join(";") + ";"
+    );
+    ONECOLOR[colorSpaceName].propertyNames = propertyNames;
+
+    var prototype = ONECOLOR[colorSpaceName].prototype;
+
+    ['valueOf', 'hex', 'hexa', 'css', 'cssa'].forEach(function (methodName) {
+        prototype[methodName] = prototype[methodName] || (colorSpaceName === 'RGB' ? prototype.hex : new Function("return this.rgb()." + methodName + "();"));
+    });
+
+    prototype.isColor = true;
+
+    prototype.equals = function (otherColor, epsilon) {
+        if (undef(epsilon)) {
+            epsilon = 1e-10;
+        }
+
+        otherColor = otherColor[colorSpaceName.toLowerCase()]();
+
+        for (var i = 0; i < propertyNames.length; i = i + 1) {
+            if (Math.abs(this['_' + propertyNames[i]] - otherColor['_' + propertyNames[i]]) > epsilon) {
+                return false;
+            }
+        }
+
+        return true;
+    };
+
+    prototype.toJSON = new Function(
+        "return ['" + colorSpaceName + "', " +
+            propertyNames.map(function (propertyName) {
+                return "this._" + propertyName;
+            }, this).join(", ") +
+        "];"
+    );
+
+    for (var propertyName in config) {
+        if (config.hasOwnProperty(propertyName)) {
+            var matchFromColorSpace = propertyName.match(/^from(.*)$/);
+            if (matchFromColorSpace) {
+                ONECOLOR[matchFromColorSpace[1].toUpperCase()].prototype[colorSpaceName.toLowerCase()] = config[propertyName];
+            } else {
+                prototype[propertyName] = config[propertyName];
+            }
+        }
+    }
+
+    // It is pretty easy to implement the conversion to the same color space:
+    prototype[colorSpaceName.toLowerCase()] = function () {
+        return this;
+    };
+    prototype.toString = new Function("return \"[one.color." + colorSpaceName + ":\"+" + propertyNames.map(function (propertyName, i) {
+        return "\" " + propertyNames[i] + "=\"+this._" + propertyName;
+    }).join("+") + "+\"]\";");
+
+    // Generate getters and setters
+    propertyNames.forEach(function (propertyName, i) {
+        prototype[propertyName] = prototype[propertyName === 'black' ? 'k' : propertyName[0]] = new Function("value", "isDelta",
+            // Simple getter mode: color.red()
+            "if (typeof value === 'undefined') {" +
+                "return this._" + propertyName + ";" +
+            "}" +
+            // Adjuster: color.red(+.2, true)
+            "if (isDelta) {" +
+                "return new this.constructor(" + propertyNames.map(function (otherPropertyName, i) {
+                    return "this._" + otherPropertyName + (propertyName === otherPropertyName ? "+value" : "");
+                }).join(", ") + ");" +
+            "}" +
+            // Setter: color.red(.2);
+            "return new this.constructor(" + propertyNames.map(function (otherPropertyName, i) {
+                return propertyName === otherPropertyName ? "value" : "this._" + otherPropertyName;
+            }).join(", ") + ");");
+    });
+
+    function installForeignMethods(targetColorSpaceName, sourceColorSpaceName) {
+        var obj = {};
+        obj[sourceColorSpaceName.toLowerCase()] = new Function("return this.rgb()." + sourceColorSpaceName.toLowerCase() + "();"); // Fallback
+        ONECOLOR[sourceColorSpaceName].propertyNames.forEach(function (propertyName, i) {
+            obj[propertyName] = obj[propertyName === 'black' ? 'k' : propertyName[0]] = new Function("value", "isDelta", "return this." + sourceColorSpaceName.toLowerCase() + "()." + propertyName + "(value, isDelta);");
+        });
+        for (var prop in obj) {
+            if (obj.hasOwnProperty(prop) && ONECOLOR[targetColorSpaceName].prototype[prop] === undefined) {
+                ONECOLOR[targetColorSpaceName].prototype[prop] = obj[prop];
+            }
+        }
+    }
+
+    installedColorSpaces.forEach(function (otherColorSpaceName) {
+        installForeignMethods(colorSpaceName, otherColorSpaceName);
+        installForeignMethods(otherColorSpaceName, colorSpaceName);
+    });
+
+    installedColorSpaces.push(colorSpaceName);
+}
+
+ONECOLOR.installMethod = function (name, fn) {
+    installedColorSpaces.forEach(function (colorSpace) {
+        ONECOLOR[colorSpace].prototype[name] = fn;
+    });
+};
+
+installColorSpace('RGB', ['red', 'green', 'blue', 'alpha'], {
+    hex: function () {
+        var hexString = (Math.round(255 * this._red) * 0x10000 + Math.round(255 * this._green) * 0x100 + Math.round(255 * this._blue)).toString(16);
+        return '#' + ('00000'.substr(0, 6 - hexString.length)) + hexString;
+    },
+
+    hexa: function () {
+        var alphaString = Math.round(this._alpha * 255).toString(16);
+        return '#' + '00'.substr(0, 2 - alphaString.length) + alphaString + this.hex().substr(1, 6);
+    },
+
+    css: function () {
+        return "rgb(" + Math.round(255 * this._red) + "," + Math.round(255 * this._green) + "," + Math.round(255 * this._blue) + ")";
+    },
+
+    cssa: function () {
+        return "rgba(" + Math.round(255 * this._red) + "," + Math.round(255 * this._green) + "," + Math.round(255 * this._blue) + "," + this._alpha + ")";
+    }
+});
+if ("function" === 'function' && !undef(__webpack_require__(141))) {
+    !(__WEBPACK_AMD_DEFINE_RESULT__ = (function () {
+        return ONECOLOR;
+    }).call(exports, __webpack_require__, exports, module),
+				__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+} else if (true) {
+    // Node module export
+    module.exports = ONECOLOR;
+} else {
+    one = window.one || {};
+    one.color = ONECOLOR;
+}
+
+if (typeof jQuery !== 'undefined' && undef(jQuery.color)) {
+    jQuery.color = ONECOLOR;
+}
+
+/*global namedColors*/
+namedColors = {
+    aliceblue: 'f0f8ff',
+    antiquewhite: 'faebd7',
+    aqua: '0ff',
+    aquamarine: '7fffd4',
+    azure: 'f0ffff',
+    beige: 'f5f5dc',
+    bisque: 'ffe4c4',
+    black: '000',
+    blanchedalmond: 'ffebcd',
+    blue: '00f',
+    blueviolet: '8a2be2',
+    brown: 'a52a2a',
+    burlywood: 'deb887',
+    cadetblue: '5f9ea0',
+    chartreuse: '7fff00',
+    chocolate: 'd2691e',
+    coral: 'ff7f50',
+    cornflowerblue: '6495ed',
+    cornsilk: 'fff8dc',
+    crimson: 'dc143c',
+    cyan: '0ff',
+    darkblue: '00008b',
+    darkcyan: '008b8b',
+    darkgoldenrod: 'b8860b',
+    darkgray: 'a9a9a9',
+    darkgrey: 'a9a9a9',
+    darkgreen: '006400',
+    darkkhaki: 'bdb76b',
+    darkmagenta: '8b008b',
+    darkolivegreen: '556b2f',
+    darkorange: 'ff8c00',
+    darkorchid: '9932cc',
+    darkred: '8b0000',
+    darksalmon: 'e9967a',
+    darkseagreen: '8fbc8f',
+    darkslateblue: '483d8b',
+    darkslategray: '2f4f4f',
+    darkslategrey: '2f4f4f',
+    darkturquoise: '00ced1',
+    darkviolet: '9400d3',
+    deeppink: 'ff1493',
+    deepskyblue: '00bfff',
+    dimgray: '696969',
+    dimgrey: '696969',
+    dodgerblue: '1e90ff',
+    firebrick: 'b22222',
+    floralwhite: 'fffaf0',
+    forestgreen: '228b22',
+    fuchsia: 'f0f',
+    gainsboro: 'dcdcdc',
+    ghostwhite: 'f8f8ff',
+    gold: 'ffd700',
+    goldenrod: 'daa520',
+    gray: '808080',
+    grey: '808080',
+    green: '008000',
+    greenyellow: 'adff2f',
+    honeydew: 'f0fff0',
+    hotpink: 'ff69b4',
+    indianred: 'cd5c5c',
+    indigo: '4b0082',
+    ivory: 'fffff0',
+    khaki: 'f0e68c',
+    lavender: 'e6e6fa',
+    lavenderblush: 'fff0f5',
+    lawngreen: '7cfc00',
+    lemonchiffon: 'fffacd',
+    lightblue: 'add8e6',
+    lightcoral: 'f08080',
+    lightcyan: 'e0ffff',
+    lightgoldenrodyellow: 'fafad2',
+    lightgray: 'd3d3d3',
+    lightgrey: 'd3d3d3',
+    lightgreen: '90ee90',
+    lightpink: 'ffb6c1',
+    lightsalmon: 'ffa07a',
+    lightseagreen: '20b2aa',
+    lightskyblue: '87cefa',
+    lightslategray: '789',
+    lightslategrey: '789',
+    lightsteelblue: 'b0c4de',
+    lightyellow: 'ffffe0',
+    lime: '0f0',
+    limegreen: '32cd32',
+    linen: 'faf0e6',
+    magenta: 'f0f',
+    maroon: '800000',
+    mediumaquamarine: '66cdaa',
+    mediumblue: '0000cd',
+    mediumorchid: 'ba55d3',
+    mediumpurple: '9370d8',
+    mediumseagreen: '3cb371',
+    mediumslateblue: '7b68ee',
+    mediumspringgreen: '00fa9a',
+    mediumturquoise: '48d1cc',
+    mediumvioletred: 'c71585',
+    midnightblue: '191970',
+    mintcream: 'f5fffa',
+    mistyrose: 'ffe4e1',
+    moccasin: 'ffe4b5',
+    navajowhite: 'ffdead',
+    navy: '000080',
+    oldlace: 'fdf5e6',
+    olive: '808000',
+    olivedrab: '6b8e23',
+    orange: 'ffa500',
+    orangered: 'ff4500',
+    orchid: 'da70d6',
+    palegoldenrod: 'eee8aa',
+    palegreen: '98fb98',
+    paleturquoise: 'afeeee',
+    palevioletred: 'd87093',
+    papayawhip: 'ffefd5',
+    peachpuff: 'ffdab9',
+    peru: 'cd853f',
+    pink: 'ffc0cb',
+    plum: 'dda0dd',
+    powderblue: 'b0e0e6',
+    purple: '800080',
+    rebeccapurple: '639',
+    red: 'f00',
+    rosybrown: 'bc8f8f',
+    royalblue: '4169e1',
+    saddlebrown: '8b4513',
+    salmon: 'fa8072',
+    sandybrown: 'f4a460',
+    seagreen: '2e8b57',
+    seashell: 'fff5ee',
+    sienna: 'a0522d',
+    silver: 'c0c0c0',
+    skyblue: '87ceeb',
+    slateblue: '6a5acd',
+    slategray: '708090',
+    slategrey: '708090',
+    snow: 'fffafa',
+    springgreen: '00ff7f',
+    steelblue: '4682b4',
+    tan: 'd2b48c',
+    teal: '008080',
+    thistle: 'd8bfd8',
+    tomato: 'ff6347',
+    turquoise: '40e0d0',
+    violet: 'ee82ee',
+    wheat: 'f5deb3',
+    white: 'fff',
+    whitesmoke: 'f5f5f5',
+    yellow: 'ff0',
+    yellowgreen: '9acd32'
+};
+
+/*global INCLUDE, installColorSpace, ONECOLOR*/
+
+installColorSpace('XYZ', ['x', 'y', 'z', 'alpha'], {
+    fromRgb: function () {
+        // http://www.easyrgb.com/index.php?X=MATH&H=02#text2
+        var convert = function (channel) {
+                return channel > 0.04045 ?
+                    Math.pow((channel + 0.055) / 1.055, 2.4) :
+                    channel / 12.92;
+            },
+            r = convert(this._red),
+            g = convert(this._green),
+            b = convert(this._blue);
+
+        // Reference white point sRGB D65:
+        // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
+        return new ONECOLOR.XYZ(
+            r * 0.4124564 + g * 0.3575761 + b * 0.1804375,
+            r * 0.2126729 + g * 0.7151522 + b * 0.0721750,
+            r * 0.0193339 + g * 0.1191920 + b * 0.9503041,
+            this._alpha
+        );
+    },
+
+    rgb: function () {
+        // http://www.easyrgb.com/index.php?X=MATH&H=01#text1
+        var x = this._x,
+            y = this._y,
+            z = this._z,
+            convert = function (channel) {
+                return channel > 0.0031308 ?
+                    1.055 * Math.pow(channel, 1 / 2.4) - 0.055 :
+                    12.92 * channel;
+            };
+
+        // Reference white point sRGB D65:
+        // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
+        return new ONECOLOR.RGB(
+            convert(x *  3.2404542 + y * -1.5371385 + z * -0.4985314),
+            convert(x * -0.9692660 + y *  1.8760108 + z *  0.0415560),
+            convert(x *  0.0556434 + y * -0.2040259 + z *  1.0572252),
+            this._alpha
+        );
+    },
+
+    lab: function () {
+        // http://www.easyrgb.com/index.php?X=MATH&H=07#text7
+        var convert = function (channel) {
+                return channel > 0.008856 ?
+                    Math.pow(channel, 1 / 3) :
+                    7.787037 * channel + 4 / 29;
+            },
+            x = convert(this._x /  95.047),
+            y = convert(this._y / 100.000),
+            z = convert(this._z / 108.883);
+
+        return new ONECOLOR.LAB(
+            (116 * y) - 16,
+            500 * (x - y),
+            200 * (y - z),
+            this._alpha
+        );
+    }
+});
+
+/*global INCLUDE, installColorSpace, ONECOLOR*/
+
+installColorSpace('LAB', ['l', 'a', 'b', 'alpha'], {
+    fromRgb: function () {
+        return this.xyz().lab();
+    },
+
+    rgb: function () {
+        return this.xyz().rgb();
+    },
+
+    xyz: function () {
+        // http://www.easyrgb.com/index.php?X=MATH&H=08#text8
+        var convert = function (channel) {
+                var pow = Math.pow(channel, 3);
+                return pow > 0.008856 ?
+                    pow :
+                    (channel - 16 / 116) / 7.87;
+            },
+            y = (this._l + 16) / 116,
+            x = this._a / 500 + y,
+            z = y - this._b / 200;
+
+        return new ONECOLOR.XYZ(
+            convert(x) *  95.047,
+            convert(y) * 100.000,
+            convert(z) * 108.883,
+            this._alpha
+        );
+    }
+});
+
+/*global one*/
+
+installColorSpace('HSV', ['hue', 'saturation', 'value', 'alpha'], {
+    rgb: function () {
+        var hue = this._hue,
+            saturation = this._saturation,
+            value = this._value,
+            i = Math.min(5, Math.floor(hue * 6)),
+            f = hue * 6 - i,
+            p = value * (1 - saturation),
+            q = value * (1 - f * saturation),
+            t = value * (1 - (1 - f) * saturation),
+            red,
+            green,
+            blue;
+        switch (i) {
+        case 0:
+            red = value;
+            green = t;
+            blue = p;
+            break;
+        case 1:
+            red = q;
+            green = value;
+            blue = p;
+            break;
+        case 2:
+            red = p;
+            green = value;
+            blue = t;
+            break;
+        case 3:
+            red = p;
+            green = q;
+            blue = value;
+            break;
+        case 4:
+            red = t;
+            green = p;
+            blue = value;
+            break;
+        case 5:
+            red = value;
+            green = p;
+            blue = q;
+            break;
+        }
+        return new ONECOLOR.RGB(red, green, blue, this._alpha);
+    },
+
+    hsl: function () {
+        var l = (2 - this._saturation) * this._value,
+            sv = this._saturation * this._value,
+            svDivisor = l <= 1 ? l : (2 - l),
+            saturation;
+
+        // Avoid division by zero when lightness approaches zero:
+        if (svDivisor < 1e-9) {
+            saturation = 0;
+        } else {
+            saturation = sv / svDivisor;
+        }
+        return new ONECOLOR.HSL(this._hue, saturation, l / 2, this._alpha);
+    },
+
+    fromRgb: function () { // Becomes one.color.RGB.prototype.hsv
+        var red = this._red,
+            green = this._green,
+            blue = this._blue,
+            max = Math.max(red, green, blue),
+            min = Math.min(red, green, blue),
+            delta = max - min,
+            hue,
+            saturation = (max === 0) ? 0 : (delta / max),
+            value = max;
+        if (delta === 0) {
+            hue = 0;
+        } else {
+            switch (max) {
+            case red:
+                hue = (green - blue) / delta / 6 + (green < blue ? 1 : 0);
+                break;
+            case green:
+                hue = (blue - red) / delta / 6 + 1 / 3;
+                break;
+            case blue:
+                hue = (red - green) / delta / 6 + 2 / 3;
+                break;
+            }
+        }
+        return new ONECOLOR.HSV(hue, saturation, value, this._alpha);
+    }
+});
+
+/*global one*/
+
+
+installColorSpace('HSL', ['hue', 'saturation', 'lightness', 'alpha'], {
+    hsv: function () {
+        // Algorithm adapted from http://wiki.secondlife.com/wiki/Color_conversion_scripts
+        var l = this._lightness * 2,
+            s = this._saturation * ((l <= 1) ? l : 2 - l),
+            saturation;
+
+        // Avoid division by zero when l + s is very small (approaching black):
+        if (l + s < 1e-9) {
+            saturation = 0;
+        } else {
+            saturation = (2 * s) / (l + s);
+        }
+
+        return new ONECOLOR.HSV(this._hue, saturation, (l + s) / 2, this._alpha);
+    },
+
+    rgb: function () {
+        return this.hsv().rgb();
+    },
+
+    fromRgb: function () { // Becomes one.color.RGB.prototype.hsv
+        return this.hsv().hsl();
+    }
+});
+
+/*global one*/
+
+installColorSpace('CMYK', ['cyan', 'magenta', 'yellow', 'black', 'alpha'], {
+    rgb: function () {
+        return new ONECOLOR.RGB((1 - this._cyan * (1 - this._black) - this._black),
+                                 (1 - this._magenta * (1 - this._black) - this._black),
+                                 (1 - this._yellow * (1 - this._black) - this._black),
+                                 this._alpha);
+    },
+
+    fromRgb: function () { // Becomes one.color.RGB.prototype.cmyk
+        // Adapted from http://www.javascripter.net/faq/rgb2cmyk.htm
+        var red = this._red,
+            green = this._green,
+            blue = this._blue,
+            cyan = 1 - red,
+            magenta = 1 - green,
+            yellow = 1 - blue,
+            black = 1;
+        if (red || green || blue) {
+            black = Math.min(cyan, Math.min(magenta, yellow));
+            cyan = (cyan - black) / (1 - black);
+            magenta = (magenta - black) / (1 - black);
+            yellow = (yellow - black) / (1 - black);
+        } else {
+            black = 1;
+        }
+        return new ONECOLOR.CMYK(cyan, magenta, yellow, black, this._alpha);
+    }
+});
+
+ONECOLOR.installMethod('clearer', function (amount) {
+    return this.alpha(isNaN(amount) ? -0.1 : -amount, true);
+});
+
+
+ONECOLOR.installMethod('darken', function (amount) {
+    return this.lightness(isNaN(amount) ? -0.1 : -amount, true);
+});
+
+
+ONECOLOR.installMethod('desaturate', function (amount) {
+    return this.saturation(isNaN(amount) ? -0.1 : -amount, true);
+});
+
+function gs () {
+    var rgb = this.rgb(),
+        val = rgb._red * 0.3 + rgb._green * 0.59 + rgb._blue * 0.11;
+
+    return new ONECOLOR.RGB(val, val, val, this._alpha);
+};
+
+ONECOLOR.installMethod('greyscale', gs);
+ONECOLOR.installMethod('grayscale', gs);
+
+
+ONECOLOR.installMethod('lighten', function (amount) {
+    return this.lightness(isNaN(amount) ? 0.1 : amount, true);
+});
+
+ONECOLOR.installMethod('mix', function (otherColor, weight) {
+    otherColor = ONECOLOR(otherColor).rgb();
+    weight = 1 - (isNaN(weight) ? 0.5 : weight);
+
+    var w = weight * 2 - 1,
+        a = this._alpha - otherColor._alpha,
+        weight1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2,
+        weight2 = 1 - weight1,
+        rgb = this.rgb();
+
+    return new ONECOLOR.RGB(
+        rgb._red * weight1 + otherColor._red * weight2,
+        rgb._green * weight1 + otherColor._green * weight2,
+        rgb._blue * weight1 + otherColor._blue * weight2,
+        rgb._alpha * weight + otherColor._alpha * (1 - weight)
+    );
+});
+
+ONECOLOR.installMethod('negate', function () {
+    var rgb = this.rgb();
+    return new ONECOLOR.RGB(1 - rgb._red, 1 - rgb._green, 1 - rgb._blue, this._alpha);
+});
+
+ONECOLOR.installMethod('opaquer', function (amount) {
+    return this.alpha(isNaN(amount) ? 0.1 : amount, true);
+});
+
+ONECOLOR.installMethod('rotate', function (degrees) {
+    return this.hue((degrees || 0) / 360, true);
+});
+
+
+ONECOLOR.installMethod('saturate', function (amount) {
+    return this.saturation(isNaN(amount) ? 0.1 : amount, true);
+});
+
+// Adapted from http://gimp.sourcearchive.com/documentation/2.6.6-1ubuntu1/color-to-alpha_8c-source.html
+/*
+    toAlpha returns a color where the values of the argument have been converted to alpha
+*/
+ONECOLOR.installMethod('toAlpha', function (color) {
+    var me = this.rgb(),
+        other = ONECOLOR(color).rgb(),
+        epsilon = 1e-10,
+        a = new ONECOLOR.RGB(0, 0, 0, me._alpha),
+        channels = ['_red', '_green', '_blue'];
+
+    channels.forEach(function (channel) {
+        if (me[channel] < epsilon) {
+            a[channel] = me[channel];
+        } else if (me[channel] > other[channel]) {
+            a[channel] = (me[channel] - other[channel]) / (1 - other[channel]);
+        } else if (me[channel] > other[channel]) {
+            a[channel] = (other[channel] - me[channel]) / other[channel];
+        } else {
+            a[channel] = 0;
+        }
+    });
+
+    if (a._red > a._green) {
+        if (a._red > a._blue) {
+            me._alpha = a._red;
+        } else {
+            me._alpha = a._blue;
+        }
+    } else if (a._green > a._blue) {
+        me._alpha = a._green;
+    } else {
+        me._alpha = a._blue;
+    }
+
+    if (me._alpha < epsilon) {
+        return me;
+    }
+
+    channels.forEach(function (channel) {
+        me[channel] = (me[channel] - other[channel]) / me._alpha + other[channel];
+    });
+    me._alpha *= a._alpha;
+
+    return me;
+});
+
+/*global one*/
+
+// This file is purely for the build system
+
+// Order is important to prevent channel name clashes. Lab <-> hsL
+
+// Convenience functions
+
+
+
+/***/ }),
+/* 141 */
+/***/ (function(module, exports) {
+
+/* WEBPACK VAR INJECTION */(function(__webpack_amd_options__) {/* globals __webpack_amd_options__ */
+module.exports = __webpack_amd_options__;
+
+/* WEBPACK VAR INJECTION */}.call(exports, {}))
+
+/***/ })
+/******/ ]);
+});
\ No newline at end of file
diff --git a/resources/locales.clj b/resources/locales.clj
index b1e6d2cdfaab288d9a955e26e2439744713bd77d..4e0a619618f7fd2b87e52913fc21545d68a4a8c7 100644
--- a/resources/locales.clj
+++ b/resources/locales.clj
@@ -1,5 +1,5 @@
 {
-  :locales  #{"en"}
+  :locales  #{"en" "fr" "es" "nb" "pt" }
   :packages ["metabase"]
   :bundle   "metabase.Messages"
 }
diff --git a/resources/migrations/000_migrations.yaml b/resources/migrations/000_migrations.yaml
index e85824f75725440f467d8355ee93fc9f508d6a60..00e9d6b94e65c305ee25966a7fffa8a617e4c1aa 100644
--- a/resources/migrations/000_migrations.yaml
+++ b/resources/migrations/000_migrations.yaml
@@ -1,7 +1,22 @@
 databaseChangeLog:
+# The quoting strategy decides when things like column names should be quoted.
+# This is in place to deal with Liquibase not knowing about all of the new
+# reserved words in MySQL 8+. Using a column name that is a reserved word
+# causes a failure. Quoting all objects breaks H2 support though as it will
+# quote table names but not quote that same table name in a foreign key reference
+# which will cause a failure when trying to initially setup the database.
+  - property:
+      name: quote_strategy
+      value: QUOTE_ALL_OBJECTS
+      dbms: mysql,mariadb
+  - property:
+      name: quote_strategy
+      value: LEGACY
+      dbms: postgresql,h2
   - changeSet:
       id: '1'
       author: agilliland
+      objectQuotingStrategy: ${quote_strategy}
       changes:
       - createTable:
           columns:
@@ -1449,6 +1464,8 @@ databaseChangeLog:
   - changeSet:
       id: 10
       author: cammsaul
+      validCheckSum: 7:97fec69516d0dfe424ea7365f51bb87e
+      validCheckSum: 7:3b90e2fe0ac8e617a1f30ef95d39319b
       changes:
         - createTable:
             tableName: revision
@@ -1509,7 +1526,7 @@ databaseChangeLog:
               replace: WITHOUT
               with: WITH
         - modifySql:
-            dbms: mysql
+            dbms: mysql,mariadb
             replace:
               replace: object VARCHAR
               with: object TEXT
@@ -1552,6 +1569,8 @@ databaseChangeLog:
   - changeSet:
       id: 13
       author: agilliland
+      validCheckSum: 7:f27286894439bef33ff93761f9b32bc4
+      validCheckSum: 7:1bc8ccc9b1803cda5651f144029be40c
       changes:
         - createTable:
             tableName: activity
@@ -1636,7 +1655,7 @@ databaseChangeLog:
               replace: WITHOUT
               with: WITH
         - modifySql:
-            dbms: mysql
+            dbms: mysql,mariadb
             replace:
               replace: details VARCHAR
               with: details TEXT
@@ -1698,6 +1717,7 @@ databaseChangeLog:
   - changeSet:
       id: 15
       author: agilliland
+      objectQuotingStrategy: ${quote_strategy}
       changes:
         - addColumn:
             tableName: revision
@@ -2060,6 +2080,7 @@ databaseChangeLog:
   - changeSet:
       id: 23
       author: agilliland
+      objectQuotingStrategy: ${quote_strategy}
       changes:
         - modifyDataType:
             tableName: metabase_table
@@ -3186,6 +3207,7 @@ databaseChangeLog:
   - changeSet:
       id: 46
       author: camsaul
+      objectQuotingStrategy: ${quote_strategy}
       changes:
         - addNotNullConstraint:
             tableName: report_dashboardcard
@@ -3501,7 +3523,7 @@ databaseChangeLog:
   - property:
         name: blob.type
         value: blob
-        dbms: mysql,h2
+        dbms: mysql,h2,mariadb
   - property:
       name: blob.type
       value: bytea
@@ -4272,7 +4294,7 @@ databaseChangeLog:
             dbms: postgresql,h2
             sql: "COMMENT ON COLUMN collection.name IS 'The user-facing name of this Collection.'"
         - sql:
-            dbms: mysql
+            dbms: mysql,mariadb
             sql: "ALTER TABLE `collection` CHANGE `name` `name` TEXT NOT NULL COMMENT 'The user-facing name of this Collection.'"
 
 # In 0.30.0 we finally removed the long-deprecated native read permissions. Since they're no longer considered valid by
@@ -4295,3 +4317,751 @@ databaseChangeLog:
             tableName: raw_column
         - dropTable:
             tableName: raw_table
+
+# The Quartz Task Scheduler can use a DB to 'cluster' tasks and make sure they are only ran by a single instance where
+# using a multi-instance Metabase setup.
+
+  - changeSet:
+      id: 89
+      author: camsaul
+      comment: 'Added 0.30.0'
+      changes:
+        - createTable:
+            tableName: QRTZ_JOB_DETAILS
+            remarks: 'Used for Quartz scheduler.'
+            columns:
+              - column:
+                  name: SCHED_NAME
+                  type: varchar(120)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: JOB_NAME
+                  type: varchar(200)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: JOB_GROUP
+                  type: varchar(200)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: DESCRIPTION
+                  type: varchar(250)
+              - column:
+                  name: JOB_CLASS_NAME
+                  type: varchar(250)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: IS_DURABLE
+                  type: bool
+                  constraints:
+                    nullable: false
+              - column:
+                  name: IS_NONCONCURRENT
+                  type: bool
+                  constraints:
+                    nullable: false
+              - column:
+                  name: IS_UPDATE_DATA
+                  type: bool
+                  constraints:
+                    nullable: false
+              - column:
+                  name: REQUESTS_RECOVERY
+                  type: bool
+                  constraints:
+                    nullable: false
+              - column:
+                  name: JOB_DATA
+                  type: ${blob.type}
+        - addPrimaryKey:
+            tableName: QRTZ_JOB_DETAILS
+            columnNames: SCHED_NAME, JOB_NAME, JOB_GROUP
+            constraintName: PK_QRTZ_JOB_DETAILS
+        - createTable:
+            tableName: QRTZ_TRIGGERS
+            remarks: 'Used for Quartz scheduler.'
+            columns:
+              - column:
+                  name: SCHED_NAME
+                  type: varchar(120)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: TRIGGER_NAME
+                  type: varchar(200)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: TRIGGER_GROUP
+                  type: varchar(200)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: JOB_NAME
+                  type: varchar(200)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: JOB_GROUP
+                  type: varchar(200)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: DESCRIPTION
+                  type: varchar(250)
+              - column:
+                  name: NEXT_FIRE_TIME
+                  type: bigint
+              - column:
+                  name: PREV_FIRE_TIME
+                  type: bigint
+              - column:
+                  name: PRIORITY
+                  type: integer
+              - column:
+                  name: TRIGGER_STATE
+                  type: varchar(16)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: TRIGGER_TYPE
+                  type: varchar(8)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: START_TIME
+                  type: bigint
+                  constraints:
+                    nullable: false
+              - column:
+                  name: END_TIME
+                  type: bigint
+              - column:
+                  name: CALENDAR_NAME
+                  type: varchar(200)
+              - column:
+                  name: MISFIRE_INSTR
+                  type: smallint
+              - column:
+                  name: JOB_DATA
+                  type: ${blob.type}
+        - addPrimaryKey:
+            tableName: QRTZ_TRIGGERS
+            columnNames: SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP
+            constraintName: PK_QRTZ_TRIGGERS
+        - addForeignKeyConstraint:
+            baseTableName: QRTZ_TRIGGERS
+            baseColumnNames: SCHED_NAME, JOB_NAME, JOB_GROUP
+            referencedTableName: QRTZ_JOB_DETAILS
+            referencedColumnNames: SCHED_NAME, JOB_NAME, JOB_GROUP
+            constraintName: FK_QRTZ_TRIGGERS_JOB_DETAILS
+        - createTable:
+            tableName: QRTZ_SIMPLE_TRIGGERS
+            remarks: 'Used for Quartz scheduler.'
+            columns:
+              - column:
+                  name: SCHED_NAME
+                  type: varchar(120)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: TRIGGER_NAME
+                  type: varchar(200)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: TRIGGER_GROUP
+                  type: varchar(200)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: REPEAT_COUNT
+                  type: bigint
+                  constraints:
+                    nullable: false
+              - column:
+                  name: REPEAT_INTERVAL
+                  type: bigint
+                  constraints:
+                    nullable: false
+              - column:
+                  name: TIMES_TRIGGERED
+                  type: bigint
+                  constraints:
+                    nullable: false
+        - addPrimaryKey:
+            tableName: QRTZ_SIMPLE_TRIGGERS
+            columnNames: SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP
+            constraintName: PK_QRTZ_SIMPLE_TRIGGERS
+        - addForeignKeyConstraint:
+            baseTableName: QRTZ_SIMPLE_TRIGGERS
+            baseColumnNames: SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP
+            referencedTableName: QRTZ_TRIGGERS
+            referencedColumnNames: SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP
+            constraintName: FK_QRTZ_SIMPLE_TRIGGERS_TRIGGERS
+        - createTable:
+            tableName: QRTZ_CRON_TRIGGERS
+            remarks: 'Used for Quartz scheduler.'
+            columns:
+              - column:
+                  name: SCHED_NAME
+                  type: varchar(120)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: TRIGGER_NAME
+                  type: varchar(200)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: TRIGGER_GROUP
+                  type: varchar(200)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: CRON_EXPRESSION
+                  type: varchar(120)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: TIME_ZONE_ID
+                  type: varchar(80)
+        - addPrimaryKey:
+            tableName: QRTZ_CRON_TRIGGERS
+            columnNames: SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP
+            constraintName: PK_QRTZ_CRON_TRIGGERS
+        - addForeignKeyConstraint:
+            baseTableName: QRTZ_CRON_TRIGGERS
+            baseColumnNames: SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP
+            referencedTableName: QRTZ_TRIGGERS
+            referencedColumnNames: SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP
+            constraintName: FK_QRTZ_CRON_TRIGGERS_TRIGGERS
+        - createTable:
+            tableName: QRTZ_SIMPROP_TRIGGERS
+            remarks: 'Used for Quartz scheduler.'
+            columns:
+              - column:
+                  name: SCHED_NAME
+                  type: varchar(120)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: TRIGGER_NAME
+                  type: varchar(200)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: TRIGGER_GROUP
+                  type: varchar(200)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: STR_PROP_1
+                  type: varchar(512)
+              - column:
+                  name: STR_PROP_2
+                  type: varchar(512)
+              - column:
+                  name: STR_PROP_3
+                  type: varchar(512)
+              - column:
+                  name: INT_PROP_1
+                  type: int
+              - column:
+                  name: INT_PROP_2
+                  type: int
+              - column:
+                  name: LONG_PROP_1
+                  type: bigint
+              - column:
+                  name: LONG_PROP_2
+                  type: bigint
+              - column:
+                  name: DEC_PROP_1
+                  type: numeric(13,4)
+              - column:
+                  name: DEC_PROP_2
+                  type: numeric(13,4)
+              - column:
+                  name: BOOL_PROP_1
+                  type: bool
+              - column:
+                  name: BOOL_PROP_2
+                  type: bool
+        - addPrimaryKey:
+            tableName: QRTZ_SIMPROP_TRIGGERS
+            columnNames: SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP
+            constraintName: PK_QRTZ_SIMPROP_TRIGGERS
+        - addForeignKeyConstraint:
+            baseTableName: QRTZ_SIMPROP_TRIGGERS
+            baseColumnNames: SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP
+            referencedTableName: QRTZ_TRIGGERS
+            referencedColumnNames: SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP
+            constraintName: FK_QRTZ_SIMPROP_TRIGGERS_TRIGGERS
+        - createTable:
+            tableName: QRTZ_BLOB_TRIGGERS
+            remarks: 'Used for Quartz scheduler.'
+            columns:
+              - column:
+                  name: SCHED_NAME
+                  type: varchar(120)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: TRIGGER_NAME
+                  type: varchar(200)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: TRIGGER_GROUP
+                  type: varchar(200)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: BLOB_DATA
+                  type: ${blob.type}
+        - addPrimaryKey:
+            tableName: QRTZ_BLOB_TRIGGERS
+            columnNames: SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP
+            constraintName: PK_QRTZ_BLOB_TRIGGERS
+        - addForeignKeyConstraint:
+            baseTableName: QRTZ_BLOB_TRIGGERS
+            baseColumnNames: SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP
+            referencedTableName: QRTZ_TRIGGERS
+            referencedColumnNames: SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP
+            constraintName: FK_QRTZ_BLOB_TRIGGERS_TRIGGERS
+        - createTable:
+            tableName: QRTZ_CALENDARS
+            remarks: 'Used for Quartz scheduler.'
+            columns:
+              - column:
+                  name: SCHED_NAME
+                  type: varchar(120)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: CALENDAR_NAME
+                  type: varchar(200)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: CALENDAR
+                  type: ${blob.type}
+                  constraints:
+                    nullable: false
+        - addPrimaryKey:
+            tableName: QRTZ_CALENDARS
+            columnNames: SCHED_NAME, CALENDAR_NAME
+            constraintName: PK_QRTZ_CALENDARS
+        - createTable:
+            tableName: QRTZ_PAUSED_TRIGGER_GRPS
+            remarks: 'Used for Quartz scheduler.'
+            columns:
+              - column:
+                  name: SCHED_NAME
+                  type: varchar(120)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: TRIGGER_GROUP
+                  type: varchar(200)
+                  constraints:
+                    nullable: false
+        - addPrimaryKey:
+            tableName: QRTZ_PAUSED_TRIGGER_GRPS
+            columnNames: SCHED_NAME, TRIGGER_GROUP
+            constraintName: PK_SCHED_NAME
+        - createTable:
+            tableName: QRTZ_FIRED_TRIGGERS
+            remarks: 'Used for Quartz scheduler.'
+            columns:
+              - column:
+                  name: SCHED_NAME
+                  type: varchar(120)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: ENTRY_ID
+                  type: varchar(95)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: TRIGGER_NAME
+                  type: varchar(200)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: TRIGGER_GROUP
+                  type: varchar(200)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: INSTANCE_NAME
+                  type: varchar(200)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: FIRED_TIME
+                  type: bigint
+                  constraints:
+                    nullable: false
+# Note: this column is not used on Quartz 2.1.x; it is used in 2.2.x, which recommends making it NOT NULL. I've made it
+# nullable since at the time of this migration we're still using 2.1.7; including it gives us an easy upgrade path in
+# the future.
+              - column:
+                  name: SCHED_TIME
+                  type: bigint
+              - column:
+                  name: PRIORITY
+                  type: integer
+                  constraints:
+                    nullable: false
+              - column:
+                  name: STATE
+                  type: varchar(16)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: JOB_NAME
+                  type: varchar(200)
+              - column:
+                  name: JOB_GROUP
+                  type: varchar(200)
+              - column:
+                  name: IS_NONCONCURRENT
+                  type: bool
+              - column:
+                  name: REQUESTS_RECOVERY
+                  type: bool
+        - addPrimaryKey:
+            tableName: QRTZ_FIRED_TRIGGERS
+            columnNames: SCHED_NAME, ENTRY_ID
+            constraintName: PK_QRTZ_FIRED_TRIGGERS
+        - createTable:
+            tableName: QRTZ_SCHEDULER_STATE
+            remarks: 'Used for Quartz scheduler.'
+            columns:
+              - column:
+                  name: SCHED_NAME
+                  type: varchar(120)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: INSTANCE_NAME
+                  type: varchar(200)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: LAST_CHECKIN_TIME
+                  type: bigint
+                  constraints:
+                    nullable: false
+              - column:
+                  name: CHECKIN_INTERVAL
+                  type: bigint
+                  constraints:
+                    nullable: false
+        - addPrimaryKey:
+            tableName: QRTZ_SCHEDULER_STATE
+            columnNames: SCHED_NAME, INSTANCE_NAME
+            constraintName: PK_QRTZ_SCHEDULER_STATE
+        - createTable:
+            tableName: QRTZ_LOCKS
+            remarks: 'Used for Quartz scheduler.'
+            columns:
+              - column:
+                  name: SCHED_NAME
+                  type: varchar(120)
+                  constraints:
+                    nullable: false
+              - column:
+                  name: LOCK_NAME
+                  type: varchar(40)
+                  constraints:
+                    nullable: false
+        - addPrimaryKey:
+            tableName: QRTZ_LOCKS
+            columnNames: SCHED_NAME, LOCK_NAME
+            constraintName: PK_QRTZ_LOCKS
+        - createIndex:
+            indexName: IDX_QRTZ_J_REQ_RECOVERY
+            tableName: QRTZ_JOB_DETAILS
+            columns:
+              - column:
+                  name: SCHED_NAME
+              - column:
+                  name: REQUESTS_RECOVERY
+        - createIndex:
+            indexName: IDX_QRTZ_J_GRP
+            tableName: QRTZ_JOB_DETAILS
+            columns:
+              - column:
+                  name: SCHED_NAME
+              - column:
+                  name: JOB_GROUP
+        - createIndex:
+            indexName: IDX_QRTZ_T_J
+            tableName: QRTZ_TRIGGERS
+            columns:
+              - column:
+                  name: SCHED_NAME
+              - column:
+                  name: JOB_NAME
+              - column:
+                  name: JOB_GROUP
+        - createIndex:
+            indexName: IDX_QRTZ_T_JG
+            tableName: QRTZ_TRIGGERS
+            columns:
+              - column:
+                  name: SCHED_NAME
+              - column:
+                  name: JOB_GROUP
+        - createIndex:
+            indexName: IDX_QRTZ_T_C
+            tableName: QRTZ_TRIGGERS
+            columns:
+              - column:
+                  name: SCHED_NAME
+              - column:
+                  name: CALENDAR_NAME
+        - createIndex:
+            indexName: IDX_QRTZ_T_G
+            tableName: QRTZ_TRIGGERS
+            columns:
+              - column:
+                  name: SCHED_NAME
+              - column:
+                  name: TRIGGER_GROUP
+        - createIndex:
+            indexName: IDX_QRTZ_T_STATE
+            tableName: QRTZ_TRIGGERS
+            columns:
+              - column:
+                  name: SCHED_NAME
+              - column:
+                  name: TRIGGER_STATE
+        - createIndex:
+            indexName: IDX_QRTZ_T_N_STATE
+            tableName: QRTZ_TRIGGERS
+            columns:
+              - column:
+                  name: SCHED_NAME
+              - column:
+                  name: TRIGGER_NAME
+              - column:
+                  name: TRIGGER_GROUP
+              - column:
+                  name: TRIGGER_STATE
+        - createIndex:
+            indexName: IDX_QRTZ_T_N_G_STATE
+            tableName: QRTZ_TRIGGERS
+            columns:
+              - column:
+                  name: SCHED_NAME
+              - column:
+                  name: TRIGGER_GROUP
+              - column:
+                  name: TRIGGER_STATE
+        - createIndex:
+            indexName: IDX_QRTZ_T_NEXT_FIRE_TIME
+            tableName: QRTZ_TRIGGERS
+            columns:
+              - column:
+                  name: SCHED_NAME
+              - column:
+                  name: NEXT_FIRE_TIME
+        - createIndex:
+            indexName: IDX_QRTZ_T_NFT_ST
+            tableName: QRTZ_TRIGGERS
+            columns:
+              - column:
+                  name: SCHED_NAME
+              - column:
+                  name: TRIGGER_STATE
+              - column:
+                  name: NEXT_FIRE_TIME
+        - createIndex:
+            indexName: IDX_QRTZ_T_NFT_MISFIRE
+            tableName: QRTZ_TRIGGERS
+            columns:
+              - column:
+                  name: SCHED_NAME
+              - column:
+                  name: MISFIRE_INSTR
+              - column:
+                  name: NEXT_FIRE_TIME
+        - createIndex:
+            indexName: IDX_QRTZ_T_NFT_ST_MISFIRE
+            tableName: QRTZ_TRIGGERS
+            columns:
+              - column:
+                  name: SCHED_NAME
+              - column:
+                  name: MISFIRE_INSTR
+              - column:
+                  name: NEXT_FIRE_TIME
+              - column:
+                  name: TRIGGER_STATE
+        - createIndex:
+            indexName: IDX_QRTZ_T_NFT_ST_MISFIRE_GRP
+            tableName: QRTZ_TRIGGERS
+            columns:
+              - column:
+                  name: SCHED_NAME
+              - column:
+                  name: MISFIRE_INSTR
+              - column:
+                  name: NEXT_FIRE_TIME
+              - column:
+                  name: TRIGGER_GROUP
+              - column:
+                  name: TRIGGER_STATE
+        - createIndex:
+            indexName: IDX_QRTZ_FT_TRIG_INST_NAME
+            tableName: QRTZ_FIRED_TRIGGERS
+            columns:
+              - column:
+                  name: SCHED_NAME
+              - column:
+                  name: INSTANCE_NAME
+        - createIndex:
+            indexName: IDX_QRTZ_FT_INST_JOB_REQ_RCVRY
+            tableName: QRTZ_FIRED_TRIGGERS
+            columns:
+              - column:
+                  name: SCHED_NAME
+              - column:
+                  name: INSTANCE_NAME
+              - column:
+                  name: REQUESTS_RECOVERY
+        - createIndex:
+            indexName: IDX_QRTZ_FT_J_G
+            tableName: QRTZ_FIRED_TRIGGERS
+            columns:
+              - column:
+                  name: SCHED_NAME
+              - column:
+                  name: JOB_NAME
+              - column:
+                  name: JOB_GROUP
+        - createIndex:
+            indexName: IDX_QRTZ_FT_JG
+            tableName: QRTZ_FIRED_TRIGGERS
+            columns:
+              - column:
+                  name: SCHED_NAME
+              - column:
+                  name: JOB_GROUP
+        - createIndex:
+            indexName: IDX_QRTZ_FT_T_G
+            tableName: QRTZ_FIRED_TRIGGERS
+            columns:
+              - column:
+                  name: SCHED_NAME
+              - column:
+                  name: TRIGGER_NAME
+              - column:
+                  name: TRIGGER_GROUP
+        - createIndex:
+            indexName: IDX_QRTZ_FT_TG
+            tableName: QRTZ_FIRED_TRIGGERS
+            columns:
+              - column:
+                  name: SCHED_NAME
+              - column:
+                  name: TRIGGER_GROUP
+
+# Forgot to get rid of the raw_table_id and raw_column_id columns when we dropped the tables they referenced in migration 87.
+
+  - changeSet:
+      id: 91
+      author: camsaul
+      comment: 'Added 0.30.0'
+      changes:
+        - dropColumn:
+            tableName: metabase_table
+            columnName: raw_table_id
+        - dropColumn:
+            tableName: metabase_field
+            columnName: raw_column_id
+
+# Create the TaskHistory table, intended to provide debugging info on our background/quartz processes
+  - changeSet:
+      id: 94
+      author: senior
+      comment: 'Added 0.31.0'
+      changes:
+        - createTable:
+            tableName: task_history
+            remarks: 'Timing and metadata info about background/quartz processes'
+            columns:
+              - column:
+                  name: id
+                  type: int
+                  autoIncrement: true
+                  constraints:
+                    primaryKey: true
+                    nullable: false
+              - column:
+                  name: task
+                  type: VARCHAR(254)
+                  remarks: 'Name of the task'
+                  constraints:
+                    nullable: false
+              # The sync tasks all have a db_id, but there are others that won't, such as the pulses
+              # task or task history cleanup. The way around this is to create a join table between
+              # TASK_HISTORY and METABASE_DATABASE, but that doesn't seem worth it right now.
+              - column:
+                  name: db_id
+                  type: integer
+              - column:
+                  name: started_at
+                  type: datetime
+                  constraints:
+                    nullable: false
+              - column:
+                  name: ended_at
+                  type: datetime
+                  constraints:
+                    nullable: false
+              - column:
+                  name: duration
+                  type: int
+                  constraints:
+                    nullable: false
+              - column:
+                  name: task_details
+                  remarks: 'JSON string with additional info on the task'
+                  type: text
+        - createIndex:
+            indexName: idx_task_history_end_time
+            tableName: task_history
+            columns:
+              - column:
+                  name: ended_at
+        - createIndex:
+            indexName: idx_task_history_db_id
+            tableName: task_history
+            columns:
+              - column:
+                  name: db_id
+# Before this changeset, the databasechangelog table didn't include any uniqueness constraing for the databasechangelog
+# table. Not having anything that uniquely identifies a row can cause issues for database replication. In earlier
+# versions of Liquibase the uniquenes constraint was (ID, AUTHOR, FILENAME) but that was dropped
+# (https://liquibase.jira.com/browse/CORE-1909) as some as the combination of the three columns caused issues on some
+# databases. We only support PostgreSQL, MySQL and H2 which doesn't have that issue. This changeset puts back that
+# uniqueness constraint since the issue shouldn't affect us and it will allow replication without the user needed to
+# add their own constraint.
+  - changeSet:
+      id: 95
+      author: senior
+      comment: 'Added 0.31.0'
+      changes:
+        - addUniqueConstraint:
+            columnNames: id, author, filename
+            constraintName: idx_databasechangelog_id_author_filename
+            tableName: DATABASECHANGELOG
diff --git a/resources/quartz.properties b/resources/quartz.properties
index 3f763e8999196f492b53672cc082aaf8dc713401..6c78d6564c4e139b32f0b5ad8486ca69d42071b1 100644
--- a/resources/quartz.properties
+++ b/resources/quartz.properties
@@ -1,8 +1,23 @@
 org.quartz.scheduler.instanceName = MetabaseScheduler
+org.quartz.scheduler.instanceId = AUTO
 org.quartz.threadPool.threadCount = 4
 
 # Don't phone home
 org.quartz.scheduler.skipUpdateCheck: true
+org.quartz.scheduler.classLoadHelper.class=metabase.task.DynamicClassLoadHelper
+
+# Use the JDBC backend so we can cluster when running multiple instances!
+# See http://www.quartz-scheduler.org/documentation/quartz-2.x/configuration/ConfigJDBCJobStoreClustering
+#     http://www.quartz-scheduler.org/documentation/quartz-2.x/configuration/ConfigJobStoreTX.html
+#     http://www.quartz-scheduler.org/documentation/quartz-2.x/configuration/ConfigDataSources.html
+#     http://clojurequartz.info/articles/durable_quartz_stores.html
+org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
+org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
+org.quartz.jobStore.dataSource=db
+
+org.quartz.jobStore.isClustered = true
+
+org.quartz.dataSource.db.validationQuery=SELECT 1
 
 # Useful for debugging when Quartz jobs run and when they misfire
 #org.quartz.plugin.triggHistory.class = org.quartz.plugins.history.LoggingTriggerHistoryPlugin
diff --git a/src/metabase/api/alert.clj b/src/metabase/api/alert.clj
index 7b4bdce009c49737e83db5abde75c61f839e08c7..276bb3908d377f05d60c943828babbd38f297bb4 100644
--- a/src/metabase/api/alert.clj
+++ b/src/metabase/api/alert.clj
@@ -1,6 +1,7 @@
 (ns metabase.api.alert
   "/api/alert endpoints"
   (:require [clojure.data :as data]
+            [clojure.tools.logging :as log]
             [compojure.core :refer [DELETE GET POST PUT]]
             [medley.core :as m]
             [metabase
@@ -13,33 +14,33 @@
              [card :refer [Card]]
              [interface :as mi]
              [pulse :as pulse :refer [Pulse]]]
-            [metabase.util.schema :as su]
+            [metabase.util
+             [i18n :refer [tru]]
+             [schema :as su]]
             [schema.core :as s]
-            [toucan.db :as db]))
-
-(defn- add-read-only-flag [alerts]
-  (for [alert alerts
-        :let  [can-read?  (mi/can-read? alert)
-               can-write? (mi/can-write? alert)]
-        :when (or can-read?
-                  can-write?)]
-    (assoc alert :read_only (not can-write?))))
+            [toucan
+             [db :as db]
+             [hydrate :refer [hydrate]]]))
 
 (api/defendpoint GET "/"
   "Fetch all alerts"
-  []
-  (add-read-only-flag (pulse/retrieve-alerts)))
+  [archived]
+  {archived (s/maybe su/BooleanString)}
+  (as-> (pulse/retrieve-alerts {:archived? (Boolean/parseBoolean archived)}) <>
+    (filter mi/can-read? <>)
+    (hydrate <> :can_write)))
 
 (api/defendpoint GET "/question/:id"
   "Fetch all questions for the given question (`Card`) id"
   [id]
-  (add-read-only-flag (if api/*is-superuser?*
-                        (pulse/retrieve-alerts-for-cards id)
-                        (pulse/retrieve-user-alerts-for-card id api/*current-user-id*))))
+  (-> (if api/*is-superuser?*
+        (pulse/retrieve-alerts-for-cards id)
+        (pulse/retrieve-user-alerts-for-card id api/*current-user-id*))
+      (hydrate :can_write)))
 
 (defn- only-alert-keys [request]
   (u/select-keys-when request
-    :present [:alert_condition :alert_first_only :alert_above_goal]))
+    :present [:alert_condition :alert_first_only :alert_above_goal :archived]))
 
 (defn- email-channel [alert]
   (m/find-first #(= :email (:channel_type %)) (:channels alert)))
@@ -134,13 +135,14 @@
 
 (api/defendpoint PUT "/:id"
   "Update a `Alert` with ID."
-  [id :as {{:keys [alert_condition card channels alert_first_only alert_above_goal card channels]
+  [id :as {{:keys [alert_condition card channels alert_first_only alert_above_goal card channels archived]
             :as alert-updates} :body}]
   {alert_condition     (s/maybe pulse/AlertConditions)
    alert_first_only    (s/maybe s/Bool)
    alert_above_goal    (s/maybe s/Bool)
    card                (s/maybe pulse/CardRef)
-   channels            (s/maybe (su/non-empty [su/Map]))}
+   channels            (s/maybe (su/non-empty [su/Map]))
+   archived            (s/maybe s/Bool)}
   ;; fethc the existing Alert in the DB
   (let [alert-before-update (api/check-404 (pulse/retrieve-alert id))]
     ;; check permissions as needed.
@@ -213,17 +215,14 @@
         (messages/send-admin-deleted-your-alert! alert creator @api/*current-user*)))))
 
 (api/defendpoint DELETE "/:id"
-  "Remove an alert"
+  "Delete an Alert. (DEPRECATED -- don't delete a Alert anymore -- archive it instead.)"
   [id]
+  (log/warn (tru "DELETE /api/alert/:id is deprecated. Instead, change its `archived` value via PUT /api/alert/:id."))
   (api/let-404 [alert (pulse/retrieve-alert id)]
-    (api/check-superuser)
-
+    (api/write-check Pulse id)
     (db/delete! Pulse :id id)
-
     (events/publish-event! :alert-delete (assoc alert :actor_id api/*current-user-id*))
-
-    (notify-on-delete-if-needed! alert)
-
-    api/generic-204-no-content))
+    (notify-on-delete-if-needed! alert))
+  api/generic-204-no-content)
 
 (api/define-routes)
diff --git a/src/metabase/api/automagic_dashboards.clj b/src/metabase/api/automagic_dashboards.clj
index 023baf6329823f1d1f908b35d27e3f41c3f4e04a..6856f8e0eff73557af3ee7f9dcdd2fe88fa7a009 100644
--- a/src/metabase/api/automagic_dashboards.clj
+++ b/src/metabase/api/automagic_dashboards.clj
@@ -4,12 +4,11 @@
             [compojure.core :refer [GET POST]]
             [metabase.api.common :as api]
             [metabase.automagic-dashboards
-             [comparison :as magic.comparison]
-             [core :as magic]
+             [comparison :refer [comparison-dashboard]]
+             [core :refer [candidate-tables automagic-analysis]]
              [rules :as rules]]
             [metabase.models
              [card :refer [Card]]
-             [dashboard :as dashboard :refer [Dashboard]]
              [database :refer [Database]]
              [field :refer [Field]]
              [metric :refer [Metric]]
@@ -18,11 +17,11 @@
              [segment :refer [Segment]]
              [table :refer [Table]]]
             [metabase.models.query.permissions :as query-perms]
-            [metabase.util.schema :as su]
-            [puppetlabs.i18n.core :refer [tru]]
+            [metabase.util
+             [i18n :refer [tru]]
+             [schema :as su]]
             [ring.util.codec :as codec]
-            [schema.core :as s]
-            [toucan.hydrate :refer [hydrate]]))
+            [schema.core :as s]))
 
 (def ^:private Show
   (su/with-api-error-message (s/maybe (s/enum "all"))
@@ -59,102 +58,10 @@
   [id]
   (-> (Database id)
       api/read-check
-      magic/candidate-tables))
+      candidate-tables))
 
 ;; ----------------------------------------- API Endpoints for viewing a transient dashboard ----------------
 
-(api/defendpoint GET "/table/:id"
-  "Return an automagic dashboard for table with id `ìd`."
-  [id show]
-  {show Show}
-  (-> id Table api/read-check (magic/automagic-analysis {:show (keyword show)})))
-
-(api/defendpoint GET "/table/:id/rule/:prefix/:rule"
-  "Return an automagic dashboard for table with id `ìd` using rule `rule`."
-  [id prefix rule show]
-  {show   Show
-   prefix Prefix
-   rule   Rule}
-  (-> id
-      Table
-      api/read-check
-      (magic/automagic-analysis
-       {:rule ["table" prefix rule]
-        :show (keyword show)})))
-
-(api/defendpoint GET "/segment/:id"
-  "Return an automagic dashboard analyzing segment with id `id`."
-  [id show]
-  {show Show}
-  (-> id Segment api/read-check (magic/automagic-analysis {:show (keyword show)})))
-
-(api/defendpoint GET "/segment/:id/rule/:prefix/:rule"
-  "Return an automagic dashboard analyzing segment with id `id`. using rule `rule`."
-  [id prefix rule show]
-  {show   Show
-   prefix Prefix
-   rule   Rule}
-  (-> id
-      Segment
-      api/read-check
-      (magic/automagic-analysis
-       {:rule ["table" prefix rule]
-        :show (keyword show)})))
-
-(api/defendpoint GET "/question/:id/cell/:cell-query"
-  "Return an automagic dashboard analyzing cell in question  with id `id` defined by
-   query `cell-querry`."
-  [id cell-query show]
-  {show       Show
-   cell-query Base64EncodedJSON}
-  (-> id
-      Card
-      api/read-check
-      (magic/automagic-analysis {:show       (keyword show)
-                                 :cell-query (decode-base64-json cell-query)})))
-
-(api/defendpoint GET "/question/:id/cell/:cell-query/rule/:prefix/:rule"
-  "Return an automagic dashboard analyzing cell in question  with id `id` defined by
-   query `cell-querry` using rule `rule`."
-  [id cell-query prefix rule show]
-  {show       Show
-   prefix     Prefix
-   rule       Rule
-   cell-query Base64EncodedJSON}
-  (-> id
-      Card
-      api/read-check
-      (magic/automagic-analysis {:show       (keyword show)
-                                 :rule       ["table" prefix rule]
-                                 :cell-query (decode-base64-json cell-query)})))
-
-(api/defendpoint GET "/metric/:id"
-  "Return an automagic dashboard analyzing metric with id `id`."
-  [id show]
-  {show Show}
-  (-> id Metric api/read-check (magic/automagic-analysis {:show (keyword show)})))
-
-(api/defendpoint GET "/field/:id"
-  "Return an automagic dashboard analyzing field with id `id`."
-  [id show]
-  {show Show}
-  (-> id Field api/read-check (magic/automagic-analysis {:show (keyword show)})))
-
-(api/defendpoint GET "/question/:id"
-  "Return an automagic dashboard analyzing question with id `id`."
-  [id show]
-  {show Show}
-  (-> id Card api/read-check (magic/automagic-analysis {:show (keyword show)})))
-
-(api/defendpoint GET "/question/:id/rule/:prefix/:rule"
-  "Return an automagic dashboard analyzing question with id `id` using rule `rule`."
-  [id prefix rule show]
-  {show Show
-   prefix Prefix
-   rule   Rule}
-  (-> id Card api/read-check (magic/automagic-analysis {:show (keyword show)
-                                                        :rule ["table" prefix rule]})))
-
 (defn- adhoc-query-read-check
   [query]
   (api/check-403 (perms/set-has-full-permissions-for-set?
@@ -162,105 +69,138 @@
                    (query-perms/perms-set (:dataset_query query) :throw-exceptions)))
   query)
 
-(api/defendpoint GET "/adhoc/:query"
-  "Return an automagic dashboard analyzing ad hoc query."
-  [query show]
-  {show  Show
-   query Base64EncodedJSON}
-  (-> query
-      decode-base64-json
-      query/adhoc-query
-      adhoc-query-read-check
-      (magic/automagic-analysis {:show (keyword show)})))
+(defn- ensure-int
+  [x]
+  (if (string? x)
+    (Integer/parseInt x)
+    x))
+
+(def ^:private ->entity
+  {"table"    (comp api/read-check Table ensure-int)
+   "segment"  (comp api/read-check Segment ensure-int)
+   "question" (comp api/read-check Card ensure-int)
+   "adhoc"    (comp adhoc-query-read-check query/adhoc-query decode-base64-json)
+   "metric"   (comp api/read-check Metric ensure-int)
+   "field"    (comp api/read-check Field ensure-int)})
+
+(def ^:private Entity
+  (su/with-api-error-message
+      (apply s/enum (keys ->entity))
+    (tru "Invalid entity type")))
+
+(def ^:private ComparisonEntity
+  (su/with-api-error-message
+      (s/enum "segment" "adhoc" "table")
+    (tru "Invalid comparison entity type. Can only be one of \"table\", \"segment\", or \"adhoc\"")))
 
-(api/defendpoint GET "/adhoc/:query/rule/:prefix/:rule"
-  "Return an automagic dashboard analyzing ad hoc query."
-  [query prefix rule show]
+(api/defendpoint GET "/:entity/:entity-id-or-query"
+  "Return an automagic dashboard for entity `entity` with id `ìd`."
+  [entity entity-id-or-query show]
   {show   Show
-   query  Base64EncodedJSON
+   entity Entity}
+  (-> entity-id-or-query ((->entity entity)) (automagic-analysis {:show (keyword show)})))
+
+(api/defendpoint GET "/:entity/:entity-id-or-query/rule/:prefix/:rule"
+  "Return an automagic dashboard for entity `entity` with id `ìd` using rule `rule`."
+  [entity entity-id-or-query prefix rule show]
+  {entity Entity
+   show   Show
    prefix Prefix
    rule   Rule}
-  (-> query
-      decode-base64-json
-      query/adhoc-query
-      adhoc-query-read-check
-      (magic/automagic-analysis {:show (keyword show)
-                                 :rule ["table" prefix rule]})))
+  (-> entity-id-or-query ((->entity entity)) (automagic-analysis {:show (keyword show)
+                                                                  :rule ["table" prefix rule]})))
 
-(api/defendpoint GET "/adhoc/:query/cell/:cell-query"
-  "Return an automagic dashboard analyzing ad hoc query."
-  [query cell-query show]
-  {show       Show
-   query      Base64EncodedJSON
+(api/defendpoint GET "/:entity/:entity-id-or-query/cell/:cell-query"
+  "Return an automagic dashboard analyzing cell in  automagic dashboard for entity `entity`
+   defined by
+   query `cell-querry`."
+  [entity entity-id-or-query cell-query show]
+  {entity     Entity
+   show       Show
    cell-query Base64EncodedJSON}
-  (let [query      (decode-base64-json query)
-        cell-query (decode-base64-json cell-query)]
-    (-> query
-        query/adhoc-query
-        adhoc-query-read-check
-        (magic/automagic-analysis {:show       (keyword show)
-                                   :cell-query cell-query}))))
+  (-> entity-id-or-query
+      ((->entity entity))
+      (automagic-analysis {:show       (keyword show)
+                           :cell-query (decode-base64-json cell-query)})))
 
-(api/defendpoint GET "/adhoc/:query/cell/:cell-query/rule/:prefix/:rule"
+(api/defendpoint GET "/:entity/:entity-id-or-query/cell/:cell-query/rule/:prefix/:rule"
   "Return an automagic dashboard analyzing cell in question  with id `id` defined by
    query `cell-querry` using rule `rule`."
-  [query cell-query prefix rule show]
-  {show       Show
+  [entity entity-id-or-query cell-query prefix rule show]
+  {entity     Entity
+   show       Show
    prefix     Prefix
    rule       Rule
-   query      Base64EncodedJSON
    cell-query Base64EncodedJSON}
-  (let [query      (decode-base64-json query)
-        cell-query (decode-base64-json cell-query)]
-    (-> query
-        query/adhoc-query
-        adhoc-query-read-check
-        (magic/automagic-analysis {:show       (keyword show)
-                                   :cell-query cell-query
-                                   :rule       ["table" prefix rule]}))))
-
-(def ^:private valid-comparison-pair?
-  #{["segment" "segment"]
-    ["segment" "table"]
-    ["segment" "adhoc"]
-    ["table" "segment"]
-    ["table" "adhoc"]
-    ["adhoc" "table"]
-    ["adhoc" "segment"]
-    ["adhoc" "adhoc"]})
-
-(defmulti
-  ^{:private true
-    :doc "Turn `x` into segment-like."
-    :arglists '([x])}
-  ->segment (comp keyword :type))
-
-(defmethod ->segment :table
-  [{:keys [id]}]
-  (-> id Table api/read-check))
-
-(defmethod ->segment :segment
-  [{:keys [id]}]
-  (-> id Segment api/read-check))
-
-(defmethod ->segment :adhoc
-  [{:keys [query name]}]
-  (-> query
-      query/adhoc-query
-      (assoc :name name)))
-
-(api/defendpoint POST "/compare"
-  "Return an automagic comparison dashboard based on given dashboard."
-  [:as {{:keys [dashboard left right]} :body}]
-  (api/read-check (valid-comparison-pair? (map :type [left right])))
-  (magic.comparison/comparison-dashboard (if (number? dashboard)
-                                           (-> (Dashboard dashboard)
-                                               api/read-check
-                                               (hydrate [:ordered_cards
-                                                         :card
-                                                         :series]))
-                                           dashboard)
-                                         (->segment left)
-                                         (->segment right)))
+  (-> entity-id-or-query
+      ((->entity entity))
+      (automagic-analysis {:show       (keyword show)
+                           :rule       ["table" prefix rule]
+                           :cell-query (decode-base64-json cell-query)})))
+
+(api/defendpoint GET "/:entity/:entity-id-or-query/compare/:comparison-entity/:comparison-entity-id-or-query"
+  "Return an automagic comparison dashboard for entity `entity` with id `ìd` compared with entity
+   `comparison-entity` with id `comparison-entity-id-or-query.`"
+  [entity entity-id-or-query show comparison-entity comparison-entity-id-or-query]
+  {show              Show
+   entity            Entity
+   comparison-entity ComparisonEntity}
+  (let [left      ((->entity entity) entity-id-or-query)
+        right     ((->entity comparison-entity) comparison-entity-id-or-query)
+        dashboard (automagic-analysis left {:show         (keyword show)
+                                            :query-filter nil
+                                            :comparison?  true})]
+    (comparison-dashboard dashboard left right {})))
+
+(api/defendpoint GET "/:entity/:entity-id-or-query/rule/:prefix/:rule/compare/:comparison-entity/:comparison-entity-id-or-query"
+  "Return an automagic comparison dashboard for entity `entity` with id `ìd` using rule `rule`;
+   compared with entity `comparison-entity` with id `comparison-entity-id-or-query.`."
+  [entity entity-id-or-query prefix rule show comparison-entity comparison-entity-id-or-query]
+  {entity            Entity
+   show              Show
+   prefix            Prefix
+   rule              Rule
+   comparison-entity ComparisonEntity}
+  (let [left      ((->entity entity) entity-id-or-query)
+        right     ((->entity comparison-entity) comparison-entity-id-or-query)
+        dashboard (automagic-analysis left {:show         (keyword show)
+                                            :rule         ["table" prefix rule]
+                                            :query-filter nil
+                                            :comparison?  true})]
+    (comparison-dashboard dashboard left right {})))
+
+(api/defendpoint GET "/:entity/:entity-id-or-query/cell/:cell-query/compare/:comparison-entity/:comparison-entity-id-or-query"
+  "Return an automagic comparison dashboard for cell in automagic dashboard for entity `entity`
+   with id `ìd` defined by query `cell-querry`; compared with entity `comparison-entity` with id
+   `comparison-entity-id-or-query.`."
+  [entity entity-id-or-query cell-query show comparison-entity comparison-entity-id-or-query]
+  {entity            Entity
+   show              Show
+   cell-query        Base64EncodedJSON
+   comparison-entity ComparisonEntity}
+  (let [left      ((->entity entity) entity-id-or-query)
+        right     ((->entity comparison-entity) comparison-entity-id-or-query)
+        dashboard (automagic-analysis left {:show         (keyword show)
+                                            :query-filter nil
+                                            :comparison?  true})]
+    (comparison-dashboard dashboard left right {:left {:cell-query (decode-base64-json cell-query)}})))
+
+(api/defendpoint GET "/:entity/:entity-id-or-query/cell/:cell-query/rule/:prefix/:rule/compare/:comparison-entity/:comparison-entity-id-or-query"
+  "Return an automagic comparison dashboard for cell in automagic dashboard for entity `entity`
+   with id `ìd` defined by query `cell-querry` using rule `rule`; compared with entity
+   `comparison-entity` with id `comparison-entity-id-or-query.`."
+  [entity entity-id-or-query cell-query prefix rule show comparison-entity comparison-entity-id-or-query]
+  {entity            Entity
+   show              Show
+   prefix            Prefix
+   rule              Rule
+   cell-query        Base64EncodedJSON
+   comparison-entity ComparisonEntity}
+  (let [left      ((->entity entity) entity-id-or-query)
+        right     ((->entity comparison-entity) comparison-entity-id-or-query)
+        dashboard (automagic-analysis left {:show         (keyword show)
+                                            :rule         ["table" prefix rule]
+                                            :query-filter nil})]
+    (comparison-dashboard dashboard left right {:left {:cell-query (decode-base64-json cell-query)}})))
 
 (api/define-routes)
diff --git a/src/metabase/api/card.clj b/src/metabase/api/card.clj
index a4b1456352c611b7dbdca9d2663b922b8634d8fc..fc9cf849ab7faf1a2edd506f3eb2b8fd40253e9d 100644
--- a/src/metabase/api/card.clj
+++ b/src/metabase/api/card.clj
@@ -32,8 +32,10 @@
             [metabase.query-processor.middleware
              [cache :as cache]
              [results-metadata :as results-metadata]]
-            [metabase.util.schema :as su]
-            [puppetlabs.i18n.core :refer [trs]]
+            [metabase.sync.analyze.query-results :as qr]
+            [metabase.util
+             [i18n :refer [trs tru]]
+             [schema :as su]]
             [schema.core :as s]
             [toucan
              [db :as db]
@@ -180,7 +182,7 @@
 ;; we'll also pass a simple checksum and have the frontend pass it back to us.  See the QP `results-metadata`
 ;; middleware namespace for more details
 
-(s/defn ^:private result-metadata-for-query :- results-metadata/ResultsMetadata
+(s/defn ^:private result-metadata-for-query :- qr/ResultsMetadata
   "Fetch the results metadata for a QUERY by running the query and seeing what the QP gives us in return.
    This is obviously a bit wasteful so hopefully we can avoid having to do this."
   [query]
@@ -191,12 +193,12 @@
                    (u/pprint-to-str 'red results))
         (get-in results [:data :results_metadata :columns])))))
 
-(s/defn ^:private result-metadata :- (s/maybe results-metadata/ResultsMetadata)
+(s/defn ^:private result-metadata :- (s/maybe qr/ResultsMetadata)
   "Get the right results metadata for this CARD. We'll check to see whether the METADATA passed in seems valid;
    otherwise we'll run the query ourselves to get the right values."
   [query metadata checksum]
   (let [valid-metadata? (and (results-metadata/valid-checksum? metadata checksum)
-                             (s/validate results-metadata/ResultsMetadata metadata))]
+                             (s/validate qr/ResultsMetadata metadata))]
     (log/info (str "Card results metadata passed in to API is "
                    (cond
                      valid-metadata? "VALID. Thanks!"
@@ -216,7 +218,7 @@
    visualization_settings su/Map
    collection_id          (s/maybe su/IntGreaterThanZero)
    collection_position    (s/maybe su/IntGreaterThanZero)
-   result_metadata        (s/maybe results-metadata/ResultsMetadata)
+   result_metadata        (s/maybe qr/ResultsMetadata)
    metadata_checksum      (s/maybe su/NonBlankString)}
   ;; check that we have permissions to run the query that we're trying to save
   (api/check-403 (perms/set-has-full-permissions-for-set? @api/*current-user-permissions-set*
@@ -395,7 +397,7 @@
    embedding_params       (s/maybe su/EmbeddingParams)
    collection_id          (s/maybe su/IntGreaterThanZero)
    collection_position    (s/maybe su/IntGreaterThanZero)
-   result_metadata        (s/maybe results-metadata/ResultsMetadata)
+   result_metadata        (s/maybe qr/ResultsMetadata)
    metadata_checksum      (s/maybe su/NonBlankString)}
   (let [card-before-update (api/write-check Card id)]
     ;; Do various permissions checks
@@ -434,10 +436,9 @@
 ;; TODO - Pretty sure this endpoint is not actually used any more, since Cards are supposed to get archived (via PUT
 ;;        /api/card/:id) instead of deleted.  Should we remove this?
 (api/defendpoint DELETE "/:id"
-  "Delete a `Card`."
+  "Delete a Card. (DEPRECATED -- don't delete a Card anymore -- archive it instead.)"
   [id]
-  (log/warn (str "DELETE /api/card/:id is deprecated. Instead of deleting a Card, "
-                 "you should change its `archived` value via PUT /api/card/:id."))
+  (log/warn (tru "DELETE /api/card/:id is deprecated. Instead, change its `archived` value via PUT /api/card/:id."))
   (let [card (api/write-check Card id)]
     (db/delete! Card :id id)
     (events/publish-event! :card-delete (assoc card :actor_id api/*current-user-id*)))
@@ -555,10 +556,11 @@
                   (u/emoji "💾"))
         ttl-seconds))))
 
-(defn- query-for-card [card parameters constraints]
+(defn- query-for-card [card parameters constraints middleware]
   (let [query (assoc (:dataset_query card)
                 :constraints constraints
-                :parameters  parameters)
+                :parameters  parameters
+                :middleware  middleware)
         ttl   (when (public-settings/enable-query-caching)
                 (or (:cache_ttl card)
                     (query-magic-ttl query)))]
@@ -567,12 +569,12 @@
 (defn run-query-for-card
   "Run the query for Card with PARAMETERS and CONSTRAINTS, and return results in the usual format."
   {:style/indent 1}
-  [card-id & {:keys [parameters constraints context dashboard-id]
+  [card-id & {:keys [parameters constraints context dashboard-id middleware]
               :or   {constraints qp/default-query-constraints
                      context     :question}}]
   {:pre [(u/maybe? sequential? parameters)]}
   (let [card    (api/read-check (Card card-id))
-        query   (query-for-card card parameters constraints)
+        query   (query-for-card card parameters constraints middleware)
         options {:executed-by  api/*current-user-id*
                  :context      context
                  :card-id      card-id
@@ -598,7 +600,8 @@
       (run-query-for-card card-id
         :parameters  (json/parse-string parameters keyword)
         :constraints nil
-        :context     (dataset-api/export-format->context export-format)))))
+        :context     (dataset-api/export-format->context export-format)
+        :middleware  {:skip-results-metadata? true}))))
 
 
 ;;; ----------------------------------------------- Sharing is Caring ------------------------------------------------
diff --git a/src/metabase/api/collection.clj b/src/metabase/api/collection.clj
index 4493b5cd518ca6818ee5a98be600d0dbb2e4e065..240979442b8474f9ef95cc1443aad76aaa0758ea 100644
--- a/src/metabase/api/collection.clj
+++ b/src/metabase/api/collection.clj
@@ -14,12 +14,13 @@
              [pulse :as pulse :refer [Pulse]]]
             [metabase.util :as u]
             [metabase.util.schema :as su]
-            [puppetlabs.i18n.core :refer [tru]]
             [schema.core :as s]
             [toucan
              [db :as db]
              [hydrate :refer [hydrate]]]))
 
+(declare root-collection)
+
 (api/defendpoint GET "/"
   "Fetch a list of all Collections that the current user has read permissions for (`:can_write` is returned as an
   additional property of each Collection so you can tell which of these you have write permissions for.)
@@ -28,10 +29,18 @@
   `?archived=true`."
   [archived]
   {archived (s/maybe su/BooleanString)}
-  (as-> (db/select Collection :archived (Boolean/parseBoolean archived)
-                   {:order-by [[:%lower.name :asc]]}) collections
-    (filter mi/can-read? collections)
-    (hydrate collections :can_write)))
+  (let [archived? (Boolean/parseBoolean archived)]
+    (as-> (db/select Collection :archived archived?
+                     {:order-by [[:%lower.name :asc]]}) collections
+      (filter mi/can-read? collections)
+      ;; include Root Collection at beginning or results if archived isn't `true`
+      (if archived?
+        collections
+        (cons (root-collection) collections))
+      (hydrate collections :can_write)
+      ;; remove the :metabase.models.collection/is-root? tag since FE doesn't need it
+      (for [collection collections]
+        (dissoc collection ::collection/is-root?)))))
 
 
 ;;; --------------------------------- Fetching a single Collection & its 'children' ----------------------------------
@@ -53,7 +62,7 @@
 
 (defmethod fetch-collection-children :card
   [_ collection {:keys [archived?]}]
-  (-> (db/select [Card :id :name :description :collection_position]
+  (-> (db/select [Card :id :name :description :collection_position :display]
         :collection_id (:id collection)
         :archived      archived?)
       (hydrate :favorite)))
@@ -66,17 +75,17 @@
 
 (defmethod fetch-collection-children :pulse
   [_ collection {:keys [archived?]}]
-  ;; Pulses currently cannot be archived -- so if it's specified don't fetch Pulses for now
-  (when-not archived?
-    (db/select [Pulse :id :name :collection_position]
-      :collection_id   (:id collection)
-      ;; exclude Alerts
-      :alert_condition nil)))
+  (db/select [Pulse :id :name :collection_position]
+    :collection_id   (:id collection)
+    :archived        archived?
+    ;; exclude Alerts
+    :alert_condition nil))
 
 (defmethod fetch-collection-children :collection
   [_ collection {:keys [archived?]}]
-  (for [child-collection (collection/effective-children collection [:= :archived archived?])]
-    (assoc child-collection :model "collection")))
+  (-> (for [child-collection (collection/effective-children collection [:= :archived archived?])]
+        (assoc child-collection :model "collection"))
+      (hydrate :can_write)))
 
 (s/defn ^:private collection-children
   "Fetch a sequence of 'child' objects belonging to a Collection, filtered using `options`."
@@ -95,7 +104,7 @@
   Works for either a normal Collection or the Root Collection."
   [collection :- collection/CollectionWithLocationAndIDOrRoot]
   (-> collection
-      (hydrate :effective_location :effective_ancestors :can_write)))
+      (hydrate :parent_id :effective_location [:effective_ancestors :can_write] :can_write)))
 
 (s/defn ^:private collection-items
   "Return items in the Collection, restricted by `children-options`.
@@ -121,18 +130,16 @@
     {:model     (keyword model)
      :archived? (Boolean/parseBoolean archived)}))
 
+
 ;;; -------------------------------------------- GET /api/collection/root --------------------------------------------
 
+(defn- root-collection []
+  (collection-detail (collection/root-collection-with-ui-details)))
+
 (api/defendpoint GET "/root"
   "Return the 'Root' Collection object with standard details added"
   []
-  (-> (collection-detail collection/root-collection)
-      ;; add in some things for the FE to display since the 'Root' Collection isn't real and wouldn't normally have
-      ;; these things
-      (assoc
-          :name (tru "Saved items")
-          :id   "root")
-      (dissoc ::collection/is-root?)))
+  (dissoc (root-collection) ::collection/is-root?))
 
 (api/defendpoint GET "/root/items"
   "Fetch objects that the current user should see at their root level. As mentioned elsewhere, the 'Root' Collection
diff --git a/src/metabase/api/common.clj b/src/metabase/api/common.clj
index 06226f682c8f470f9b5333285273df33630bb8e6..b047d565b33993f5369ffe08263cde7ce18f4181 100644
--- a/src/metabase/api/common.clj
+++ b/src/metabase/api/common.clj
@@ -14,8 +14,9 @@
              [util :as u]]
             [metabase.api.common.internal :refer :all]
             [metabase.models.interface :as mi]
-            [metabase.util.schema :as su]
-            [puppetlabs.i18n.core :refer [trs tru]]
+            [metabase.util
+             [i18n :as ui18n :refer [trs tru]]
+             [schema :as su]]
             [ring.core.protocols :as protocols]
             [ring.util.response :as response]
             [schema.core :as s]
@@ -75,9 +76,10 @@
                                       [code-or-code-message-pair rest-args]
                                       [[code-or-code-message-pair (first rest-args)] (rest rest-args)])]
      (when-not tst
-       (throw (if (map? message)
-                (ex-info (:message message) (assoc message :status-code code))
-                (ex-info message            {:status-code code}))))
+       (throw (if (and (map? message)
+                       (not (ui18n/localized-string? message)))
+                (ui18n/ex-info (:message message) (assoc message :status-code code))
+                (ui18n/ex-info message            {:status-code code}))))
      (if (empty? rest-args) tst
          (recur (first rest-args) (second rest-args) (drop 2 rest-args))))))
 
@@ -100,7 +102,7 @@
 (defn throw-invalid-param-exception
   "Throw an `ExceptionInfo` that contains information about an invalid API params in the expected format."
   [field-name message]
-  (throw (ex-info (tru "Invalid field: {0}" field-name)
+  (throw (ui18n/ex-info (tru "Invalid field: {0}" field-name)
            {:status-code 400
             :errors      {(keyword field-name) message}})))
 
@@ -189,7 +191,7 @@
   [400 (tru "Invalid Request.")])
 
 (defn check-400
-  "Throw a `400` if ARG is `false` or `nil`, otherwise return as-is."
+  "Throw a `400` if `arg` is `false` or `nil`, otherwise return as-is."
   [arg]
   (check arg generic-400))
 
@@ -204,7 +206,7 @@
   [404 (tru "Not found.")])
 
 (defn check-404
-  "Throw a `404` if ARG is `false` or `nil`, otherwise return as-is."
+  "Throw a `404` if `arg` is `false` or `nil`, otherwise return as-is."
   [arg]
   (check arg generic-404))
 
@@ -216,23 +218,23 @@
 
 ;; #### GENERIC 403 RESPONSE HELPERS
 ;; If you can't be bothered to write a custom error message
-(def ^:private generic-403
+(defn- generic-403 []
   [403 (tru "You don''t have permissions to do that.")])
 
 (defn check-403
-  "Throw a `403` if ARG is `false` or `nil`, otherwise return as-is."
+  "Throw a `403` (no permissions) if `arg` is `false` or `nil`, otherwise return as-is."
   [arg]
-  (check arg generic-403))
+  (check arg (generic-403)))
 (defmacro let-403
   "Bind a form as with `let`; throw a 403 if it is `nil` or `false`."
   {:style/indent 1}
   [& body]
-  `(api-let ~generic-403 ~@body))
+  `(api-let (generic-403) ~@body))
 
 (defn throw-403
   "Throw a generic 403 (no permissions) error response."
   []
-  (throw (ex-info (tru "You don''t have permissions to do that.") {:status-code 403})))
+  (throw (ui18n/ex-info (tru "You don''t have permissions to do that.") {:status-code 403})))
 
 ;; #### GENERIC 500 RESPONSE HELPERS
 ;; For when you don't feel like writing something useful
@@ -240,7 +242,7 @@
   [500 (tru "Internal server error.")])
 
 (defn check-500
-  "Throw a `500` if ARG is `false` or `nil`, otherwise return as-is."
+  "Throw a `500` if `arg` is `false` or `nil`, otherwise return as-is."
   [arg]
   (check arg generic-500))
 
@@ -437,12 +439,12 @@
                           (finally
                             (async/close! error-chan))))]
     (async/go-loop []
-      (let [[response-or-timeout c] (async/alts!! [response-chan (async/timeout streaming-response-keep-alive-interval-ms)])]
+      (let [[response-or-timeout c] (async/alts! [response-chan (async/timeout streaming-response-keep-alive-interval-ms)])]
         (if response-or-timeout
           ;; We have a response since it's non-nil, write the results and close, we're done
           (do
             ;; If output-chan is closed, it's already too late, nothing else we need to do
-            (async/>!! output-chan response-or-timeout)
+            (async/>! output-chan response-or-timeout)
             (async/close! output-chan))
           (do
             ;; We don't have a result yet, but enough time has passed, let's assume it's not an error
@@ -451,7 +453,7 @@
             ;; sending this character fails because the connection is closed, the chan will then close.  Newlines are
             ;; no-ops when reading JSON which this depends upon.
             (log/debug (u/format-color 'blue (trs "Response not ready, writing one byte & sleeping...")))
-            (if (async/>!! output-chan \newline)
+            (if (async/>! output-chan \newline)
               ;; Success put the channel, wait and see if we get the response next time
               (recur)
               ;; The channel is closed, client has given up, we should give up too
diff --git a/src/metabase/api/common/internal.clj b/src/metabase/api/common/internal.clj
index 7ee3fc4b5d866aa825a41b593e5e6858ea7e4fef..4976ed9e5b45304a0dc46f5197d50438694a3112 100644
--- a/src/metabase/api/common/internal.clj
+++ b/src/metabase/api/common/internal.clj
@@ -6,8 +6,9 @@
             [medley.core :as m]
             [metabase.config :as config]
             [metabase.util :as u]
-            [metabase.util.schema :as su]
-            [puppetlabs.i18n.core :refer [trs tru]]
+            [metabase.util
+             [i18n :as ui18n :refer [tru]]
+             [schema :as su]]
             [schema.core :as s]))
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
@@ -111,7 +112,7 @@
   [^String value]
   (try (Integer/parseInt value)
        (catch NumberFormatException _
-         (throw (ex-info (format "Not a valid integer: '%s'" value) {:status-code 400})))))
+         (throw (ui18n/ex-info (tru "Not a valid integer: ''{0}''" value) {:status-code 400})))))
 
 (def ^:dynamic *auto-parse-types*
   "Map of `param-type` -> map with the following keys:
@@ -218,7 +219,7 @@
   [field-name value schema]
   (try (s/validate schema value)
        (catch Throwable e
-         (throw (ex-info (tru "Invalid field: {0}" field-name)
+         (throw (ui18n/ex-info (tru "Invalid field: {0}" field-name)
                   {:status-code 400
                    :errors      {(keyword field-name) (or (su/api-error-message schema)
                                                           (:message (ex-data e))
diff --git a/src/metabase/api/dashboard.clj b/src/metabase/api/dashboard.clj
index aa64401e20190b0a57731c341c7085193f70f304..47e6f822194f9a2d08ac22b041f238cf07bf49e9 100644
--- a/src/metabase/api/dashboard.clj
+++ b/src/metabase/api/dashboard.clj
@@ -2,6 +2,7 @@
   "/api/dashboard endpoints."
   (:require [clojure.tools.logging :as log]
             [compojure.core :refer [DELETE GET POST PUT]]
+            [metabase.automagic-dashboards.populate :as magic.populate]
             [metabase
              [events :as events]
              [query-processor :as qp]
@@ -195,7 +196,7 @@
   [id]
   (u/prog1 (-> (Dashboard id)
                api/check-404
-               (hydrate [:ordered_cards :card :series])
+               (hydrate [:ordered_cards :card :series] :can_write)
                api/read-check
                api/check-not-archived
                hide-unreadable-cards
@@ -399,12 +400,22 @@
 
 ;;; ---------------------------------------------- Transient dashboards ----------------------------------------------
 
+(api/defendpoint POST "/save/collection/:parent-collection-id"
+  "Save a denormalized description of dashboard into collection with ID `:parent-collection-id`."
+  [parent-collection-id :as {dashboard :body}]
+  (collection/check-write-perms-for-collection parent-collection-id)
+  (->> (dashboard/save-transient-dashboard! dashboard parent-collection-id)
+       (events/publish-event! :dashboard-create)))
+
 (api/defendpoint POST "/save"
   "Save a denormalized description of dashboard."
   [:as {dashboard :body}]
-  (api/check-superuser)
-  (->> (dashboard/save-transient-dashboard! dashboard)
-       (events/publish-event! :dashboard-create)))
+  (let [parent-collection-id (if api/*is-superuser?*
+                               (:id (magic.populate/get-or-create-root-container-collection))
+                               (db/select-one-field :id 'Collection
+                                 :personal_owner_id api/*current-user-id*))]
+    (->> (dashboard/save-transient-dashboard! dashboard parent-collection-id)
+         (events/publish-event! :dashboard-create))))
 
 
 (api/define-routes)
diff --git a/src/metabase/api/database.clj b/src/metabase/api/database.clj
index 64f7ab10289aedc98466614d856f68f5a149e22a..6464903f84b22f708b52a78eb3ea51017e9fe0c4 100644
--- a/src/metabase/api/database.clj
+++ b/src/metabase/api/database.clj
@@ -13,6 +13,7 @@
             [metabase.api
              [common :as api]
              [table :as table-api]]
+            [metabase.mbql.util :as mbql.u]
             [metabase.models
              [card :refer [Card]]
              [database :as database :refer [Database protected-password]]
@@ -21,7 +22,6 @@
              [interface :as mi]
              [permissions :as perms]
              [table :refer [Table]]]
-            [metabase.query-processor.util :as qputil]
             [metabase.sync
              [analyze :as analyze]
              [field-values :as sync-field-values]
@@ -97,14 +97,7 @@
    use queries with those aggregations as source queries. This function determines whether CARD is using one
    of those queries so we can filter it out in Clojure-land."
   [{{{aggregations :aggregation} :query} :dataset_query}]
-  (when (seq aggregations)
-    (some (fn [[ag-type]]
-            (contains? #{:cum-count :cum-sum} (qputil/normalize-token ag-type)))
-          ;; if we were passed in old-style [ag] instead of [[ag1], [ag2]] convert to new-style so we can iterate
-          ;; over list of aggregations
-          (if-not (sequential? (first aggregations))
-            [aggregations]
-            aggregations))))
+  (seq (mbql.u/clause-instances #{:cum-count :cum-sum} aggregations)))
 
 (defn- source-query-cards
   "Fetch the Cards that can be used as source queries (e.g. presented as virtual tables)."
@@ -550,21 +543,36 @@
   (delete-all-field-values-for-database! id)
   {:status :ok})
 
+
 ;;; ------------------------------------------ GET /api/database/:id/schemas -----------------------------------------
 
+(defn- can-read-schema?
+  "Does the current user have permissions to know the schema with `schema-name` exists? (Do they have permissions to see
+  at least some of its tables?)"
+  [database-id schema-name]
+  (perms/set-has-partial-permissions? @api/*current-user-permissions-set*
+    (perms/object-path database-id schema-name)))
+
 (api/defendpoint GET "/:id/schemas"
   "Returns a list of all the schemas found for the database `id`"
   [id]
-  (let [db (api/read-check Database id)]
-    (sort (db/select-field :schema Table :db_id id))))
+  (api/read-check Database id)
+  (->> (db/select-field :schema Table :db_id id)
+       (filter (partial can-read-schema? id))
+       sort))
+
 
 ;;; ------------------------------------- GET /api/database/:id/schema/:schema ---------------------------------------
 
 (api/defendpoint GET "/:id/schema/:schema"
   "Returns a list of tables for the given database `id` and `schema`"
   [id schema]
-  (let [db (api/read-check Database id)]
-    (api/let-404 [tables (seq (db/select Table :db_id id :schema schema {:order-by [[:name :asc]]}))]
-      tables)))
+  (api/read-check Database id)
+  (api/check-403 (can-read-schema? id schema))
+  (->> (db/select Table :db_id id, :schema schema, {:order-by [[:name :asc]]})
+       (filter mi/can-read?)
+       seq
+       api/check-404))
+
 
 (api/define-routes)
diff --git a/src/metabase/api/dataset.clj b/src/metabase/api/dataset.clj
index 26167aed328340eff4371d8f8301574338079f9a..196d61793d318a3102c5e79b0e433dfbee3c831a 100644
--- a/src/metabase/api/dataset.clj
+++ b/src/metabase/api/dataset.clj
@@ -14,8 +14,8 @@
             [metabase.util
              [date :as du]
              [export :as ex]
+             [i18n :refer [trs tru]]
              [schema :as su]]
-            [puppetlabs.i18n.core :refer [trs tru]]
             [schema.core :as s]))
 
 ;;; -------------------------------------------- Running a Query Normally --------------------------------------------
@@ -120,10 +120,13 @@
   [export-format query]
   {query         su/JSONString
    export-format ExportFormat}
-  (let [query (json/parse-string query keyword)]
-    (api/read-check Database (:database query))
+  (let [{:keys [database] :as query} (json/parse-string query keyword)]
+    (when-not (= database database/virtual-id)
+      (api/read-check Database database))
     (as-format export-format
-      (qp/process-query-and-save-execution! (dissoc query :constraints)
+      (qp/process-query-and-save-execution! (-> query
+                                                (dissoc :constraints)
+                                                (assoc-in [:middleware :skip-results-metadata?] true))
         {:executed-by api/*current-user-id*, :context (export-format->context export-format)}))))
 
 
diff --git a/src/metabase/api/embed.clj b/src/metabase/api/embed.clj
index 46ac0d35eabe40d2546cc473e700765955d0f710..99a755c07f6ea57fd9702862e98b6e059b3b0c31 100644
--- a/src/metabase/api/embed.clj
+++ b/src/metabase/api/embed.clj
@@ -31,6 +31,7 @@
             [metabase.util :as u]
             [metabase.util
              [embed :as eu]
+             [i18n :refer [tru]]
              [schema :as su]]
             [schema.core :as s]
             [toucan.db :as db]))
@@ -84,8 +85,8 @@
            (not (str/blank? v)))))
 
 (s/defn ^:private validate-and-merge-params :- {s/Keyword s/Any}
-  "Validate that the TOKEN-PARAMS passed in the JWT and the USER-PARAMS (passed as part of the URL) are allowed, and
-  that ones that are required are specified by checking them against a Card or Dashboard's OBJECT-EMBEDDING-PARAMS
+  "Validate that the `token-params` passed in the JWT and the `user-params` (passed as part of the URL) are allowed, and
+  that ones that are required are specified by checking them against a Card or Dashboard's `object-embedding-params`
   (the object's value of `:embedding_params`). Throws a 400 if any of the checks fail. If all checks are successful,
   returns a *merged* parameters map."
   [object-embedding-params :- su/EmbeddingParams, token-params :- {s/Keyword s/Any}, user-params :- {s/Keyword s/Any}]
@@ -99,14 +100,14 @@
 ;;; ---------------------------------------------- Other Param Util Fns ----------------------------------------------
 
 (defn- remove-params-in-set
-  "Remove any PARAMS from the list whose `:slug` is in the PARAMS-TO-REMOVE set."
+  "Remove any `params` from the list whose `:slug` is in the `params-to-remove` set."
   [params params-to-remove]
   (for [param params
         :when (not (contains? params-to-remove (keyword (:slug param))))]
     param))
 
 (s/defn ^:private remove-locked-and-disabled-params
-  "Remove the `:parameters` for DASHBOARD-OR-CARD that listed as `disabled` or `locked` in the EMBEDDING-PARAMS
+  "Remove the `:parameters` for DASHBOARD-OR-CARD that listed as `disabled` or `locked` in the `embedding-params`
   whitelist, or not present in the whitelist. This is done so the frontend doesn't display widgets for params the user
   can't set."
   [dashboard-or-card, embedding-params :- su/EmbeddingParams]
@@ -120,23 +121,24 @@
     (update dashboard-or-card :parameters remove-params-in-set params-to-remove)))
 
 (defn- remove-token-parameters
-  "Removes any parameters with slugs matching keys provided in TOKEN-PARAMS, as these should not be exposed to the user."
+  "Removes any parameters with slugs matching keys provided in `token-params`, as these should not be exposed to the
+  user."
   [dashboard-or-card token-params]
   (update dashboard-or-card :parameters remove-params-in-set (set (keys token-params))))
 
 (defn- template-tag-parameters
-  "Transforms native query's `template_tags` into `parameters`."
+  "Transforms native query's `template-tags` into `parameters`."
   [card]
   ;; NOTE: this should mirror `getTemplateTagParameters` in frontend/src/metabase/meta/Parameter.js
-  (for [[_ {tag-type :type, widget-type :widget_type, :as tag}] (get-in card [:dataset_query :native :template_tags])
+  (for [[_ {tag-type :type, widget-type :widget-type, :as tag}] (get-in card [:dataset_query :native :template-tags])
         :when                         (and tag-type
-                                           (or widget-type (not= tag-type "dimension")))]
+                                           (or widget-type (not= tag-type :dimension)))]
     {:id      (:id tag)
-     :type    (or widget-type (if (= tag-type "date") "date/single" "category"))
-     :target  (if (= tag-type "dimension")
-                ["dimension" ["template-tag" (:name tag)]]
-                ["variable" ["template-tag" (:name tag)]])
-     :name    (:display_name tag)
+     :type    (or widget-type (if (= tag-type :date) :date/single :category))
+     :target  (if (= tag-type :dimension)
+                [:dimension [:template-tag (:name tag)]]
+                [:variable  [:template-tag (:name tag)]])
+     :name    (:display-name tag)
      :slug    (:name tag)
      :default (:default tag)}))
 
@@ -146,7 +148,7 @@
   (update card :parameters concat (template-tag-parameters card)))
 
 (s/defn ^:private apply-parameter-values :- (s/maybe [{:slug   su/NonBlankString
-                                                       :type   su/NonBlankString
+                                                       :type   s/Keyword
                                                        :target s/Any
                                                        :value  s/Any}])
   "Adds `value` to parameters with `slug` matching a key in `parameter-values` and removes parameters without a
@@ -169,7 +171,8 @@
 (defn- resolve-dashboard-parameters
   "Returns parameters for a card on a dashboard with `:target` resolved via `:parameter_mappings`."
   [dashboard-id dashcard-id card-id]
-  (let [param-id->param (u/key-by :id (db/select-one-field :parameters Dashboard :id dashboard-id))]
+  (let [param-id->param (u/key-by :id (for [param (db/select-one-field :parameters Dashboard :id dashboard-id)]
+                                        (update param :type keyword)))]
     ;; throw a 404 if there's no matching DashboardCard so people can't get info about other Cards that aren't in this
     ;; Dashboard we don't need to check that card-id matches the DashboardCard because we might be trying to get param
     ;; info for a series belonging to this dashcard (card-id might be for a series)
@@ -219,8 +222,8 @@
 ;;; -------------------------- Dashboard Fns used by both /api/embed and /api/preview_embed --------------------------
 
 (defn dashboard-for-unsigned-token
-  "Return the info needed for embedding about Dashboard specified in TOKEN.
-   Additional CONSTRAINTS can be passed to the `public-dashboard` function that fetches the Dashboard."
+  "Return the info needed for embedding about Dashboard specified in `token`. Additional `constraints` can be passed to
+  the `public-dashboard` function that fetches the Dashboard."
   {:style/indent 1}
   [unsigned-token & {:keys [embedding-params constraints]}]
   (let [dashboard-id (eu/get-in-unsigned-token-or-throw unsigned-token [:resource :dashboard])
@@ -245,7 +248,7 @@
 ;;; ------------------------------------- Other /api/embed-specific utility fns --------------------------------------
 
 (defn- check-embedding-enabled-for-object
-  "Check that embedding is enabled, that OBJECT exists, and embedding for OBJECT is enabled."
+  "Check that embedding is enabled, that `object` exists, and embedding for `object` is enabled."
   ([entity id]
    (check-embedding-enabled-for-object (db/select-one [entity :enable_embedding] :id id)))
   ([object]
@@ -253,7 +256,7 @@
    (api/check-404 object)
    (api/check-not-archived object)
    (api/check (:enable_embedding object)
-     [400 "Embedding is not enabled for this object."])))
+     [400 (tru "Embedding is not enabled for this object.")])))
 
 (def ^:private ^{:arglists '([dashboard-id])} check-embedding-enabled-for-dashboard
   (partial check-embedding-enabled-for-object Dashboard))
@@ -277,7 +280,7 @@
 
 
 (defn- run-query-for-unsigned-token
-  "Run the query belonging to Card identified by UNSIGNED-TOKEN. Checks that embedding is enabled both globally and
+  "Run the query belonging to Card identified by `unsigned-token`. Checks that embedding is enabled both globally and
   for this Card."
   [unsigned-token query-params & options]
   (let [card-id (eu/get-in-unsigned-token-or-throw unsigned-token [:resource :question])]
@@ -324,7 +327,8 @@
     (dashboard-for-unsigned-token unsigned, :constraints {:enable_embedding true})))
 
 
-(api/defendpoint GET "/dashboard/:token/dashcard/:dashcard-id/card/:card-id"
+
+(defn- card-for-signed-token
   "Fetch the results of running a Card belonging to a Dashboard using a JSON Web Token signed with the
    `embedding-secret-key`.
 
@@ -334,7 +338,8 @@
       :params   <parameters>}
 
    Additional dashboard parameters can be provided in the query string, but params in the JWT token take precedence."
-  [token dashcard-id card-id & query-params]
+  {:style/indent 1}
+  [token dashcard-id card-id query-params]
   (let [unsigned-token (eu/unsign token)
         dashboard-id   (eu/get-in-unsigned-token-or-throw unsigned-token [:resource :dashboard])]
     (check-embedding-enabled-for-dashboard dashboard-id)
@@ -346,6 +351,11 @@
       :token-params     (eu/get-in-unsigned-token-or-throw unsigned-token [:params])
       :query-params     query-params)))
 
+(api/defendpoint GET "/dashboard/:token/dashcard/:dashcard-id/card/:card-id"
+  "Fetch the results of running a Card belonging to a Dashboard using a JSON Web Token signed with the
+  `embedding-secret-key`"
+  [token dashcard-id card-id & query-params]
+  (card-for-signed-token token dashcard-id card-id query-params ))
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                        FieldValues, Search, Remappings                                         |
@@ -417,4 +427,12 @@
     (public-api/dashboard-field-remapped-values dashboard-id field-id remapped-id value)))
 
 
+(api/defendpoint GET ["/dashboard/:token/dashcard/:dashcard-id/card/:card-id/:export-format",
+                      :export-format dataset-api/export-format-regex]
+  "Fetch the results of running a Card belonging to a Dashboard using a JSON Web Token signed with the
+  `embedding-secret-key` return the data in one of the export formats"
+  [token export-format dashcard-id card-id & query-params]
+  {export-format dataset-api/ExportFormat}
+  (dataset-api/as-format export-format (card-for-signed-token token dashcard-id card-id query-params )))
+
 (api/define-routes)
diff --git a/src/metabase/api/field.clj b/src/metabase/api/field.clj
index c3409d5363515ec9d2e395a9f8b7fc2bde97aeeb..2b5a77c2214e595d77f3d2d2e1e7ebb654f53073 100644
--- a/src/metabase/api/field.clj
+++ b/src/metabase/api/field.clj
@@ -303,7 +303,8 @@
         [result result]))))
 
 (api/defendpoint GET "/:id/search/:search-id"
-  "Search for values of a Field that match values of another Field when breaking out by the "
+  "Search for values of a Field with `search-id` that start with `value`. See docstring for
+  `metabase.api.field/search-values` for a more detailed explanation."
   [id search-id value limit]
   {value su/NonBlankString
    limit (s/maybe su/IntStringGreaterThanZero)}
diff --git a/src/metabase/api/geojson.clj b/src/metabase/api/geojson.clj
index 96209270ed718351154b52f77ae78b0757bfa51c..6aaf4938748c34a981816c9d43c8cda919488c68 100644
--- a/src/metabase/api/geojson.clj
+++ b/src/metabase/api/geojson.clj
@@ -5,10 +5,12 @@
             [metabase.api.common :refer [defendpoint define-routes]]
             [metabase.models.setting :as setting :refer [defsetting]]
             [metabase.util :as u]
-            [metabase.util.schema :as su]
-            [puppetlabs.i18n.core :refer [tru]]
+            [metabase.util
+             [i18n :as ui18n :refer [tru]]
+             [schema :as su]]
             [ring.util.response :as rr]
-            [schema.core :as s])
+            [schema.core :as s]
+            [metabase.util.i18n :as ui18n])
   (:import org.apache.commons.io.input.ReaderInputStream))
 
 (def ^:private ^:const ^Integer geojson-fetch-timeout-ms
@@ -87,7 +89,7 @@
   [key]
   {key su/NonBlankString}
   (let [url (or (get-in (custom-geojson) [(keyword key) :url])
-                (throw (ex-info (tru "Invalid custom GeoJSON key: {0}" key)
+                (throw (ui18n/ex-info (tru "Invalid custom GeoJSON key: {0}" key)
                          {:status-code 400})))]
     ;; TODO - it would be nice if we could also avoid returning our usual cache-busting headers with the response here
     (-> (rr/response (ReaderInputStream. (io/reader url)))
diff --git a/src/metabase/api/getting_started.clj b/src/metabase/api/getting_started.clj
deleted file mode 100644
index a0a4d3a1b7cb01448a794b76c629cf58910f308c..0000000000000000000000000000000000000000
--- a/src/metabase/api/getting_started.clj
+++ /dev/null
@@ -1,45 +0,0 @@
-(ns metabase.api.getting-started
-  "/api/getting_started routes."
-  (:require [compojure.core :refer [GET]]
-            [medley.core :as m]
-            [metabase.api.common :as api]
-            [metabase.models
-             [interface :as mi]
-             [setting :refer [defsetting]]]
-            [metabase.util :as u]
-            [puppetlabs.i18n.core :refer [tru]]
-            [toucan.db :as db]))
-
-(defsetting getting-started-things-to-know
-  (tru "''Some things to know'' text field for the Getting Started guide."))
-
-(defsetting getting-started-contact-name
-  (tru "Name of somebody users can contact for help in the Getting Started guide."))
-
-(defsetting getting-started-contact-email
-  (tru "Email of somebody users can contact for help in the Getting Started guide."))
-
-
-(api/defendpoint GET "/"
-  "Fetch basic info for the Getting Started guide."
-  []
-  (let [metric-ids  (map :id (filter mi/can-read? (db/select ['Metric :table_id :id]     :show_in_getting_started true, {:order-by [:%lower.name]})))
-        table-ids   (map :id (filter mi/can-read? (db/select ['Table :db_id :schema :id] :show_in_getting_started true, {:order-by [:%lower.name]})))
-        segment-ids (map :id (filter mi/can-read? (db/select ['Segment :table_id :id]    :show_in_getting_started true, {:order-by [:%lower.name]})))]
-    {:things_to_know           (getting-started-things-to-know)
-     :contact                  {:name  (getting-started-contact-name)
-                                :email (getting-started-contact-email)}
-     :most_important_dashboard (when-let [dashboard (db/select-one ['Dashboard :id :collection_id]
-                                                      :show_in_getting_started true)]
-                                 (when (mi/can-read? dashboard)
-                                   (u/get-id dashboard)))
-     :important_metrics        metric-ids
-     :important_tables         table-ids
-     :important_segments       segment-ids
-     ;; A map of metric_id -> sequence of important field_ids
-     :metric_important_fields  (m/map-vals (partial map :field_id)
-                                           (group-by :metric_id (when (seq metric-ids)
-                                                                  (db/select ['MetricImportantField :field_id :metric_id]
-                                                                    :metric_id [:in metric-ids]))))}))
-
-(api/define-routes)
diff --git a/src/metabase/api/public.clj b/src/metabase/api/public.clj
index e3758dd68100dbfe043c1fd91c2b4237a75bae29..862273c777e9f6a3fc91c20594d6e621c57394d8 100644
--- a/src/metabase/api/public.clj
+++ b/src/metabase/api/public.clj
@@ -24,12 +24,13 @@
              [params :as params]]
             [metabase.util
              [embed :as embed]
+             [i18n :refer [tru]]
              [schema :as su]]
-            [puppetlabs.i18n.core :refer [tru]]
             [schema.core :as s]
             [toucan
              [db :as db]
-             [hydrate :refer [hydrate]]]))
+             [hydrate :refer [hydrate]]]
+            [metabase.mbql.normalize :as normalize]))
 
 (def ^:private ^:const ^Integer default-embed-max-height 800)
 (def ^:private ^:const ^Integer default-embed-max-width 1024)
@@ -42,7 +43,7 @@
   [card]
   (card/map->CardInstance
    (u/select-nested-keys card [:id :name :description :display :visualization_settings
-                               [:dataset_query :type [:native :template_tags]]])))
+                               [:dataset_query :type [:native :template-tags]]])))
 
 (defn public-card
   "Return a public Card matching key-value CONDITIONS, removing all columns that should not be visible to the general
@@ -192,7 +193,8 @@
           slug->dashboard-param   (u/key-by :slug dashboard-params)
           dashcard-param-mappings (dashboard->dashcard-param-mappings dashboard-id)]
       (for [{slug :slug, target :target, :as query-param} query-params
-            :let [dashboard-param
+            :let [target (normalize/normalize-tokens target :ignore-path)
+                  dashboard-param
                   (or
                    ;; try to match by slug...
                    (slug->dashboard-param slug)
diff --git a/src/metabase/api/pulse.clj b/src/metabase/api/pulse.clj
index e2bd363d97c7ef8c432af692515bb08fe5c9e6fb..7941a535449464e8080ae222f7773f3f7a56ece6 100644
--- a/src/metabase/api/pulse.clj
+++ b/src/metabase/api/pulse.clj
@@ -1,6 +1,7 @@
 (ns metabase.api.pulse
   "/api/pulse endpoints."
-  (:require [compojure.core :refer [DELETE GET POST PUT]]
+  (:require [clojure.tools.logging :as log]
+            [compojure.core :refer [DELETE GET POST PUT]]
             [hiccup.core :refer [html]]
             [metabase
              [email :as email]
@@ -18,21 +19,22 @@
              [pulse-channel :refer [channel-types]]]
             [metabase.pulse.render :as render]
             [metabase.util
+             [i18n :refer [tru]]
              [schema :as su]
              [urls :as urls]]
             [schema.core :as s]
-            [toucan.db :as db])
+            [toucan
+             [db :as db]
+             [hydrate :refer [hydrate]]])
   (:import java.io.ByteArrayInputStream))
 
 (api/defendpoint GET "/"
-  "Fetch all `Pulses`"
-  []
-  (for [pulse (pulse/retrieve-pulses)
-        :let  [can-read?  (mi/can-read? pulse)
-               can-write? (mi/can-write? pulse)]
-        :when (or can-read?
-                  can-write?)]
-    (assoc pulse :read_only (not can-write?))))
+  "Fetch all Pulses"
+  [archived]
+  {archived (s/maybe su/BooleanString)}
+  (as-> (pulse/retrieve-pulses {:archived? (Boolean/parseBoolean archived)}) <>
+    (filter mi/can-read? <>)
+    (hydrate <> :can_write)))
 
 (defn check-card-read-permissions
   "Users can only create a pulse for `cards` they have access to."
@@ -72,17 +74,18 @@
 (api/defendpoint GET "/:id"
   "Fetch `Pulse` with ID."
   [id]
-  (api/read-check (pulse/retrieve-pulse id)))
-
+  (-> (api/read-check (pulse/retrieve-pulse id))
+      (hydrate :can_write)))
 
 (api/defendpoint PUT "/:id"
   "Update a Pulse with `id`."
-  [id :as {{:keys [name cards channels skip_if_empty collection_id], :as pulse-updates} :body}]
+  [id :as {{:keys [name cards channels skip_if_empty collection_id archived], :as pulse-updates} :body}]
   {name          (s/maybe su/NonBlankString)
    cards         (s/maybe (su/non-empty [pulse/CoercibleToCardRef]))
    channels      (s/maybe (su/non-empty [su/Map]))
    skip_if_empty (s/maybe s/Bool)
-   collection_id (s/maybe su/IntGreaterThanZero)}
+   collection_id (s/maybe su/IntGreaterThanZero)
+   archived      (s/maybe s/Bool)}
   ;; do various perms checks
   (let [pulse-before-update (api/write-check Pulse id)]
     (check-card-read-permissions cards)
@@ -94,15 +97,17 @@
       (api/maybe-reconcile-collection-position! pulse-before-update pulse-updates)
       ;; ok, now update the Pulse
       (pulse/update-pulse!
-       (assoc (select-keys pulse-updates [:name :cards :channels :skip_if_empty :collection_id :collection_position])
+       (assoc (select-keys pulse-updates [:name :cards :channels :skip_if_empty :collection_id :collection_position
+                                          :archived])
          :id id))))
   ;; return updated Pulse
   (pulse/retrieve-pulse id))
 
 
 (api/defendpoint DELETE "/:id"
-  "Delete a `Pulse`."
+  "Delete a Pulse. (DEPRECATED -- don't delete a Pulse anymore -- archive it instead.)"
   [id]
+  (log/warn (tru "DELETE /api/pulse/:id is deprecated. Instead, change its `archived` value via PUT /api/pulse/:id."))
   (api/let-404 [pulse (Pulse id)]
     (api/write-check Pulse id)
     (db/delete! Pulse :id id)
@@ -111,7 +116,7 @@
 
 
 (api/defendpoint GET "/form_input"
-  "Provides relevant configuration information and user choices for creating/updating `Pulses`."
+  "Provides relevant configuration information and user choices for creating/updating Pulses."
   []
   (let [chan-types (-> channel-types
                        (assoc-in [:slack :configured] (slack/slack-configured?))
@@ -135,7 +140,7 @@
                                                                :card-id     (u/get-id card)}))
 
 (api/defendpoint GET "/preview_card/:id"
-  "Get HTML rendering of a `Card` with ID."
+  "Get HTML rendering of a Card with `id`."
   [id]
   (let [card   (api/read-check Card id)
         result (pulse-card-query-results card)]
@@ -148,7 +153,7 @@
                   (render/render-pulse-card-for-display (p/defaulted-timezone card) card result))]])}))
 
 (api/defendpoint GET "/preview_card_info/:id"
-  "Get JSON object containing HTML rendering of a `Card` with ID and other information."
+  "Get JSON object containing HTML rendering of a Card with `id` and other information."
   [id]
   (let [card      (api/read-check Card id)
         result    (pulse-card-query-results card)
@@ -165,7 +170,7 @@
      :col_count       (count (:cols (:data result)))}))
 
 (api/defendpoint GET "/preview_card_png/:id"
-  "Get PNG rendering of a `Card` with ID."
+  "Get PNG rendering of a Card with `id`."
   [id]
   (let [card   (api/read-check Card id)
         result (pulse-card-query-results card)
@@ -175,13 +180,16 @@
 
 (api/defendpoint POST "/test"
   "Test send an unsaved pulse."
-  [:as {{:keys [name cards channels skip_if_empty] :as body} :body}]
-  {name          su/NonBlankString
-   cards         (su/non-empty [pulse/CardRef])
-   channels      (su/non-empty [su/Map])
-   skip_if_empty s/Bool}
+  [:as {{:keys [name cards channels skip_if_empty collection_id collection_position] :as body} :body}]
+  {name                su/NonBlankString
+   cards               (su/non-empty [pulse/CoercibleToCardRef])
+   channels            (su/non-empty [su/Map])
+   skip_if_empty       (s/maybe s/Bool)
+   collection_id       (s/maybe su/IntGreaterThanZero)
+   collection_position (s/maybe su/IntGreaterThanZero)}
   (check-card-read-permissions cards)
   (p/send-pulse! body)
   {:ok true})
 
+
 (api/define-routes)
diff --git a/src/metabase/api/routes.clj b/src/metabase/api/routes.clj
index 903ba777e79038eeed20ebf7f3182753e51c0007..e5babb2903699e7fe24ab57aa422d204f78694fe 100644
--- a/src/metabase/api/routes.clj
+++ b/src/metabase/api/routes.clj
@@ -15,7 +15,6 @@
              [embed :as embed]
              [field :as field]
              [geojson :as geojson]
-             [getting-started :as getting-started]
              [ldap :as ldap]
              [metric :as metric]
              [notify :as notify]
@@ -35,7 +34,7 @@
              [user :as user]
              [util :as util]]
             [metabase.middleware :as middleware]
-            [puppetlabs.i18n.core :refer [tru]]))
+            [metabase.util.i18n :refer [tru]]))
 
 (def ^:private +generic-exceptions
   "Wrap ROUTES so any Exception thrown is just returned as a generic 400, to prevent details from leaking in public
@@ -67,7 +66,6 @@
   (context "/email"                [] (+auth email/routes))
   (context "/embed"                [] (+message-only-exceptions embed/routes))
   (context "/field"                [] (+auth field/routes))
-  (context "/getting_started"      [] (+auth getting-started/routes))
   (context "/geojson"              [] (+auth geojson/routes))
   (context "/ldap"                 [] (+auth ldap/routes))
   (context "/metric"               [] (+auth metric/routes))
diff --git a/src/metabase/api/search.clj b/src/metabase/api/search.clj
index cc141cb89a19e383d374c658a5b9403809196ba5..b6fbad58df7118404b46c0206c803fb2db4f2aeb 100644
--- a/src/metabase/api/search.clj
+++ b/src/metabase/api/search.clj
@@ -2,7 +2,8 @@
   (:require [clojure.string :as str]
             [compojure.core :refer [GET]]
             [honeysql.helpers :as h]
-            [metabase.api.common :refer [*current-user-id* *current-user-permissions-set* check-403 defendpoint define-routes]]
+            [metabase.api.common :refer [*current-user-id* *current-user-permissions-set* check-403 defendpoint
+                                         define-routes]]
             [metabase.models
              [card :refer [Card]]
              [card-favorite :refer [CardFavorite]]
@@ -45,7 +46,7 @@
   default-columns)
 
 (defn- ->column
-  "Returns the column name. If the column is aliased, i.e. [`:original_namd` `:aliased_name`], return the aliased
+  "Returns the column name. If the column is aliased, i.e. [`:original_name` `:aliased_name`], return the aliased
   column name"
   [column-or-aliased]
   (if (sequential? column-or-aliased)
@@ -95,8 +96,9 @@
   [query-map entity-type entity-columns]
   (let [col-name->column (u/key-by ->column entity-columns)
         cols-or-nils     (make-canonical-columns entity-type col-name->column)]
-    (apply h/merge-select query-map (concat cols-or-nils ))))
+    (apply h/merge-select query-map (concat cols-or-nils))))
 
+;; TODO - not used anywhere except `merge-name-and-archived-search` anymore so we can roll it into that
 (s/defn ^:private merge-name-search
   "Add case-insensitive name query criteria to `query-map`"
   [query-map {:keys [search-string]} :- SearchContext]
@@ -161,14 +163,13 @@
       (add-collection-criteria :collection_id search-ctx)))
 
 (s/defmethod ^:private create-search-query :pulse
-  [_ {:keys [archived?] :as search-ctx} :- SearchContext]
+  [_ search-ctx :- SearchContext]
   ;; Pulses don't currently support being archived, omit if archived is true
-  (when-not archived?
-    (-> (make-honeysql-search-query Pulse "pulse" pulse-columns-without-type)
-        (merge-name-search search-ctx)
-        (add-collection-criteria :collection_id search-ctx)
-        ;; We don't want alerts included in pulse results
-        (h/merge-where [:= :alert_condition nil]))))
+  (-> (make-honeysql-search-query Pulse "pulse" pulse-columns-without-type)
+      (merge-name-and-archived-search search-ctx)
+      (add-collection-criteria :collection_id search-ctx)
+      ;; We don't want alerts included in pulse results
+      (h/merge-where [:= :alert_condition nil])))
 
 (s/defmethod ^:private create-search-query :metric
   [_ search-ctx :- SearchContext]
diff --git a/src/metabase/api/session.clj b/src/metabase/api/session.clj
index eeb578f5373dbf6dd45f341d76898ec30725beb7..69a9c6a29ec19f024d22d120b34a5b8503fc1524 100644
--- a/src/metabase/api/session.clj
+++ b/src/metabase/api/session.clj
@@ -17,9 +17,9 @@
              [setting :refer [defsetting]]
              [user :as user :refer [User]]]
             [metabase.util
+             [i18n :as ui18n :refer [trs tru]]
              [password :as pass]
              [schema :as su]]
-            [puppetlabs.i18n.core :refer [trs tru]]
             [schema.core :as s]
             [throttle.core :as throttle]
             [toucan.db :as db]))
@@ -33,7 +33,8 @@
     (db/insert! Session
       :id      <>
       :user_id (:id user))
-    (events/publish-event! :user-login {:user_id (:id user), :session_id <>, :first_login (not (boolean (:last_login user)))})))
+    (events/publish-event! :user-login
+      {:user_id (:id user), :session_id <>, :first_login (not (boolean (:last_login user)))})))
 
 ;;; ## API Endpoints
 
@@ -53,8 +54,9 @@
     (try
       (when-let [user-info (ldap/find-user username)]
         (when-not (ldap/verify-password user-info password)
-          ;; Since LDAP knows about the user, fail here to prevent the local strategy to be tried with a possibly outdated password
-          (throw (ex-info password-fail-message
+          ;; Since LDAP knows about the user, fail here to prevent the local strategy to be tried with a possibly
+          ;; outdated password
+          (throw (ui18n/ex-info password-fail-message
                    {:status-code 400
                     :errors      {:password password-fail-snippet}})))
         ;; password is ok, return new session
@@ -83,7 +85,7 @@
       (email-login username password) ; Then try local authentication
       ;; If nothing succeeded complain about it
       ;; Don't leak whether the account doesn't exist or the password was incorrect
-      (throw (ex-info password-fail-message
+      (throw (ui18n/ex-info password-fail-message
                {:status-code 400
                 :errors      {:password password-fail-snippet}}))))
 
@@ -114,7 +116,8 @@
   (throttle/check (forgot-password-throttlers :ip-address) remote-address)
   (throttle/check (forgot-password-throttlers :email)      email)
   ;; Don't leak whether the account doesn't exist, just pretend everything is ok
-  (when-let [{user-id :id, google-auth? :google_auth} (db/select-one ['User :id :google_auth] :email email, :is_active true)]
+  (when-let [{user-id :id, google-auth? :google_auth} (db/select-one ['User :id :google_auth]
+                                                        :email email, :is_active true)]
     (let [reset-token        (user/set-password-reset-token! user-id)
           password-reset-url (str (public-settings/site-url) "/auth/reset_password/" reset-token)]
       (email/send-password-reset-email! email google-auth? server-name password-reset-url)
@@ -130,7 +133,8 @@
   [^String token]
   (when-let [[_ user-id] (re-matches #"(^\d+)_.+$" token)]
     (let [user-id (Integer/parseInt user-id)]
-      (when-let [{:keys [reset_token reset_triggered], :as user} (db/select-one [User :id :last_login :reset_triggered :reset_token]
+      (when-let [{:keys [reset_token reset_triggered], :as user} (db/select-one [User :id :last_login :reset_triggered
+                                                                                 :reset_token]
                                                                    :id user-id, :is_active true)]
         ;; Make sure the plaintext token matches up with the hashed one for this user
         (when (u/ignore-exceptions
@@ -185,10 +189,10 @@
 (defn- google-auth-token-info [^String token]
   (let [{:keys [status body]} (http/post (str "https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=" token))]
     (when-not (= status 200)
-      (throw (ex-info (tru "Invalid Google Auth token.") {:status-code 400})))
+      (throw (ui18n/ex-info (tru "Invalid Google Auth token.") {:status-code 400})))
     (u/prog1 (json/parse-string body keyword)
       (when-not (= (:email_verified <>) "true")
-        (throw (ex-info (tru "Email is not verified.") {:status-code 400}))))))
+        (throw (ui18n/ex-info (tru "Email is not verified.") {:status-code 400}))))))
 
 ;; TODO - are these general enough to move to `metabase.util`?
 (defn- email->domain ^String [email]
@@ -206,8 +210,9 @@
   (when-not (autocreate-user-allowed-for-email? email)
     ;; Use some wacky status code (428 - Precondition Required) so we will know when to so the error screen specific
     ;; to this situation
-    (throw (ex-info (tru "You''ll need an administrator to create a Metabase account before you can use Google to log in.")
-             {:status-code 428}))))
+    (throw
+     (ui18n/ex-info (tru "You''ll need an administrator to create a Metabase account before you can use Google to log in.")
+       {:status-code 428}))))
 
 (s/defn ^:private google-auth-create-new-user!
   [{:keys [email] :as new-user} :- user/NewUser]
diff --git a/src/metabase/api/setup.clj b/src/metabase/api/setup.clj
index d612b46184b51e407dce9a2366f2e7f051351bac..944b3e79b194d23a6332c2745029621b83ed6fad 100644
--- a/src/metabase/api/setup.clj
+++ b/src/metabase/api/setup.clj
@@ -14,8 +14,9 @@
              [database :refer [Database]]
              [session :refer [Session]]
              [user :as user :refer [User]]]
-            [metabase.util.schema :as su]
-            [puppetlabs.i18n.core :refer [tru]]
+            [metabase.util
+             [i18n :refer [tru]]
+             [schema :as su]]
             [schema.core :as s]
             [toucan.db :as db]))
 
@@ -142,7 +143,7 @@
      {:title       (tru "Organize questions")
       :group       (tru "Curate your data")
       :description (tru "Have a lot of saved questions in {0}? Create collections to help manage them and add context." (tru "Metabase"))
-      :link        "/questions/"
+      :link        "/collection/root"
       :completed   has-collections?
       :triggered   (>= num-cards 30)}
      {:title       (tru "Create metrics")
diff --git a/src/metabase/api/table.clj b/src/metabase/api/table.clj
index f0e2310c04c68d212630c9fc99d5ad1d988e8977..9c073d5f1487f68068b3db29f0b15104fd988dd4 100644
--- a/src/metabase/api/table.clj
+++ b/src/metabase/api/table.clj
@@ -19,7 +19,7 @@
             [metabase.sync.field-values :as sync-field-values]
             [metabase.util.schema :as su]
             [schema.core :as s]
-            [puppetlabs.i18n.core :refer [trs tru]]
+            [metabase.util.i18n :refer [trs tru]]
             [toucan
              [db :as db]
              [hydrate :refer [hydrate]]]))
@@ -156,13 +156,13 @@
                               (pred v))) dimension-options-for-response)))
 
 (def ^:private date-default-index
-  (dimension-index-for-type "type/DateTime" #(= day-str (:name %))))
+  (dimension-index-for-type "type/DateTime" #(= (str day-str) (str (:name %)))))
 
 (def ^:private numeric-default-index
-  (dimension-index-for-type "type/Number" #(.contains ^String (:name %) auto-bin-str)))
+  (dimension-index-for-type "type/Number" #(.contains ^String (str (:name %)) (str auto-bin-str))))
 
 (def ^:private coordinate-default-index
-  (dimension-index-for-type "type/Coordinate" #(.contains ^String (:name %) auto-bin-str)))
+  (dimension-index-for-type "type/Coordinate" #(.contains ^String (str (:name %)) (str auto-bin-str))))
 
 (defn- supports-numeric-binning? [driver]
   (and driver (contains? (driver/features driver) :binning)))
@@ -212,16 +212,11 @@
                 (update field :values fv/field-values->pairs)
                 field)))))
 
-(api/defendpoint GET "/:id/query_metadata"
-  "Get metadata about a `Table` us eful for running queries.
-   Returns DB, fields, field FKs, and field values.
-
-  By passing `include_sensitive_fields=true`, information *about* sensitive `Fields` will be returned; in no case will
-  any of its corresponding values be returned. (This option is provided for use in the Admin Edit Metadata page)."
-  [id include_sensitive_fields]
-  {include_sensitive_fields (s/maybe su/BooleanString)}
-  (let [table (api/read-check Table id)
-        driver (driver/database-id->driver (:db_id table))]
+(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]
+  (api/read-check table)
+  (let [driver (driver/database-id->driver (:db_id table))]
     (-> table
         (hydrate :db [:fields [:target :has_field_values] :dimensions :has_field_values] :segments :metrics)
         (m/dissoc-in [:db :details])
@@ -234,6 +229,16 @@
                           (partial filter (fn [{:keys [visibility_type]}]
                                             (not= (keyword visibility_type) :sensitive))))))))
 
+(api/defendpoint GET "/:id/query_metadata"
+  "Get metadata about a `Table` useful for running queries.
+   Returns DB, fields, field FKs, and field values.
+
+  By passing `include_sensitive_fields=true`, information *about* sensitive `Fields` will be returned; in no case will
+  any of its corresponding values be returned. (This option is provided for use in the Admin Edit Metadata page)."
+  [id include_sensitive_fields]
+  {include_sensitive_fields (s/maybe su/BooleanString)}
+  (fetch-query-metadata (Table id) include_sensitive_fields))
+
 (defn- card-result-metadata->virtual-fields
   "Return a sequence of 'virtual' fields metadata for the 'virtual' table for a Card in the Saved Questions 'virtual'
    database."
@@ -245,12 +250,10 @@
           (assoc
               :table_id     (str "card__" card-id)
               :id           [:field-literal (:name col) (or (:base_type col) :type/*)]
-              ;; don't return :special_type if it's a PK or FK because it confuses the frontend since it can't
-              ;; actually be used that way IRL
-              :special_type (when-let [special-type (keyword (:special_type col))]
-                              (when-not (or (isa? special-type :type/PK)
-                                            (isa? special-type :type/FK))
-                                special-type)))
+              ;; Assoc special_type at least temprorarily. We need the correct special type in place to make decisions
+              ;; about what kind of dimension options should be added. PK/FK values will be removed after we've added
+              ;; the dimension options
+              :special_type (keyword (:special_type col)))
           add-field-dimension-options))))
 
 (defn card->virtual-table
@@ -268,6 +271,16 @@
                                                                            database_id
                                                                            (:result_metadata card))))))
 
+(defn- remove-nested-pk-fk-special-types
+  "This method clears the special_type attribute for PK/FK fields of nested queries. Those fields having a special
+  type confuses the frontend and it can really used in the same way"
+  [{:keys [fields] :as metadata-response}]
+  (assoc metadata-response :fields (for [{:keys [special_type] :as field} fields]
+                                     (if (or (isa? special_type :type/PK)
+                                             (isa? special_type :type/FK))
+                                       (assoc field :special_type nil)
+                                       field))))
+
 (api/defendpoint GET "/card__:id/query_metadata"
   "Return metadata for the 'virtual' table for a Card."
   [id]
@@ -277,7 +290,8 @@
     (-> card
         api/read-check
         (card->virtual-table :include-fields? true)
-        (assoc-dimension-options (driver/database-id->driver database_id)))))
+        (assoc-dimension-options (driver/database-id->driver database_id))
+        remove-nested-pk-fk-special-types)))
 
 (api/defendpoint GET "/card__:id/fks"
   "Return FK info for the 'virtual' table for a Card. This is always empty, so this endpoint
diff --git a/src/metabase/api/tiles.clj b/src/metabase/api/tiles.clj
index 6a242975a38332ddd7bafc1c803330652a9d9ffa..e7c62269a0abcb7aa1a545cd2a7ae0c8734334f9 100644
--- a/src/metabase/api/tiles.clj
+++ b/src/metabase/api/tiles.clj
@@ -1,14 +1,15 @@
 (ns metabase.api.tiles
   "`/api/tiles` endpoints."
   (:require [cheshire.core :as json]
-            [clojure.core.match :refer [match]]
             [compojure.core :refer [GET]]
             [metabase
              [query-processor :as qp]
              [util :as u]]
             [metabase.api.common :as api]
-            [metabase.util.schema :as su]
-            [puppetlabs.i18n.core :refer [tru]])
+            [metabase.mbql.util :as mbql.u]
+            [metabase.util
+             [i18n :refer [tru]]
+             [schema :as su]])
   (:import java.awt.Color
            java.awt.image.BufferedImage
            java.io.ByteArrayOutputStream
@@ -51,12 +52,14 @@
   [details lat-field-id lon-field-id x y zoom]
   (let [top-left      (x+y+zoom->lat-lon      x       y  zoom)
         bottom-right  (x+y+zoom->lat-lon (inc x) (inc y) zoom)
-        inside-filter ["INSIDE" lat-field-id lon-field-id (top-left :lat) (top-left :lon) (bottom-right :lat) (bottom-right :lon)]]
-    (update details :filter
-      #(match %
-         ["AND" & _]              (conj % inside-filter)
-         [(_ :guard string?) & _] (conj ["AND"] % inside-filter)
-         :else                    inside-filter))))
+        inside-filter [:inside
+                       [:field-id lat-field-id]
+                       [:field-id lon-field-id]
+                       (top-left :lat)
+                       (top-left :lon)
+                       (bottom-right :lat)
+                       (bottom-right :lon)]]
+    (update details :filter mbql.u/combine-filter-clauses inside-filter)))
 
 
 ;;; --------------------------------------------------- RENDERING ----------------------------------------------------
diff --git a/src/metabase/api/user.clj b/src/metabase/api/user.clj
index 62bdebac745030d887337415d1a645d567e1abc7..ace9cd5ec681451e3c19bae7500b4248f72809ea 100644
--- a/src/metabase/api/user.clj
+++ b/src/metabase/api/user.clj
@@ -2,6 +2,7 @@
   "/api/user endpoints"
   (:require [cemerick.friend.credentials :as creds]
             [compojure.core :refer [DELETE GET POST PUT]]
+            [honeysql.helpers :as hh]
             [metabase.api
              [common :as api]
              [session :as session-api]]
@@ -9,8 +10,9 @@
             [metabase.integrations.ldap :as ldap]
             [metabase.models.user :as user :refer [User]]
             [metabase.util :as u]
-            [metabase.util.schema :as su]
-            [puppetlabs.i18n.core :refer [tru]]
+            [metabase.util
+             [i18n :refer [tru]]
+             [schema :as su]]
             [schema.core :as s]
             [toucan
              [db :as db]
@@ -34,10 +36,10 @@
   (cond-> (db/select (vec (cons User (if api/*is-superuser?*
                                        user/admin-or-self-visible-columns
                                        user/non-admin-or-self-visible-columns)))
-            (merge {:order-by [[:%lower.last_name :asc]
-                               [:%lower.first_name :asc]]}
-                   (when-not include_deactivated
-                     {:where [:= :is_active true]})))
+            (-> {}
+                (hh/merge-order-by [:%lower.last_name :asc] [:%lower.first_name :asc])
+                (hh/merge-where (when-not include_deactivated
+                                  [:= :is_active true]))))
     ;; For admins, also include the IDs of the  Users' Personal Collections
     api/*is-superuser?* (hydrate :personal_collection_id)))
 
@@ -86,6 +88,19 @@
   (check-self-or-superuser id)
   (api/check-404 (fetch-user :id id, :is_active true)))
 
+(defn- valid-email-update?
+  "This predicate tests whether or not the user is allowed to update the email address associated with this account."
+  [{:keys [google_auth ldap_auth email] :as foo } maybe-new-email]
+  (or
+   ;; Admin users can update
+   api/*is-superuser?*
+   ;; If the email address didn't change, let it through
+   (= email maybe-new-email)
+   ;; We should not allow a regular user to change their email address if they are a google/ldap user
+   (and
+    (not google_auth)
+    (not ldap_auth))))
+
 (api/defendpoint PUT "/:id"
   "Update an existing, active `User`."
   [id :as {{:keys [email first_name last_name is_superuser login_attributes] :as body} :body}]
@@ -95,18 +110,20 @@
    login_attributes (s/maybe user/LoginAttributes)}
   (check-self-or-superuser id)
   ;; only allow updates if the specified account is active
-  (api/check-404 (db/exists? User, :id id, :is_active true))
-  ;; can't change email if it's already taken BY ANOTHER ACCOUNT
-  (api/checkp (not (db/exists? User, :email email, :id [:not= id]))
-    "email" (tru "Email address already associated to another user."))
-  (api/check-500
-   (db/update! User id
-     (u/select-keys-when body
-       :present (when api/*is-superuser?*
-                  #{:login_attributes})
-       :non-nil (set (concat [:first_name :last_name :email]
-                             (when api/*is-superuser?*
-                               [:is_superuser]))))))
+  (api/let-404 [user-before-update (fetch-user :id id, :is_active true)]
+    ;; Google/LDAP non-admin users can't change their email to prevent account hijacking
+    (api/check-403 (valid-email-update? user-before-update email))
+    ;; can't change email if it's already taken BY ANOTHER ACCOUNT
+    (api/checkp (not (db/exists? User, :email email, :id [:not= id]))
+      "email" (tru "Email address already associated to another user."))
+    (api/check-500
+     (db/update! User id
+       (u/select-keys-when body
+         :present (when api/*is-superuser?*
+                    #{:login_attributes})
+         :non-nil (set (concat [:first_name :last_name :email]
+                               (when api/*is-superuser?*
+                                 [:is_superuser])))))))
   (fetch-user :id id))
 
 (api/defendpoint PUT "/:id/reactivate"
diff --git a/src/metabase/automagic_dashboards/comparison.clj b/src/metabase/automagic_dashboards/comparison.clj
index eddf8b0ef91eb31c8a93b15f1167f5f8a10ab1b3..33c26fa0ec5e86a852a55a6bb930cfdf9da36690 100644
--- a/src/metabase/automagic_dashboards/comparison.clj
+++ b/src/metabase/automagic_dashboards/comparison.clj
@@ -1,119 +1,274 @@
 (ns metabase.automagic-dashboards.comparison
-  #_(:require [clojure.string :as str]
-              [metabase.api.common :as api]
-              [metabase.automagic-dashboards
-               [populate :as populate]]))
-
-;; (defn- dashboard->cards
-;;   [dashboard]
-;;   (->> dashboard
-;;        :ordered_cards
-;;        (map (fn [{:keys [sizeY card col row series]}]
-;;               (assoc card
-;;                 :series   series
-;;                 :height   sizeY
-;;                 :position (+ (* row populate/grid-width) col))))
-;;        (sort-by :position)))
-
-;; (defn- clone-card
-;;   [card]
-;;   (-> card
-;;       (select-keys [:dataset_query :description :display :name :result_metadata
-;;                     :visualization_settings])
-;;       (assoc :creator_id    api/*current-user-id*
-;;              :collection_id (:id (populate/automagic-collection))
-;;              :id            (gensym))))
-
-;; (defn- overlay-comparison?
-;;   [card]
-;;   (and (-> card :display name (#{"bar" "line"}))
-;;        (-> card :series empty?)))
-
-;; (defn- place-row
-;;   [dashboard row left right]
-;;   (let [height       (:height left)
-;;         card-left    (clone-card left)
-;;         card-right   (clone-card right)]
-;;     (if (overlay-comparison? left)
-;;       (update dashboard :ordered_cards conj {:col                    0
-;;                                              :row                    row
-;;                                              :sizeX                  populate/grid-width
-;;                                              :sizeY                  height
-;;                                              :card                   card-left
-;;                                              :card_id                (:id card-left)
-;;                                              :series                 [card-right]
-;;                                              :visualization_settings {}
-;;                                              :id                     (gensym)})
-;;       (let [width (/ populate/grid-width 2)
-;;             series-left  (map clone-card (:series left))
-;;             series-right (map clone-card (:series right))]
-;;         (-> dashboard
-;;             (update :ordered_cards conj {:col                    0
-;;                                          :row                    row
-;;                                          :sizeX                  width
-;;                                          :sizeY                  height
-;;                                          :card                   card-left
-;;                                          :card_id                (:id card-left)
-;;                                          :series                 series-left
-;;                                          :visualization_settings {}
-;;                                          :id                     (gensym)})
-;;             (update :ordered_cards conj {:col                    width
-;;                                          :row                    row
-;;                                          :sizeX                  width
-;;                                          :sizeY                  height
-;;                                          :card                   card-right
-;;                                          :card_id                (:id card-right)
-;;                                          :series                 series-right
-;;                                          :visualization_settings {}
-;;                                          :id                     (gensym)}))))))
-
-;; (def ^:private ^Long ^:const title-height 2)
-
-;; (defn- add-col-title
-;;   [dashboard title col]
-;;   (populate/add-text-card dashboard {:text   (format "# %s" title)
-;;                                      :width  (/ populate/grid-width 2)
-;;                                      :height title-height}
-;;                           [0 col]))
-
-;; (defn- unroll-multiseries
-;;   [card]
-;;   (if (and (-> card :display name (= "line"))
-;;            (-> card :dataset_query :query :aggregation count (> 1)))
-;;     (for [aggregation (-> card :dataset_query :query :aggregation)]
-;;       (assoc-in card [:dataset_query :query :aggregation] [aggregation]))
-;;     [card]))
-
-;; (defn- inject-segment
-;;   "Inject filter clause into card."
-;;   [query-filter card]
-;;   (-> card
-;;       (update-in [:dataset_query :query :filter] merge-filters query-filter)
-;;       (update :series (partial map (partial inject-segment query-filter)))))
+  (:require [medley.core :as m]
+            [metabase
+             [related :as related]
+             [util :as u]]
+            [metabase.api.common :as api]
+            [metabase.automagic-dashboards
+             [core :refer [->field ->related-entity ->root automagic-analysis capitalize-first cell-title
+                           encode-base64-json metric-name source-name]]
+             [filters :as filters]
+             [populate :as populate]]
+            [metabase.mbql
+             [normalize :as normalize]
+             [util :as mbql.u]]
+            [metabase.models.table :refer [Table]]
+            [metabase.query-processor.util :as qp.util]
+            [puppetlabs.i18n.core :as i18n :refer [tru]]))
+
+(def ^:private ^{:arglists '([root])} comparison-name
+  (comp capitalize-first (some-fn :comparison-name :full-name)))
+
+(defn- dashboard->cards
+  [dashboard]
+  (->> dashboard
+       :ordered_cards
+       (map (fn [{:keys [sizeY card col row series] :as dashcard}]
+              (assoc card
+                :text     (-> dashcard :visualization_settings :text)
+                :series   series
+                :height   sizeY
+                :position (+ (* row populate/grid-width) col))))
+       (sort-by :position)))
+
+(defn- clone-card
+  [card]
+  (-> card
+      (select-keys [:dataset_query :description :display :name :result_metadata
+                    :visualization_settings])
+      (assoc :creator_id    api/*current-user-id*
+             :collection_id nil
+             :id            (gensym))))
+
+(def ^:private ^{:arglists '([card])} display-type
+  (comp qp.util/normalize-token :display))
+
+(defn- inject-filter
+  "Inject filter clause into card."
+  [{:keys [query-filter cell-query] :as root} card]
+  (-> card
+      (update :dataset_query #(reduce mbql.u/add-filter-clause
+                                      %
+                                      (map (partial normalize/normalize-fragment [:query :filter])
+                                           [query-filter cell-query])))
+      (update :series (partial map (partial inject-filter root)))))
+
+(defn- multiseries?
+  [card]
+  (or (-> card :series not-empty)
+      (-> card (get-in [:dataset_query :query :aggregation]) count (> 1))
+      (-> card (get-in [:dataset_query :query :breakout]) count (> 1))))
+
+(defn- overlay-comparison?
+  [card]
+  (and (-> card display-type (#{:bar :line}))
+       (not (multiseries? card))))
+
+(defn- comparison-row
+  [dashboard row left right card]
+  (if (:display card)
+    (let [height                   (:height card)
+          card-left                (->> card (inject-filter left) clone-card)
+          card-right               (->> card (inject-filter right) clone-card)
+          [color-left color-right] (->> [left right]
+                                        (map #(get-in % [:dataset_query :query :filter]))
+                                        populate/map-to-colors)]
+      (if (overlay-comparison? card)
+        (let [card   (-> card-left
+                         (assoc-in [:visualization_settings :graph.colors] [color-left color-right])
+                         (update :name #(format "%s (%s)" % (comparison-name left))))
+              series (-> card-right
+                         (update :name #(format "%s (%s)" % (comparison-name right)))
+                         vector)]
+          (update dashboard :ordered_cards conj {:col                    0
+                                                 :row                    row
+                                                 :sizeX                  populate/grid-width
+                                                 :sizeY                  height
+                                                 :card                   card
+                                                 :card_id                (:id card)
+                                                 :series                 series
+                                                 :visualization_settings {:graph.y_axis.auto_split false}
+                                                 :id                     (gensym)}))
+        (let [width        (/ populate/grid-width 2)
+              series-left  (map clone-card (:series card-left))
+              series-right (map clone-card (:series card-right))
+              card-left    (cond-> card-left
+                             (not (multiseries? card-left))
+                             (assoc-in [:visualization_settings :graph.colors] [color-left]))
+              card-right   (cond-> card-right
+                             (not (multiseries? card-right))
+                             (assoc-in [:visualization_settings :graph.colors] [color-right]))]
+          (-> dashboard
+              (update :ordered_cards conj {:col                    0
+                                           :row                    row
+                                           :sizeX                  width
+                                           :sizeY                  height
+                                           :card                   card-left
+                                           :card_id                (:id card-left)
+                                           :series                 series-left
+                                           :visualization_settings {}
+                                           :id                     (gensym)})
+              (update :ordered_cards conj {:col                    width
+                                           :row                    row
+                                           :sizeX                  width
+                                           :sizeY                  height
+                                           :card                   card-right
+                                           :card_id                (:id card-right)
+                                           :series                 series-right
+                                           :visualization_settings {}
+                                           :id                     (gensym)})))))
+    (populate/add-text-card dashboard {:text                   (:text card)
+                                       :width                  (/ populate/grid-width 2)
+                                       :height                 (:height card)
+                                       :visualization-settings {:dashcard.background false
+                                                                :text.align_vertical :bottom}}
+                            [row 0])))
+
+(def ^:private ^Long ^:const title-height 2)
+
+(defn- add-col-title
+  [dashboard title description col]
+  (let [height (cond-> title-height
+                 description inc)]
+    [(populate/add-text-card dashboard
+                             {:text                   (if description
+                                                        (format "# %s\n\n%s" title description)
+                                                        (format "# %s" title))
+                              :width                  (/ populate/grid-width 2)
+                              :height                 height
+                              :visualization-settings {:dashcard.background false
+                                                       :text.align_vertical :top}}
+                             [0 col])
+     height]))
+
+(defn- add-title-row
+  [dashboard left right]
+  (let [[dashboard height-left]  (add-col-title dashboard
+                                                (comparison-name left)
+                                                (-> left :entity :description) 0)
+        [dashboard height-right] (add-col-title dashboard
+                                                (comparison-name right)
+                                                (-> right :entity :description)
+                                                (/ populate/grid-width 2))]
+    [dashboard (max height-left height-right)]))
+
+(defn- series-labels
+  [card]
+  (get-in card [:visualization_settings :graph.series_labels]
+          (map (comp capitalize-first metric-name)
+               (get-in card [:dataset_query :query :aggregation]))))
+
+(defn- unroll-multiseries
+  [card]
+  (if (and (multiseries? card)
+           (-> card :display (= :line)))
+    (for [[aggregation label] (map vector
+                                   (get-in card [:dataset_query :query :aggregation])
+                                   (series-labels card))]
+      (-> card
+          (assoc-in [:dataset_query :query :aggregation] [aggregation])
+          (assoc :name label)
+          (m/dissoc-in [:visualization_settings :graph.series_labels])))
+    [card]))
+
+(defn- segment-constituents
+  [segment]
+  (->> (filters/inject-refinement (:query-filter segment) (:cell-query segment))
+       filters/collect-field-references
+       (map filters/field-reference->id)
+       distinct
+       (map (partial ->field segment))))
+
+(defn- update-related
+  [related left right]
+  (-> related
+      (update :related (comp distinct conj) (-> right :entity ->related-entity))
+      (assoc :compare (concat
+                       (for [segment (->> left :entity related/related :segments (map ->root))
+                             :when (not= segment right)]
+                         {:url         (str (:url left) "/compare/segment/"
+                                            (-> segment :entity u/get-id))
+                          :title       (tru "Compare with {0}" (:comparison-name segment))
+                          :description ""})
+                       (when (and ((some-fn :query-filter :cell-query) left)
+                                  (not= (:source left) (:entity right)))
+                         [{:url         (if (->> left :source (instance? (type Table)))
+                                          (str (:url left) "/compare/table/"
+                                               (-> left :source u/get-id))
+                                          (str (:url left) "/compare/adhoc/"
+                                               (encode-base64-json
+                                                {:database (:database left)
+                                                 :type     :query
+                                                 :query    {:source-table (->> left
+                                                                               :source
+                                                                               u/get-id
+                                                                               (str "card__" ))}})))
+                           :title       (tru "Compare with entire dataset")
+                           :description ""}])))
+      (as-> related
+          (if (-> related :compare empty?)
+            (dissoc related :compare)
+            related))))
+
+(defn- part-vs-whole-comparison?
+  [left right]
+  (and ((some-fn :cell-query :query-filter) left)
+       (not ((some-fn :cell-query :query-filter) right))))
 
 (defn comparison-dashboard
   "Create a comparison dashboard based on dashboard `dashboard` comparing subsets of
    the dataset defined by segments `left` and `right`."
-  [dashboard left right]
-  ;; (->> dashboard
-  ;;      dashboard->cards
-  ;;      (mapcat unroll-multiseries)
-  ;;      (map (juxt (partial inject-segment left)
-  ;;                 (partial inject-segment right)))
-  ;;      (reduce (fn [[dashboard row] [left right]]
-  ;;                [(place-row dashboard row left right)
-  ;;                 (+ row (:height left))])
-  ;;              [(-> {:name        (format "Comparison of %s and %s"
-  ;;                                         (full-name left)
-  ;;                                         (full-name right))
-  ;;                    :description (format "Automatically generated comparison dashboard comparing %s and %s"
-  ;;                                         (full-name left)
-  ;;                                         (full-name right))
-  ;;                    :creator_id  api/*current-user-id*
-  ;;                    :parameters  []}
-  ;;                   (add-col-title (-> left full-name str/capitalize) 0)
-  ;;                   (add-col-title (-> right full-name str/capitalize)
-  ;;                                  (/ populate/grid-width 2)))
-  ;;               title-height])
-  ;;      first)
-  )
+  [dashboard left right opts]
+  (let [left               (-> left
+                               ->root
+                               (merge (:left opts)))
+        right              (-> right
+                               ->root
+                               (merge (:right opts)))
+        left               (cond-> left
+                             (-> opts :left :cell-query)
+                             (assoc :comparison-name (->> opts
+                                                          :left
+                                                          :cell-query
+                                                          (cell-title left))))
+        right              (cond-> right
+                             (part-vs-whole-comparison? left right)
+                             (assoc :comparison-name (condp instance? (:entity right)
+                                                       (type Table)
+                                                       (tru "All {0}" (:short-name right))
+
+                                                       (tru "{0}, all {1}"
+                                                            (comparison-name right)
+                                                            (source-name right)))))
+        segment-dashboards (->> (concat (segment-constituents left)
+                                        (segment-constituents right))
+                                distinct
+                                (map #(automagic-analysis % {:source       (:source left)
+                                                             :rules-prefix ["comparison"]})))]
+    (assert (or (= (:source left) (:source right))
+                (= (-> left :source :table_id) (-> right :source u/get-id))))
+    (->> (concat segment-dashboards [dashboard])
+         (reduce #(populate/merge-dashboards %1 %2 {:skip-titles? true}))
+         dashboard->cards
+         (m/distinct-by (some-fn :dataset_query hash))
+         (transduce (mapcat unroll-multiseries)
+                    (fn
+                      ([]
+                       (let [title (tru "Comparison of {0} and {1}"
+                                        (comparison-name left)
+                                        (comparison-name right))]
+                         (-> {:name              title
+                              :transient_name    title
+                              :transient_filters nil
+                              :param_fields      nil
+                              :description       (tru "Automatically generated comparison dashboard comparing {0} and {1}"
+                                                      (comparison-name left)
+                                                      (comparison-name right))
+                              :creator_id        api/*current-user-id*
+                              :parameters        []
+                              :related           (update-related (:related dashboard) left right)}
+                             (add-title-row left right))))
+                      ([[dashboard row]] dashboard)
+                      ([[dashboard row] card]
+                       [(comparison-row dashboard row left right card)
+                        (+ row (:height card))]))))))
diff --git a/src/metabase/automagic_dashboards/core.clj b/src/metabase/automagic_dashboards/core.clj
index 844cf3725b248d3a89019993566cc6f5ad863ac3..4ad7cc6d871e9571b5304fd531549e356e2a9e8d 100644
--- a/src/metabase/automagic_dashboards/core.clj
+++ b/src/metabase/automagic_dashboards/core.clj
@@ -6,112 +6,161 @@
             [clj-time
              [core :as t]
              [format :as t.format]]
+            [clojure
+             [string :as str]
+             [walk :as walk]]
             [clojure.math.combinatorics :as combo]
-            [clojure.string :as str]
             [clojure.tools.logging :as log]
-            [clojure.walk :as walk]
             [kixi.stats
              [core :as stats]
              [math :as math]]
             [medley.core :as m]
+            [metabase
+             [driver :as driver]
+             [related :as related]
+             [util :as u]]
             [metabase.automagic-dashboards
              [filters :as filters]
              [populate :as populate]
              [rules :as rules]]
+            [metabase.mbql
+             [normalize :as normalize]
+             [util :as mbql.u]]
             [metabase.models
              [card :as card :refer [Card]]
              [database :refer [Database]]
-             [field :refer [Field] :as field]
+             [field :as field :refer [Field]]
              [interface :as mi]
-             [metric :refer [Metric] :as metric]
+             [metric :as metric :refer [Metric]]
              [query :refer [Query]]
              [segment :refer [Segment]]
              [table :refer [Table]]]
-            [metabase.query-processor.middleware.expand-macros :refer [merge-filter-clauses]]
+            [metabase.query-processor.middleware.expand-macros :as qp.expand]
             [metabase.query-processor.util :as qp.util]
-            [metabase.related :as related]
             [metabase.sync.analyze.classify :as classify]
-            [metabase.util :as u]
-            [puppetlabs.i18n.core :as i18n :refer [tru trs]]
+            [metabase.util.date :as date]
+            [puppetlabs.i18n.core :as i18n :refer [trs tru]]
             [ring.util.codec :as codec]
             [schema.core :as s]
-            [toucan.db :as db]))
+            [toucan.db :as db])
+  (:import java.util.TimeZone))
 
 (def ^:private public-endpoint "/auto/dashboard/")
 
 (def ^:private ^{:arglists '([field])} id-or-name
   (some-fn :id :name))
 
-(defn- ->field
+(defn ->field
+  "Return `Field` instance for a given ID or name in the context of root."
   [root id-or-name]
   (if (->> root :source (instance? (type Table)))
     (Field id-or-name)
-    (let [field (->> root
-                     :source
-                     :result_metadata
-                     (some (comp #{id-or-name} :name)))]
+    (when-let [field (->> root
+                          :source
+                          :result_metadata
+                          (m/find-first (comp #{id-or-name} :name)))]
       (-> field
           (update :base_type keyword)
           (update :special_type keyword)
           field/map->FieldInstance
           (classify/run-classifiers {})))))
 
-(defn- metric->description
-  [root metric]
-  (let [aggregation-clause (-> metric :definition :aggregation first)
-        field              (some->> aggregation-clause
-                                    second
-                                    filters/field-reference->id
-                                    (->field root))]
-    (if field
-      (tru "{0} of {1}" (-> aggregation-clause first name str/capitalize) (:display_name field))
-      (-> aggregation-clause first name str/capitalize))))
+(def ^{:arglists '([root])} source-name
+  "Return the (display) name of the soruce of a given root object."
+  (comp (some-fn :display_name :name) :source))
+
+(def ^:private op->name
+  {:sum       (tru "sum")
+   :avg       (tru "average")
+   :min       (tru "minumum")
+   :max       (tru "maximum")
+   :count     (tru "number")
+   :distinct  (tru "distinct count")
+   :stddev    (tru "standard deviation")
+   :cum-count (tru "cumulative count")
+   :cum-sum   (tru "cumulative sum")})
+
+(def ^:private ^{:arglists '([metric])} saved-metric?
+  (every-pred (partial mbql.u/is-clause? :metric)
+              (complement qp.expand/ga-metric?)))
+
+(def ^:private ^{:arglists '([metric])} custom-expression?
+  (partial mbql.u/is-clause? :named))
+
+(def ^:private ^{:arglists '([metric])} adhoc-metric?
+  (complement (some-fn saved-metric? custom-expression?)))
+
+(defn metric-name
+  "Return the name of the metric or name by describing it."
+  [[op & args :as metric]]
+  (cond
+    (qp.expand/ga-metric? metric) (-> args first str (subs 3) str/capitalize)
+    (adhoc-metric? metric)        (-> op qp.util/normalize-token op->name)
+    (saved-metric? metric)        (-> args first Metric :name)
+    :else                         (second args)))
+
+(defn metric-op
+  "Return the name op of the metric"
+  [[op & args :as metric]]
+  (if (saved-metric? metric)
+    (-> args first Metric (get-in [:definition :aggregation 0 0]))
+    op))
 
 (defn- join-enumeration
-  [[x & xs]]
-  (if xs
+  [xs]
+  (if (next xs)
     (tru "{0} and {1}" (str/join ", " (butlast xs)) (last xs))
-    x))
+    (first xs)))
+
+(defn- metric->description
+  [root aggregation-clause]
+  (join-enumeration
+   (for [metric (if (sequential? (first aggregation-clause))
+                  aggregation-clause
+                  [aggregation-clause])]
+     (if (adhoc-metric? metric)
+       (tru "{0} of {1}" (metric-name metric) (or (some->> metric
+                                                           second
+                                                           filters/field-reference->id
+                                                           (->field root)
+                                                           :display_name)
+                                                  (source-name root)))
+       (metric-name metric)))))
 
 (defn- question-description
   [root question]
-  (let [aggregations (->> (qp.util/get-in-normalized question [:dataset_query :query :aggregation])
-                          (map (fn [[op arg]]
-                                 (cond
-                                   (-> op qp.util/normalize-token (= :metric))
-                                   (-> arg Metric :name)
-
-                                   arg
-                                   (tru "{0} of {1}" (name op) (->> arg
-                                                                    filters/field-reference->id
-                                                                    (->field root)
-                                                                    :display_name))
-
-                                   :else
-                                   (name op))))
-                          join-enumeration)
-        dimensions   (->> (qp.util/get-in-normalized question [:dataset_query :query :breakout])
+  (let [aggregations (->> (get-in question [:dataset_query :query :aggregation])
+                          (metric->description root))
+        dimensions   (->> (get-in question [:dataset_query :query :breakout])
                           (mapcat filters/collect-field-references)
                           (map (comp :display_name
                                      (partial ->field root)
                                      filters/field-reference->id))
                           join-enumeration)]
-    (tru "{0} by {1}" aggregations dimensions)))
+    (if dimensions
+      (tru "{0} by {1}" aggregations dimensions)
+      aggregations)))
 
-(def ^:private ^{:arglists '([x])} encode-base64-json
+(def ^{:arglists '([x])} encode-base64-json
+  "Encode given object as base-64 encoded JSON."
   (comp codec/base64-encode codecs/str->bytes json/encode))
 
+(defn- ga-table?
+  [table]
+  (isa? (:entity_type table) :entity/GoogleAnalyticsTable))
+
 (defmulti
-  ^{:private  true
+  ^{:doc ""
     :arglists '([entity])}
   ->root type)
 
 (defmethod ->root (type Table)
   [table]
   {:entity       table
-   :full-name    (if (isa? (:entity_type table) :entity/GoogleAnalyticsTable)
+   :full-name    (if (ga-table? table)
                    (:display_name table)
                    (tru "{0} table" (:display_name table)))
+   :short-name   (:display_name table)
    :source       table
    :database     (:db_id table)
    :url          (format "%stable/%s" public-endpoint (u/get-id table))
@@ -120,19 +169,24 @@
 (defmethod ->root (type Segment)
   [segment]
   (let [table (-> segment :table_id Table)]
-    {:entity       segment
-     :full-name    (tru "{0} segment" (:name segment))
-     :source       table
-     :database     (:db_id table)
-     :query-filter (-> segment :definition :filter)
-     :url          (format "%ssegment/%s" public-endpoint (u/get-id segment))
-     :rules-prefix ["table"]}))
+    {:entity          segment
+     :full-name       (tru "{0} in the {1} segment" (:display_name table) (:name segment))
+     :short-name      (:display_name table)
+     :comparison-name (tru "{0} segment" (:name segment))
+     :source          table
+     :database        (:db_id table)
+     :query-filter    [:segment (u/get-id segment)]
+     :url             (format "%ssegment/%s" public-endpoint (u/get-id segment))
+     :rules-prefix    ["table"]}))
 
 (defmethod ->root (type Metric)
   [metric]
   (let [table (-> metric :table_id Table)]
     {:entity       metric
-     :full-name    (tru "{0} metric" (:name metric))
+     :full-name    (if (:id metric)
+                     (tru "{0} metric" (:name metric))
+                     (:name metric))
+     :short-name   (:name metric)
      :source       table
      :database     (:db_id table)
      ;; We use :id here as it might not be a concrete field but rather one from a nested query which
@@ -145,6 +199,7 @@
   (let [table (field/table field)]
     {:entity       field
      :full-name    (tru "{0} field" (:display_name field))
+     :short-name   (:display_name field)
      :source       table
      :database     (:db_id table)
      ;; We use :id here as it might not be a concrete metric but rather one from a nested query
@@ -156,18 +211,24 @@
   (comp qp.util/query->source-card-id :dataset_query))
 
 (def ^:private ^{:arglists '([card-or-question])} native-query?
-  (comp #{:native} qp.util/normalize-token #(qp.util/get-in-normalized % [:dataset_query :type])))
+  (comp #{:native} qp.util/normalize-token #(get-in % [:dataset_query :type])))
 
 (def ^:private ^{:arglists '([card-or-question])} source-question
   (comp Card qp.util/query->source-card-id :dataset_query))
 
 (defn- table-like?
   [card-or-question]
-  (let [[aggregation & _] (qp.util/get-in-normalized card-or-question [:dataset_query :query :aggregation])]
-    (or (nil? aggregation)
-        (and (or (string? aggregation)
-                 (keyword? aggregation))
-             (= (qp.util/normalize-token aggregation) :rows)))))
+  (nil? (get-in card-or-question [:dataset_query :query :aggregation])))
+
+(defn- table-id
+  "Get the Table ID from `card-or-question`, which can be either a Card from the DB (which has a `:table_id` property)
+  or an ad-hoc query (referred to as a 'question' in this namespace) created with the
+  `metabase.models.query/adhoc-query` function, which has a `:table-id` property."
+  ;; TODO - probably better if we just changed `adhoc-query` to use the same keys as Cards (e.g. `:table_id`) so we
+  ;; didn't need this function, seems like something that would be too easy to forget
+  [card-or-question]
+  (or (:table_id card-or-question)
+      (:table-id card-or-question)))
 
 (defn- source
   [card]
@@ -176,31 +237,35 @@
                              source-question
                              (assoc :entity_type :entity/GenericTable))
     (native-query? card) (-> card (assoc :entity_type :entity/GenericTable))
-    :else                (-> card ((some-fn :table_id :table-id)) Table)))
+    :else                (-> card table-id Table)))
 
 (defmethod ->root (type Card)
   [card]
-  {:entity       card
-   :source       (source card)
-   :database     (:database_id card)
-   :query-filter (qp.util/get-in-normalized card [:dataset_query :query :filter])
-   :full-name    (tru "{0} question" (:name card))
-   :url          (format "%squestion/%s" public-endpoint (u/get-id card))
-   :rules-prefix [(if (table-like? card)
-                    "table"
-                    "question")]})
+  (let [source (source card)]
+    {:entity       card
+     :source       source
+     :database     (:database_id card)
+     :query-filter (get-in card [:dataset_query :query :filter])
+     :full-name    (tru "\"{0}\" question" (:name card))
+     :short-name   (source-name {:source source})
+     :url          (format "%squestion/%s" public-endpoint (u/get-id card))
+     :rules-prefix [(if (table-like? card)
+                      "table"
+                      "question")]}))
 
 (defmethod ->root (type Query)
   [query]
-  (let [source   (source query)]
+  (let [source (source query)]
     {:entity       query
      :source       source
      :database     (:database-id query)
+     :query-filter (get-in query [:dataset_query :query :filter])
      :full-name    (cond
                      (native-query? query) (tru "Native query")
                      (table-like? query)   (-> source ->root :full-name)
                      :else                 (question-description {:source source} query))
-     :url          (format "%sadhoc/%s" public-endpoint (encode-base64-json query))
+     :short-name   (source-name {:source source})
+     :url          (format "%sadhoc/%s" public-endpoint (encode-base64-json (:dataset_query query)))
      :rules-prefix [(if (table-like? query)
                       "table"
                       "question")]}))
@@ -220,7 +285,7 @@
                                       :type
                                       :type/DateTime
                                       ((juxt :earliest :latest))
-                                      (map t.format/parse))]
+                                      (map date/str->date-time))]
     (condp > (t/in-hours (t/interval earliest latest))
       3               :minute
       (* 24 7)        :hour
@@ -230,7 +295,7 @@
     :day))
 
 (defmethod ->reference [:mbql (type Field)]
-  [_ {:keys [fk_target_field_id id link aggregation fingerprint name base_type] :as field}]
+  [_ {:keys [fk_target_field_id id link aggregation name base_type] :as field}]
   (let [reference (cond
                     link               [:fk-> link id]
                     fk_target_field_id [:fk-> id fk_target_field_id]
@@ -242,8 +307,7 @@
                                      (optimal-datetime-resolution field))]
 
       (and aggregation
-           ; We don't handle binning on non-analyzed fields gracefully
-           (-> fingerprint :type :type/Number :min))
+           (isa? base_type :type/Number))
       [:binning-strategy reference aggregation]
 
       :else
@@ -264,7 +328,7 @@
 (defmethod ->reference [:mbql (type Metric)]
   [_ {:keys [id definition]}]
   (if id
-    ["METRIC" id]
+    [:metric id]
     (-> definition :aggregation first)))
 
 (defmethod ->reference [:native (type Field)]
@@ -349,8 +413,13 @@
                                  bindings)
                           (comp first #(filter-tables % tables) rules/->entity)
                           identity)]
-    (str/replace s #"\[\[(\w+)\]\]" (fn [[_ identifier]]
-                                     (->reference template-type (bindings identifier))))))
+    (str/replace s #"\[\[(\w+)(?:\.([\w\-]+))?\]\]"
+                 (fn [[_ identifier attribute]]
+                   (let [entity    (bindings identifier)
+                         attribute (some-> attribute qp.util/normalize-token)]
+                     (or (and (ifn? entity) (entity attribute))
+                         (root attribute)
+                         (->reference template-type entity)))))))
 
 (defn- field-candidates
   [context {:keys [field_type links_to named max_cardinality] :as constraints}]
@@ -421,12 +490,12 @@
   [dimensions metrics order-by]
   (let [dimensions (set dimensions)]
     (for [[identifier ordering] (map first order-by)]
-      [(if (dimensions identifier)
-          [:dimension identifier]
-          [:aggregation (u/index-of #{identifier} metrics)])
-        (if (= ordering "ascending")
-          :ascending
-          :descending)])))
+      [(if (= ordering "ascending")
+         :asc
+         :desc)
+       (if (dimensions identifier)
+         [:dimension identifier]
+         [:aggregation (u/index-of #{identifier} metrics)])])))
 
 (defn- build-query
   ([context bindings filters metrics dimensions limit order_by]
@@ -438,25 +507,28 @@
         subform))
     {:type     :query
      :database (-> context :root :database)
-     :query    (cond-> {:source_table (if (->> context :source (instance? (type Table)))
+     :query    (cond-> {:source-table (if (->> context :source (instance? (type Table)))
                                         (-> context :source u/get-id)
                                         (->> context :source u/get-id (str "card__")))}
-                 (not-empty filters)
-                 (assoc :filter (transduce (map :filter)
-                                           merge-filter-clauses
-                                           filters))
-
-                 (not-empty dimensions)
+                 (seq filters)
+                 (assoc :filter (apply
+                                 vector
+                                 :and
+                                 (map (comp (partial normalize/normalize-fragment [:query :filter])
+                                            :filter)
+                                      filters)))
+
+                 (seq dimensions)
                  (assoc :breakout dimensions)
 
-                 (not-empty metrics)
+                 (seq metrics)
                  (assoc :aggregation (map :metric metrics))
 
                  limit
                  (assoc :limit limit)
 
-                 (not-empty order_by)
-                 (assoc :order_by order_by))}))
+                 (seq order_by)
+                 (assoc :order-by order_by))}))
   ([context bindings query]
    {:type     :native
     :native   {:query (fill-templates :native context bindings query)}
@@ -469,8 +541,8 @@
        (every? (partial get dimensions))))
 
 (defn- resolve-overloading
-  "Find the overloaded definition with the highest `score` for which all
-   referenced dimensions have at least one matching field."
+  "Find the overloaded definition with the highest `score` for which all referenced dimensions have at least one
+  matching field."
   [{:keys [dimensions]} definitions]
   (apply merge-with (fn [a b]
                       (case (map (partial has-matches? dimensions) [a b])
@@ -489,20 +561,43 @@
            (u/update-when :graph.metrics metric->name)
            (u/update-when :graph.dimensions dimension->name))]))
 
+(defn capitalize-first
+  "Capitalize only the first letter in a given string."
+  [s]
+  (str (str/upper-case (subs s 0 1)) (subs s 1)))
+
 (defn- instantiate-metadata
   [x context bindings]
   (-> (walk/postwalk
        (fn [form]
          (if (string? form)
-           (fill-templates :string context bindings form)
+           (let [new-form (fill-templates :string context bindings form)]
+             (if (not= new-form form)
+               (capitalize-first new-form)
+               new-form))
            form))
        x)
       (u/update-when :visualization #(instantate-visualization % bindings (:metrics context)))))
 
 (defn- valid-breakout-dimension?
-  [{:keys [base_type engine] :as f}]
-  (not (and (isa? base_type :type/Number)
-            (= engine :druid))))
+  [{:keys [base_type engine fingerprint aggregation]}]
+  (or (nil? aggregation)
+      (not (isa? base_type :type/Number))
+      (and (driver/driver-supports? (driver/engine->driver engine) :binning)
+           (-> fingerprint :type :type/Number :min))))
+
+(defn- singular-cell-dimensions
+  [root]
+  (letfn [(collect-dimensions [[op & args]]
+            (case (some-> op qp.util/normalize-token)
+              :and (mapcat collect-dimensions args)
+              :=   (filters/collect-field-references args)
+              nil))]
+    (->> root
+         :cell-query
+         collect-dimensions
+         (map filters/field-reference->id)
+         set)))
 
 (defn- card-candidates
   "Generate all potential cards given a card definition and bindings for
@@ -511,8 +606,7 @@
   (let [order_by        (build-order-by dimensions metrics order_by)
         metrics         (map (partial get (:metrics context)) metrics)
         filters         (cond-> (map (partial get (:filters context)) filters)
-                          (:query-filter context)
-                          (conj {:filter (:query-filter context)}))
+                          (:query-filter context) (conj {:filter (:query-filter context)}))
         score           (if query
                           score
                           (* (or (->> dimensions
@@ -522,33 +616,39 @@
                                  rules/max-score)
                              (/ score rules/max-score)))
         dimensions      (map (comp (partial into [:dimension]) first) dimensions)
-        used-dimensions (rules/collect-dimensions [dimensions metrics filters query])]
+        used-dimensions (rules/collect-dimensions [dimensions metrics filters query])
+        cell-dimension? (->> context :root singular-cell-dimensions)]
     (->> used-dimensions
          (map (some-fn #(get-in (:dimensions context) [% :matches])
                        (comp #(filter-tables % (:tables context)) rules/->entity)))
          (apply combo/cartesian-product)
-         (filter (fn [instantiations]
+         (map (partial zipmap used-dimensions))
+         (filter (fn [bindings]
                    (->> dimensions
-                        (map (comp (zipmap used-dimensions instantiations) second))
-                        (every? valid-breakout-dimension?))))
-         (map (fn [instantiations]
-                (let [bindings (zipmap used-dimensions instantiations)
-                      query    (if query
-                                 (build-query context bindings query)
-                                 (build-query context bindings
-                                              filters
-                                              metrics
-                                              dimensions
-                                              limit
-                                              order_by))]
+                        (map (fn [[_ identifier opts]]
+                               (merge (bindings identifier) opts)))
+                        (every? (every-pred valid-breakout-dimension?
+                                            (complement (comp cell-dimension? id-or-name)))))))
+         (map (fn [bindings]
+                (let [query (if query
+                              (build-query context bindings query)
+                              (build-query context bindings
+                                           filters
+                                           metrics
+                                           dimensions
+                                           limit
+                                           order_by))]
                   (-> card
-                      (assoc :metrics metrics)
                       (instantiate-metadata context (->> metrics
                                                          (map :name)
                                                          (zipmap (:metrics card))
                                                          (merge bindings)))
-                      (assoc :score         score
-                             :dataset_query query))))))))
+                      (assoc :dataset_query query
+                             :metrics       (for [metric metrics]
+                                              {:name ((some-fn :name (comp metric-name :metric)) metric)
+                                               :op   (-> metric :metric metric-op)})
+                             :dimensions    (map (comp :name bindings second) dimensions)
+                             :score         score))))))))
 
 (defn- matching-rules
   "Return matching rules orderd by specificity.
@@ -571,11 +671,15 @@
   [table]
   (for [{:keys [id target]} (field/with-targets
                               (db/select Field
-                                         :table_id           (u/get-id table)
-                                         :fk_target_field_id [:not= nil]))
+                                :table_id           (u/get-id table)
+                                :fk_target_field_id [:not= nil]
+                                :active             true))
         :when (some-> target mi/can-read?)]
     (-> target field/table (assoc :link id))))
 
+(def ^:private ^{:arglists '([source])} source->engine
+  (comp :engine Database (some-fn :db_id :database_id)))
+
 (defmulti
   ^{:private  true
     :arglists '([context entity])}
@@ -583,10 +687,12 @@
 
 (defmethod inject-root (type Field)
   [context field]
-  (let [field (assoc field :link (->> context
-                                      :tables
-                                      (m/find-first (comp #{(:table_id field)} u/get-id))
-                                      :link))]
+  (let [field (assoc field
+                :link   (->> context
+                             :tables
+                             (m/find-first (comp #{(:table_id field)} u/get-id))
+                             :link)
+                :engine (-> context :source source->engine))]
     (update context :dimensions
             (fn [dimensions]
               (->> dimensions
@@ -617,11 +723,12 @@
   (let [source        (:source root)
         tables        (concat [source] (when (instance? (type Table) source)
                                          (linked-tables source)))
-        engine        (-> source ((some-fn :db_id :database_id)) Database :engine)
+        engine        (source->engine source)
         table->fields (if (instance? (type Table) source)
                         (comp (->> (db/select Field
-                                              :table_id        [:in (map u/get-id tables)]
-                                              :visibility_type "normal")
+                                     :table_id        [:in (map u/get-id tables)]
+                                     :visibility_type "normal"
+                                     :active          true)
                                    field/with-targets
                                    (map #(assoc % :engine engine))
                                    (group-by :table_id))
@@ -634,7 +741,7 @@
                                         (update :special_type keyword)
                                         field/map->FieldInstance
                                         (classify/run-classifiers {})
-                                        (map #(assoc % :engine engine)))))
+                                        (assoc :engine engine))))
                              constantly))]
     (as-> {:source       (assoc source :fields (table->fields source))
            :root         root
@@ -666,8 +773,11 @@
   ([root rule context]
    (-> rule
        (select-keys [:title :description :transient_title :groups])
-       (instantiate-metadata context {})
-       (assoc :refinements (:cell-query root)))))
+       (cond->
+         (:comparison? root)
+         (update :groups (partial m/map-vals (fn [{:keys [title comparison_title] :as group}]
+                                               (assoc group :title (or comparison_title title))))))
+       (instantiate-metadata context {}))))
 
 (s/defn ^:private apply-rule
   [root, rule :- rules/Rule]
@@ -675,20 +785,22 @@
         dashboard (make-dashboard root rule context)
         filters   (->> rule
                        :dashboard_filters
-                       (mapcat (comp :matches (:dimensions context))))
+                       (mapcat (comp :matches (:dimensions context)))
+                       (remove (comp (singular-cell-dimensions root) id-or-name)))
         cards     (make-cards context rule)]
     (when (or (not-empty cards)
               (-> rule :cards nil?))
       [(assoc dashboard
-         :filters  filters
-         :cards    cards
-         :context  context)
-       rule])))
+         :filters filters
+         :cards   cards)
+       rule
+       context])))
 
-(def ^:private ^:const ^Long max-related 6)
+(def ^:private ^:const ^Long max-related 8)
 (def ^:private ^:const ^Long max-cards 15)
 
-(defn- ->related-entity
+(defn ->related-entity
+  "Turn `entity` into an entry in `:related.`"
   [entity]
   (let [root      (->root entity)
         rule      (->> root
@@ -700,84 +812,193 @@
      :description (:description dashboard)}))
 
 (defn- related-entities
-  ([root] (related-entities max-related root))
-  ([n root]
-   (let [recommendations     (-> root :entity related/related)
-         fields-selector     (comp (partial remove key-col?) :fields)
-         ;; Not everything `related/related` returns is relevent for us. Also note that the order
-         ;; influences which entities get shown when results are trimmed.
-         relevant-dimensions [:table :segments :metrics :linking-to :dashboard-mates
-                              :similar-questions :linked-from :tables fields-selector]]
-     (->> relevant-dimensions
-          (reduce (fn [acc selector]
-                    (concat acc (-> recommendations selector rules/ensure-seq)))
-                  [])
-          (take n)
-          (map ->related-entity)))))
+  [root]
+  (-> root
+      :entity
+      related/related
+      (update :fields (partial remove key-col?))
+      (->> (m/map-vals (comp (partial map ->related-entity) rules/ensure-seq)))))
 
 (s/defn ^:private indepth
   [root, rule :- (s/maybe rules/Rule)]
   (->> (rules/get-rules (concat (:rules-prefix root) [(:rule rule)]))
        (keep (fn [indepth]
-               (when-let [[dashboard _] (apply-rule root indepth)]
+               (when-let [[dashboard _ _] (apply-rule root indepth)]
                  {:title       ((some-fn :short-title :title) dashboard)
                   :description (:description dashboard)
                   :url         (format "%s/rule/%s/%s" (:url root) (:rule rule) (:rule indepth))})))
-       (take max-related)))
+       (hash-map :indepth)))
 
 (defn- drilldown-fields
-  [dashboard]
-  (->> dashboard
-       :context
-       :dimensions
-       vals
-       (mapcat :matches)
-       filters/interesting-fields
-       (map ->related-entity)))
+  [context]
+  (when (and (->> context :root :source (instance? (type Table)))
+             (-> context :root :entity ga-table? not))
+    (->> context
+         :dimensions
+         vals
+         (mapcat :matches)
+         filters/interesting-fields
+         (map ->related-entity)
+         (hash-map :drilldown-fields))))
+
+(defn- comparisons
+  [root]
+  {:compare (concat
+             (for [segment (->> root :entity related/related :segments (map ->root))]
+               {:url         (str (:url root) "/compare/segment/" (-> segment :entity u/get-id))
+                :title       (tru "Compare with {0}" (:comparison-name segment))
+                :description ""})
+             (when ((some-fn :query-filter :cell-query) root)
+               [{:url         (if (->> root :source (instance? (type Table)))
+                                (str (:url root) "/compare/table/" (-> root :source u/get-id))
+                                (str (:url root) "/compare/adhoc/"
+                                     (encode-base64-json
+                                      {:database (:database root)
+                                       :type     :query
+                                       :query    {:source-table (->> root
+                                                                     :source
+                                                                     u/get-id
+                                                                     (str "card__" ))}})))
+                 :title       (tru "Compare with entire dataset")
+                 :description ""}]))})
+
+(defn- fill-related
+  "We fill available slots round-robin style. Each selector is a list of fns that are tried against
+   `related` in sequence until one matches."
+  [available-slots selectors related]
+  (let [pop-first         (fn [m ks]
+                            (loop [[k & ks] ks]
+                              (let [item (-> k m first)]
+                                (cond
+                                  item        [item (update m k rest)]
+                                  (empty? ks) [nil m]
+                                  :else       (recur ks)))))
+        count-leafs        (comp count (partial mapcat val))
+        [selected related] (reduce-kv
+                            (fn [[selected related] k v]
+                              (loop [[selector & remaining-selectors] v
+                                     related                          related
+                                     selected                         selected]
+                                (let [[next related] (pop-first related (mapcat shuffle selector))
+                                      num-selected   (count-leafs selected)]
+                                  (cond
+                                    (= num-selected available-slots)
+                                    (reduced [selected related])
+
+                                    next
+                                    (recur remaining-selectors related (update selected k conj next))
+
+                                    (empty? remaining-selectors)
+                                    [selected related]
+
+                                    :else
+                                    (recur remaining-selectors related selected)))))
+                            [{} related]
+                            selectors)
+        num-selected (count-leafs selected)]
+    (if (pos? num-selected)
+      (merge-with concat
+        selected
+        (fill-related (- available-slots num-selected) selectors related))
+      {})))
+
+(def ^:private related-selectors
+  {(type Table)   (let [down     [[:indepth] [:segments :metrics] [:drilldown-fields]]
+                        sideways [[:linking-to :linked-from] [:tables]]
+                        compare  [[:compare]]]
+                    {:zoom-in [down down down down]
+                     :related [sideways sideways]
+                     :compare [compare compare]})
+   (type Segment) (let [down     [[:indepth] [:segments :metrics] [:drilldown-fields]]
+                        sideways [[:linking-to] [:tables]]
+                        up       [[:table]]
+                        compare  [[:compare]]]
+                    {:zoom-in  [down down down]
+                     :zoom-out [up]
+                     :related  [sideways sideways]
+                     :compare  [compare compare]})
+   (type Metric)  (let [down     [[:drilldown-fields]]
+                        sideways [[:metrics :segments]]
+                        up       [[:table]]
+                        compare  [[:compare]]]
+                    {:zoom-in  [down down]
+                     :zoom-out [up]
+                     :related  [sideways sideways sideways]
+                     :compare  [compare compare]})
+   (type Field)   (let [sideways [[:fields]]
+                        up       [[:table] [:metrics :segments]]
+                        compare  [[:compare]]]
+                    {:zoom-out [up]
+                     :related  [sideways sideways]
+                     :compare  [compare]})
+   (type Card)    (let [down     [[:drilldown-fields]]
+                        sideways [[:metrics] [:similar-questions :dashboard-mates]]
+                        up       [[:table]]
+                        compare  [[:compare]]]
+                    {:zoom-in  [down down]
+                     :zoom-out [up]
+                     :related  [sideways sideways sideways]
+                     :compare  [compare compare]})
+   (type Query)   (let [down     [[:drilldown-fields]]
+                        sideways [[:metrics] [:similar-questions]]
+                        up       [[:table]]
+                        compare  [[:compare]]]
+                    {:zoom-in  [down down]
+                     :zoom-out [up]
+                     :related  [sideways sideways sideways]
+                     :compare  [compare compare]})})
 
 (s/defn ^:private related
-  [dashboard, rule :- (s/maybe rules/Rule)]
-  (let [root    (-> dashboard :context :root)
-        indepth (indepth root rule)]
-    (if (not-empty indepth)
-      {:indepth indepth
-       :related (related-entities (- max-related (count indepth)) root)}
-      (let [drilldown-fields   (drilldown-fields dashboard)
-            n-related-entities (max (math/floor (* (/ 2 3) max-related))
-                                    (- max-related (count drilldown-fields)))]
-        {:related          (related-entities n-related-entities root)
-         :drilldown-fields (take (- max-related n-related-entities) drilldown-fields)}))))
+  "Build a balanced list of related X-rays. General composition of the list is determined for each
+   root type individually via `related-selectors`. That recepie is then filled round-robin style."
+  [{:keys [root] :as context}, rule :- (s/maybe rules/Rule)]
+  (->> (merge (indepth root rule)
+              (drilldown-fields context)
+              (related-entities root)
+              (comparisons root))
+       (fill-related max-related (related-selectors (-> root :entity type)))))
+
+(defn- filter-referenced-fields
+  "Return a map of fields referenced in filter cluase."
+  [root filter-clause]
+  (->> filter-clause
+       filters/collect-field-references
+       (mapcat (fn [[_ & ids]]
+                 (for [id ids]
+                   [id (->field root id)])))
+       (remove (comp nil? second))
+       (into {})))
 
 (defn- automagic-dashboard
   "Create dashboards for table `root` using the best matching heuristics."
-  [{:keys [rule show rules-prefix query-filter cell-query full-name] :as root}]
-  (if-let [[dashboard rule] (if rule
-                              (apply-rule root (rules/get-rule rule))
-                              (->> root
-                                   (matching-rules (rules/get-rules rules-prefix))
-                                   (keep (partial apply-rule root))
-                                   ;; `matching-rules` returns an `ArraySeq` (via `sort-by`) so
-                                   ;; `first` realises one element at a time (no chunking).
-                                   first))]
-    (do
+  [{:keys [rule show rules-prefix full-name] :as root}]
+  (if-let [[dashboard rule context] (if rule
+                                      (apply-rule root (rules/get-rule rule))
+                                      (->> root
+                                           (matching-rules (rules/get-rules rules-prefix))
+                                           (keep (partial apply-rule root))
+                                           ;; `matching-rules` returns an `ArraySeq` (via `sort-by`)
+                                           ;; so `first` realises one element at a time
+                                           ;; (no chunking).
+                                           first))]
+    (let [show (or show max-cards)]
       (log/infof (trs "Applying heuristic %s to %s.") (:rule rule) full-name)
       (log/infof (trs "Dimensions bindings:\n%s")
-                 (->> dashboard
-                      :context
+                 (->> context
                       :dimensions
                       (m/map-vals #(update % :matches (partial map :name)))
                       u/pprint-to-str))
       (log/infof (trs "Using definitions:\nMetrics:\n%s\nFilters:\n%s")
-                 (-> dashboard :context :metrics u/pprint-to-str)
-                 (-> dashboard :context :filters u/pprint-to-str))
-      (-> (cond-> dashboard
-            (or query-filter cell-query)
-            (assoc :title (tru "A closer look at {0}" full-name)))
-          (populate/create-dashboard (or show max-cards))
-          (assoc :related (related dashboard rule))
-          (assoc :more (when (and (-> dashboard :cards count (> max-cards))
-                                (not= show :all))
-                         (format "%s#show=all" (:url root))))))
+                 (-> context :metrics u/pprint-to-str)
+                 (-> context :filters u/pprint-to-str))
+      (-> dashboard
+          (populate/create-dashboard show)
+          (assoc :related           (related context rule)
+                 :more              (when (and (not= show :all)
+                                               (-> dashboard :cards count (> show)))
+                                      (format "%s#show=all" (:url root)))
+                 :transient_filters (:query-filter context)
+                 :param_fields      (->> context :query-filter (filter-referenced-fields root)))))
     (throw (ex-info (trs "Can''t create dashboard for {0}" full-name)
              {:root            root
               :available-rules (map :rule (or (some-> rule rules/get-rule vector)
@@ -809,12 +1030,12 @@
                  qp.util/normalize-token
                  (= :metric))
            (-> aggregation-clause second Metric)
-           (let [metric (metric/map->MetricInstance
-                         {:definition {:aggregation  [aggregation-clause]
-                                       :source_table (:table_id question)}
-                          :table_id   (:table_id question)})]
-             (assoc metric :name (metric->description root metric)))))
-       (qp.util/get-in-normalized question [:dataset_query :query :aggregation])))
+           (let [table-id (table-id question)]
+             (metric/map->MetricInstance {:definition {:aggregation  [aggregation-clause]
+                                                       :source-table table-id}
+                                          :name       (metric->description root aggregation-clause)
+                                          :table_id   table-id}))))
+       (get-in question [:dataset_query :query :aggregation])))
 
 (defn- collect-breakout-fields
   [root question]
@@ -822,53 +1043,165 @@
              filters/field-reference->id
              first
              filters/collect-field-references)
-       (qp.util/get-in-normalized question [:dataset_query :query :breakout])))
+       (get-in question [:dataset_query :query :breakout])))
 
 (defn- decompose-question
   [root question opts]
   (map #(automagic-analysis % (assoc opts
-                                :source   (:source root)
-                                :database (:database root)))
+                                :source       (:source root)
+                                :query-filter (:query-filter root)
+                                :database     (:database root)))
        (concat (collect-metrics root question)
                (collect-breakout-fields root question))))
 
+(defn- pluralize
+  [x]
+  (case (mod x 10)
+    1 (tru "{0}st" x)
+    2 (tru "{0}nd" x)
+    3 (tru "{0}rd" x)
+    (tru "{0}th" x)))
+
+(defn- humanize-datetime
+  [dt unit]
+  (let [dt                     (date/str->date-time dt)
+        tz                     (.getID ^TimeZone @date/jvm-timezone)
+        unparse-with-formatter (fn [formatter dt]
+                                 (t.format/unparse
+                                  (t.format/formatter formatter (t/time-zone-for-id tz))
+                                  dt))]
+    (case unit
+      :minute          (tru "at {0}" (unparse-with-formatter "h:mm a, MMMM d, YYYY" dt))
+      :hour            (tru "at {0}" (unparse-with-formatter "h a, MMMM d, YYYY" dt))
+      :day             (tru "on {0}" (unparse-with-formatter "MMMM d, YYYY" dt))
+      :week            (tru "in {0} week - {1}"
+                            (pluralize (date/date-extract :week-of-year dt tz))
+                            (str (date/date-extract :year dt tz)))
+      :month           (tru "in {0}" (unparse-with-formatter "MMMM YYYY" dt))
+      :quarter         (tru "in Q{0} - {1}"
+                            (date/date-extract :quarter-of-year dt tz)
+                            (str (date/date-extract :year dt tz)))
+      :year            (unparse-with-formatter "YYYY" dt)
+      :day-of-week     (unparse-with-formatter "EEEE" dt)
+      :hour-of-day     (tru "at {0}" (unparse-with-formatter "h a" dt))
+      :month-of-year   (unparse-with-formatter "MMMM" dt)
+      :quarter-of-year (tru "Q{0}" (date/date-extract :quarter-of-year dt tz))
+      (:minute-of-hour
+       :day-of-month
+       :day-of-year
+       :week-of-year)  (date/date-extract unit dt tz))))
+
+(defn- field-reference->field
+  [root field-reference]
+  (cond-> (->> field-reference
+               filters/collect-field-references
+               first
+               filters/field-reference->id
+               (->field root))
+    (-> field-reference first qp.util/normalize-token (= :datetime-field))
+    (assoc :unit (-> field-reference last qp.util/normalize-token))))
+
+(defmulti
+  ^{:private true
+    :arglists '([fieldset [op & args]])}
+  humanize-filter-value (fn [_ [op & args]]
+                          (qp.util/normalize-token op)))
+
+(def ^:private unit-name (comp {:minute-of-hour  "minute"
+                                :hour-of-day     "hour"
+                                :day-of-week     "day of week"
+                                :day-of-month    "day of month"
+                                :day-of-year     "day of year"
+                                :week-of-year    "week"
+                                :month-of-year   "month"
+                                :quarter-of-year "quarter"}
+                               qp.util/normalize-token))
+
+(defn- field-name
+  ([root field-reference]
+   (->> field-reference (field-reference->field root) field-name))
+  ([{:keys [display_name unit] :as field}]
+   (cond->> display_name
+     (and (filters/periodic-datetime? field) unit) (format "%s of %s" (unit-name unit)))))
+
+(defmethod humanize-filter-value :=
+  [root [_ field-reference value]]
+  (let [field      (field-reference->field root field-reference)
+        field-name (field-name field)]
+    (if (or (filters/datetime? field)
+            (filters/periodic-datetime? field))
+      (tru "{0} is {1}" field-name (humanize-datetime value (:unit field)))
+      (tru "{0} is {1}" field-name value))))
+
+(defmethod humanize-filter-value :between
+  [root [_ field-reference min-value max-value]]
+  (tru "{0} is between {1} and {2}" (field-name root field-reference) min-value max-value))
+
+(defmethod humanize-filter-value :inside
+  [root [_ lat-reference lon-reference lat-max lon-min lat-min lon-max]]
+  (tru "{0} is between {1} and {2}; and {3} is between {4} and {5}"
+       (field-name root lon-reference) lon-min lon-max
+       (field-name root lat-reference) lat-min lat-max))
+
+(defmethod humanize-filter-value :and
+  [root [_ & clauses]]
+  (->> clauses
+       (map (partial humanize-filter-value root))
+       join-enumeration))
+
+(defn cell-title
+  "Return a cell title given a root object and a cell query."
+  [root cell-query]
+  (str/join " " [(if-let [aggregation (get-in root [:entity :dataset_query :query :aggregation])]
+                   (metric->description root aggregation)
+                   (:full-name root))
+                 (tru "where {0}" (humanize-filter-value root cell-query))]))
+
 (defmethod automagic-analysis (type Card)
   [card {:keys [cell-query] :as opts}]
-  (let [root (->root card)]
-    (if (or (table-like? card)
-            cell-query)
+  (let [root     (->root card)
+        cell-url (format "%squestion/%s/cell/%s" public-endpoint
+                         (u/get-id card)
+                         (encode-base64-json cell-query))]
+    (if (table-like? card)
       (automagic-dashboard
        (merge (cond-> root
-                cell-query (merge {:url          (format "%squestion/%s/cell/%s" public-endpoint
-                                                         (u/get-id card)
-                                                         (encode-base64-json cell-query))
+                cell-query (merge {:url          cell-url
                                    :entity       (:source root)
                                    :rules-prefix ["table"]}))
               opts))
       (let [opts (assoc opts :show :all)]
-        (->> (decompose-question root card opts)
-             (apply populate/merge-dashboards (automagic-dashboard root))
-             (merge {:related (related {:context {:root {:entity card}}} nil)}))))))
+        (cond-> (reduce populate/merge-dashboards
+                        (automagic-dashboard (merge (cond-> root
+                                                      cell-query (assoc :url cell-url))
+                                                    opts))
+                        (decompose-question root card opts))
+          cell-query (merge (let [title (tru "A closer look at {0}" (cell-title root cell-query))]
+                              {:transient_name  title
+                               :name            title})))))))
 
 (defmethod automagic-analysis (type Query)
   [query {:keys [cell-query] :as opts}]
-  (let [root (->root query)]
-    (if (or (table-like? query)
-            (:cell-query opts))
+  (let [root     (->root query)
+        cell-url (format "%sadhoc/%s/cell/%s" public-endpoint
+                         (encode-base64-json (:dataset_query query))
+                         (encode-base64-json cell-query))]
+    (if (table-like? query)
       (automagic-dashboard
        (merge (cond-> root
-                cell-query (merge {:url          (format "%sadhoc/%s/cell/%s" public-endpoint
-                                                         (encode-base64-json (:dataset_query query))
-                                                         (encode-base64-json cell-query))
+                cell-query (merge {:url          cell-url
                                    :entity       (:source root)
                                    :rules-prefix ["table"]}))
-              (update opts :cell-query
-                      (partial filters/inject-refinement
-                               (qp.util/get-in-normalized query [:dataset_query :query :filter])))))
+              opts))
       (let [opts (assoc opts :show :all)]
-        (->> (decompose-question root query opts)
-             (apply populate/merge-dashboards (automagic-dashboard root))
-             (merge {:related (related {:context {:root {:entity query}}} nil)}))))))
+        (cond-> (reduce populate/merge-dashboards
+                        (automagic-dashboard (merge (cond-> root
+                                                      cell-query (assoc :url cell-url))
+                                                    opts))
+                       (decompose-question root query opts))
+          cell-query (merge (let [title (tru "A closer look at the {0}" (cell-title root cell-query))]
+                              {:transient_name  title
+                               :name            title})))))))
 
 (defmethod automagic-analysis (type Field)
   [field opts]
@@ -879,10 +1212,10 @@
   (when (not-empty tables)
     (let [field-count (->> (db/query {:select   [:table_id [:%count.* "count"]]
                                       :from     [Field]
-                                      :where    [:in :table_id (map u/get-id tables)]
+                                      :where    [:and [:in :table_id (map u/get-id tables)]
+                                                 [:= :active true]]
                                       :group-by [:table_id]})
-                           (into {} (map (fn [{:keys [count table_id]}]
-                                           [table_id count]))))
+                           (into {} (map (juxt :table_id :count))))
           list-like?  (->> (when-let [candidates (->> field-count
                                                       (filter (comp (partial >= 2) val))
                                                       (map key)
@@ -890,6 +1223,7 @@
                              (db/query {:select   [:table_id]
                                         :from     [Field]
                                         :where    [:and [:in :table_id candidates]
+                                                   [:= :active true]
                                                    [:or [:not= :special_type "type/PK"]
                                                     [:= :special_type nil]]]
                                         :group-by [:table_id]
@@ -898,6 +1232,7 @@
           link-table? (->> (db/query {:select   [:table_id [:%count.* "count"]]
                                       :from     [Field]
                                       :where    [:and [:in :table_id (keys field-count)]
+                                                 [:= :active true]
                                                  [:in :special_type ["type/PK" "type/FK"]]]
                                       :group-by [:table_id]})
                            (filter (fn [{:keys [table_id count]}]
@@ -928,7 +1263,8 @@
    (let [rules (rules/get-rules ["table"])]
      (->> (apply db/select [Table :id :schema :display_name :entity_type :db_id]
                  (cond-> [:db_id           (u/get-id database)
-                          :visibility_type nil]
+                          :visibility_type nil
+                          :active          true]
                    schema (concat [:schema schema])))
           (filter mi/can-read?)
           enhance-table-stats
diff --git a/src/metabase/automagic_dashboards/filters.clj b/src/metabase/automagic_dashboards/filters.clj
index d6d7c5b8c7e63ca1e0765a8e163006720c68e912..f63e5f85316c43ddc26db1c76b205c665f28141d 100644
--- a/src/metabase/automagic_dashboards/filters.clj
+++ b/src/metabase/automagic_dashboards/filters.clj
@@ -1,17 +1,18 @@
 (ns metabase.automagic-dashboards.filters
-  (:require [metabase.models.field :refer [Field] :as field]
-            [metabase.query-processor.middleware.expand-macros :refer [merge-filter-clauses]]
+  (:require [metabase.models.field :as field :refer [Field]]
+            [metabase.mbql.normalize :as normalize]
             [metabase.query-processor.util :as qp.util]
             [metabase.util :as u]
             [metabase.util.schema :as su]
             [schema.core :as s]
-            [toucan.db :as db]))
+            [toucan.db :as db]
+            [metabase.mbql.util :as mbql.u]))
 
 (def ^:private FieldReference
   [(s/one (s/constrained su/KeywordOrString
                          (comp #{:field-id :fk-> :field-literal} qp.util/normalize-token))
           "head")
-   s/Any])
+   (s/cond-pre s/Int su/KeywordOrString)])
 
 (def ^:private ^{:arglists '([form])} field-reference?
   "Is given form an MBQL field reference?"
@@ -25,7 +26,7 @@
 (defmethod field-reference->id :field-id
   [[_ id]]
   (if (sequential? id)
-    (second id)
+    (field-reference->id id)
     id))
 
 (defmethod field-reference->id :fk->
@@ -44,12 +45,14 @@
        (tree-seq (some-fn sequential? map?) identity)
        (filter field-reference?)))
 
-(def ^:private ^{:arglists '([field])} periodic-datetime?
+(def ^{:arglists '([field])} periodic-datetime?
+  "Is `field` a periodic datetime (eg. day of month)?"
   (comp #{:minute-of-hour :hour-of-day :day-of-week :day-of-month :day-of-year :week-of-year
           :month-of-year :quarter-of-year}
         :unit))
 
-(defn- datetime?
+(defn datetime?
+  "Is `field` a datetime?"
   [field]
   (and (not (periodic-datetime? field))
        (or (isa? (:base_type field) :type/DateTime)
@@ -154,12 +157,12 @@
           remove-unqualified
           (sort-by interestingness >)
           (take max-filters)
-          (map #(assoc % :fk-map (build-fk-map fks %)))
           (reduce
            (fn [dashboard candidate]
-             (let [filter-id     (-> candidate hash str)
+             (let [filter-id     (-> candidate ((juxt :id :name :unit)) hash str)
+                   candidate     (assoc candidate :fk-map (build-fk-map fks candidate))
                    dashcards     (:ordered_cards dashboard)
-                   dashcards-new (map #(add-filter % filter-id candidate) dashcards)]
+                   dashcards-new (keep #(add-filter % filter-id candidate) dashcards)]
                ;; Only add filters that apply to all cards.
                (if (= (count dashcards) (count dashcards-new))
                  (-> dashboard
@@ -172,40 +175,43 @@
            dashboard)))))
 
 
-(defn filter-referenced-fields
-  "Return a map of fields referenced in filter cluase."
-  [filter-clause]
-  (->> filter-clause
-       collect-field-references
-       (mapcat (fn [[_ & ids]]
-                 (for [id ids]
-                   [id (Field id)])))
-       (into {})))
-
-
 (defn- flatten-filter-clause
-  [filter-clause]
-  (when (not-empty filter-clause)
-    (if (-> filter-clause first qp.util/normalize-token (= :and))
-      (mapcat flatten-filter-clause (rest filter-clause))
+  "Returns a sequence of filter subclauses making up `filter-clause` by flattening `:and` compound filters.
+
+    (flatten-filter-clause [:and
+                            [:= [:field-id 1] 2]
+                            [:and
+                             [:= [:field-id 3] 4]
+                             [:= [:field-id 5] 6]]])
+    ;; -> ([:= [:field-id 1] 2]
+           [:= [:field-id 3] 4]
+           [:= [:field-id 5] 6])"
+  [[clause-name, :as filter-clause]]
+  (when (seq filter-clause)
+    (if (= clause-name :and)
+      (rest (mbql.u/simplify-compound-filter filter-clause))
       [filter-clause])))
 
 (defn inject-refinement
-  "Inject a filter refinement into an MBQL filter clause.
-   There are two reasons why we want to do this: 1) to reduce visual noise when we display applied
-   filters; and 2) some DBs don't do this optimization or even protest (eg. GA) if there are
-   duplicate clauses.
-
-   Assumes that any refinement sub-clauses referencing fields that are also referenced in the
-   main clause are subsets of the latter. Therefore we can rewrite the combined clause to ommit
-   the more broad version from the main clause.
-   Assumes both filter clauses can be flattened by recursively merging `:and` claueses
-   (ie. no `:and`s inside `:or` or `:not`)."
+  "Inject a filter refinement into an MBQL filter clause, returning a new filter clause.
+
+  There are two reasons why we want to do this: 1) to reduce visual noise when we display applied filters; and 2) some
+  DBs don't do this optimization or even protest (eg. GA) if there are duplicate clauses.
+
+  Assumes that any refinement sub-clauses referencing fields that are also referenced in the main clause are subsets
+  of the latter. Therefore we can rewrite the combined clause to ommit the more broad version from the main clause.
+  Assumes both filter clauses can be flattened by recursively merging `:and` claueses
+  (ie. no `:and`s inside `:or` or `:not`)."
   [filter-clause refinement]
-  (let [in-refinement? (into #{}
-                         (map collect-field-references)
-                         (flatten-filter-clause refinement))]
-    (->> filter-clause
-         flatten-filter-clause
-         (remove (comp in-refinement? collect-field-references))
-         (reduce merge-filter-clauses refinement))))
+  (let [in-refinement?  (into #{}
+                              (map collect-field-references)
+                              (flatten-filter-clause refinement))
+        existing-filters (->> filter-clause
+                             flatten-filter-clause
+                             (remove (comp in-refinement? collect-field-references)))]
+    (if (seq existing-filters)
+      ;; since the filters are programatically generated they won't have passed thru normalization, so make sure we
+      ;; normalize them before passing them to `combine-filter-clauses`, which validates its input
+      (apply mbql.u/combine-filter-clauses (map (partial normalize/normalize-fragment [:query :filter])
+                                                (cons refinement existing-filters)))
+      refinement)))
diff --git a/src/metabase/automagic_dashboards/populate.clj b/src/metabase/automagic_dashboards/populate.clj
index fb1c1c048d320d38acf44a7f2a4795bce621e52a..b5930f17aed95e2641b515b15daf5921e361af6d 100644
--- a/src/metabase/automagic_dashboards/populate.clj
+++ b/src/metabase/automagic_dashboards/populate.clj
@@ -6,9 +6,10 @@
             [metabase.automagic-dashboards.filters :as filters]
             [metabase.models
              [card :as card]
-             [field :refer [Field]]]
+             [collection :as collection]]
             [metabase.query-processor.util :as qp.util]
-            [puppetlabs.i18n.core :as i18n :refer [trs]]
+            [metabase.util :as u]
+            [metabase.util.i18n :refer [trs]]
             [toucan.db :as db]))
 
 (def ^Long grid-width
@@ -25,12 +26,23 @@
 
 (defn create-collection!
   "Create a new collection."
-  [title color description]
-  (when api/*is-superuser?*
-    (db/insert! 'Collection
-      :name        title
+  [title color description parent-collection-id]
+  (db/insert! 'Collection
+    (merge
+     {:name        title
       :color       color
-      :description description)))
+      :description description}
+     (when parent-collection-id
+       {:location (collection/children-location (db/select-one ['Collection :location :id]
+                                                  :id parent-collection-id))}))))
+
+(defn get-or-create-root-container-collection
+  "Get or create container collection for automagic dashboards in the root collection."
+  []
+  (or (db/select-one 'Collection
+        :name     "Automatically Generated Dashboards"
+        :location "/")
+      (create-collection! "Automatically Generated Dashboards" "#509EE3" nil nil)))
 
 (def colors
   "Colors used for coloring charts and collections."
@@ -47,6 +59,13 @@
             (concat acc [color (first (drop-while (conj (set acc) color) colors))])))
         [])))
 
+(defn map-to-colors
+  "Map given objects to distinct colors."
+  [objs]
+  (->> objs
+       (map (comp colors #(mod % (count colors)) hash))
+       ensure-distinct-colors))
+
 (defn- colorize
   "Pick the chart colors acording to the following rules:
   * If there is more than one breakout dimension let the frontend do it as presumably
@@ -74,23 +93,23 @@
                               filters/collect-field-references
                               (map filters/field-reference->id))
                          aggregation)]
-        {:graph.colors (->> color-keys
-                            (map (comp colors #(mod % (count colors)) hash))
-                            ensure-distinct-colors)}))))
+        {:graph.colors (map-to-colors color-keys)}))))
 
 (defn- visualization-settings
   [{:keys [metrics x_label y_label series_labels visualization dimensions] :as card}]
-  (let [metric-name (some-fn :name (comp str/capitalize name first :metric))
-        [display visualization-settings] visualization]
+  (let [[display visualization-settings] visualization]
     {:display display
-     :visualization_settings
-     (-> visualization-settings
-         (merge (colorize card))
-         (cond->
-           (some :name metrics) (assoc :graph.series_labels (map metric-name metrics))
-           series_labels        (assoc :graph.series_labels series_labels)
-           x_label              (assoc :graph.x_axis.title_text x_label)
-           y_label              (assoc :graph.y_axis.title_text y_label)))}))
+     :visualization_settings (-> visualization-settings
+                                 (assoc :graph.series_labels (map :name metrics)
+                                        :graph.metrics       (map :op metrics)
+                                        :graph.dimensions    dimensions)
+                                 (merge (colorize card))
+                                 (cond->
+                                     series_labels (assoc :graph.series_labels series_labels)
+
+                                     x_label       (assoc :graph.x_axis.title_text x_label)
+
+                                     y_label       (assoc :graph.y_axis.title_text y_label)))}))
 
 (defn- add-card
   "Add a card to dashboard `dashboard` at position [`x`, `y`]."
@@ -236,15 +255,13 @@
 (defn create-dashboard
   "Create dashboard and populate it with cards."
   ([dashboard] (create-dashboard dashboard :all))
-  ([{:keys [title transient_title description groups filters cards refinements fieldset]} n]
+  ([{:keys [title transient_title description groups filters cards refinements]} n]
    (let [n             (cond
                          (= n :all)   (count cards)
                          (keyword? n) (Integer/parseInt (name n))
                          :else        n)
          dashboard     {:name              title
                         :transient_name    (or transient_title title)
-                        :transient_filters refinements
-                        :param_fields      (filters/filter-referenced-fields refinements)
                         :description       description
                         :creator_id        api/*current-user-id*
                         :parameters        []}
@@ -258,50 +275,69 @@
                                      ;; Height doesn't need to be precise, just some
                                      ;; safe upper bound.
                                      (make-grid grid-width (* n grid-width))]))]
-     (log/infof (trs "Adding %s cards to dashboard %s:\n%s")
-                (count cards)
-                title
-                (str/join "; " (map :title cards)))
+     (log/info (trs "Adding {0} cards to dashboard {1}:\n{2}"
+                    (count cards)
+                    title
+                    (str/join "; " (map :title cards))))
      (cond-> dashboard
        (not-empty filters) (filters/add-filters filters max-filters)))))
 
+(defn- downsize-titles
+  [markdown]
+  (->> markdown
+       str/split-lines
+       (map (fn [line]
+              (if (str/starts-with? line "#")
+                (str "#" line)
+                line)))
+       str/join))
+
+(defn- merge-filters
+  [ds]
+  (when (->> ds
+             (mapcat :ordered_cards)
+             (keep (comp :table_id :card))
+             distinct
+             count
+             (= 1))
+   [(->> ds (mapcat :parameters) distinct)
+    (->> ds
+         (mapcat :ordered_cards)
+         (mapcat :parameter_mappings)
+         (map #(dissoc % :card_id))
+         distinct)]))
+
 (defn merge-dashboards
-  "Merge dashboards `ds` into dashboard `d`."
-  [d & ds]
-  (let [filter-targets (when (->> ds
-                                  (mapcat :ordered_cards)
-                                  (keep (comp :table_id :card))
-                                  distinct
-                                  count
-                                  (= 1))
-                         (->> ds
-                              (mapcat :ordered_cards)
-                              (mapcat :parameter_mappings)
-                              (mapcat (comp filters/collect-field-references :target))
-                              (map filters/field-reference->id)
-                              distinct
-                              (map Field)))]
-    (cond-> (reduce
-             (fn [target dashboard]
-               (let [offset (->> target
-                                 :ordered_cards
-                                 (map #(+ (:row %) (:sizeY %)))
-                                 (apply max -1) ; -1 so it neturalizes +1 for spacing if
-                                                ; the target dashboard is empty.
-                                 inc)]
-                 (-> target
-                     (add-text-card {:width                  grid-width
-                                     :height                 group-heading-height
-                                     :text                   (format "# %s" (:name dashboard))
-                                     :visualization-settings {:dashcard.background false
-                                                              :text.align_vertical :bottom}}
-                                    [offset 0])
-                     (update :ordered_cards concat
-                             (->> dashboard
-                                  :ordered_cards
-                                  (map #(-> %
-                                            (update :row + offset group-heading-height)
-                                            (dissoc :parameter_mappings))))))))
-             d
-             ds)
-      (not-empty filter-targets) (filters/add-filters filter-targets max-filters))))
+  "Merge dashboards `dashboard` into dashboard `target`."
+  ([target dashboard] (merge-dashboards target dashboard {}))
+  ([target dashboard {:keys [skip-titles?]}]
+   (let [[paramters parameter-mappings] (merge-filters [target dashboard])
+         offset                         (->> target
+                                             :ordered_cards
+                                             (map #(+ (:row %) (:sizeY %)))
+                                             (apply max -1) ; -1 so it neturalizes +1 for spacing
+                                                            ; if the target dashboard is empty.
+                                             inc)
+         cards                        (->> dashboard
+                                           :ordered_cards
+                                           (map #(-> %
+                                                     (update :row + offset (if skip-titles?
+                                                                             0
+                                                                             group-heading-height))
+                                                     (u/update-in-when [:visualization_settings :text]
+                                                                       downsize-titles)
+                                                     (assoc :parameter_mappings
+                                                       (when-let [card-id (:card_id %)]
+                                                         (for [mapping parameter-mappings]
+                                                           (assoc mapping :card_id card-id)))))))]
+     (-> target
+         (assoc :parameters paramters)
+         (cond->
+           (not skip-titles?)
+           (add-text-card {:width                  grid-width
+                           :height                 group-heading-height
+                           :text                   (format "# %s" (:name dashboard))
+                           :visualization-settings {:dashcard.background false
+                                                    :text.align_vertical :bottom}}
+                          [offset 0]))
+         (update :ordered_cards concat cards)))))
diff --git a/src/metabase/automagic_dashboards/rules.clj b/src/metabase/automagic_dashboards/rules.clj
index 50bad7029869ed93be85428e75301a0876ac2905..22b7d3987fc59f598e5d4ee830b731c8edf9f705 100644
--- a/src/metabase/automagic_dashboards/rules.clj
+++ b/src/metabase/automagic_dashboards/rules.clj
@@ -4,9 +4,11 @@
             [clojure.string :as str]
             [clojure.tools.logging :as log]
             [metabase.automagic-dashboards.populate :as populate]
+            [metabase.query-processor.util :as qp.util]
             [metabase.util :as u]
-            [metabase.util.schema :as su]
-            [puppetlabs.i18n.core :as i18n :refer [trs]]
+            [metabase.util
+             [i18n :refer [trs]]
+             [schema :as su]]
             [schema
              [coerce :as sc]
              [core :as s]]
@@ -104,8 +106,9 @@
                (s/optional-key :series_labels) [s/Str]}})
 
 (def ^:private Groups
-  {Identifier {(s/required-key :title)       s/Str
-               (s/optional-key :description) s/Str}})
+  {Identifier {(s/required-key :title)            s/Str
+               (s/optional-key :comparison_title) s/Str
+               (s/optional-key :description)      s/Str}})
 
 (def ^{:arglists '([definition])} identifier
   "Return `key` in `{key {}}`."
@@ -119,8 +122,7 @@
   (mapcat (comp k val first) cards))
 
 (def ^:private DimensionForm
-  [(s/one (s/constrained (s/cond-pre s/Str s/Keyword)
-                         (comp #{"dimension"} str/lower-case name))
+  [(s/one (s/constrained (s/cond-pre s/Str s/Keyword) (comp #{:dimension} qp.util/normalize-token))
           "dimension")
    (s/one s/Str "identifier")
    su/Map])
@@ -297,13 +299,13 @@
           rules-validator
           (as-> rule (assoc rule :specificity (specificity rule)))))
     (catch Exception e
-      (log/errorf (trs "Error parsing %s:\n%s")
-                  (.getFileName f)
-                  (or (some-> e
-                              ex-data
-                              (select-keys [:error :value])
-                              u/pprint-to-str)
-                      e))
+      (log/error (trs "Error parsing {0}:\n{1}"
+                      (.getFileName f)
+                      (or (some-> e
+                                  ex-data
+                                  (select-keys [:error :value])
+                                  u/pprint-to-str)
+                          e)))
       nil)))
 
 (defn- trim-trailing-slash
diff --git a/src/metabase/cmd/load_from_h2.clj b/src/metabase/cmd/load_from_h2.clj
index 01929766df171754eee25e32465ce22541b37747..6dd10f2571384d1577f7a3d2eb1d8cbe882accee 100644
--- a/src/metabase/cmd/load_from_h2.clj
+++ b/src/metabase/cmd/load_from_h2.clj
@@ -52,6 +52,7 @@
              [session :refer [Session]]
              [setting :refer [Setting]]
              [table :refer [Table]]
+             [task-history :refer [TaskHistory]]
              [user :refer [User]]
              [view-log :refer [ViewLog]]]))
 
@@ -93,6 +94,7 @@
    CollectionRevision
    DashboardFavorite
    Dimension
+   TaskHistory
    ;; migrate the list of finished DataMigrations as the very last thing (all models to copy over should be listed
    ;; above this line)
    DataMigrations])
diff --git a/src/metabase/cmd/reset_password.clj b/src/metabase/cmd/reset_password.clj
index 944a49c0da62406c15d98e74950799919a8d80d4..ce912ecc8fe3bad72903833f37523f71da8c9329 100644
--- a/src/metabase/cmd/reset_password.clj
+++ b/src/metabase/cmd/reset_password.clj
@@ -1,7 +1,7 @@
 (ns metabase.cmd.reset-password
   (:require [metabase.db :as mdb]
             [metabase.models.user :refer [User] :as user]
-            [puppetlabs.i18n.core :refer [trs]]
+            [metabase.util.i18n :refer [trs]]
             [toucan.db :as db]))
 
 (defn- set-reset-token!
diff --git a/src/metabase/config.clj b/src/metabase/config.clj
index 43f33a44f50e842f35d717639f9050a3d9d6334c..1aedaa4879b727d363b93fc88424cb6e18227460 100644
--- a/src/metabase/config.clj
+++ b/src/metabase/config.clj
@@ -61,7 +61,10 @@
 (defn- version-info-from-shell-script []
   (try
     (let [[tag hash branch date] (-> (shell/sh "./bin/version") :out s/trim (s/split #" "))]
-      {:tag tag, :hash hash, :branch branch, :date date})
+      {:tag    (or tag "?")
+       :hash   (or hash "?")
+       :branch (or branch "?")
+       :date   (or date "?")})
     ;; if ./bin/version fails (e.g., if we are developing on Windows) just return something so the whole thing doesn't barf
     (catch Throwable _
       {:tag "?", :hash "?", :branch "?", :date "?"})))
diff --git a/src/metabase/core.clj b/src/metabase/core.clj
index dcaa9cc47ed5c1be55f2e14b851c5acc69974ba2..f2fc15fb4cb53c354f330eb1197fc5a732a951be 100644
--- a/src/metabase/core.clj
+++ b/src/metabase/core.clj
@@ -22,8 +22,8 @@
             [metabase.models
              [setting :as setting]
              [user :refer [User]]]
-            [metabase.util.i18n :refer [set-locale]]
-            [puppetlabs.i18n.core :refer [locale-negotiator trs]]
+            [metabase.util.i18n :refer [set-locale trs]]
+            [puppetlabs.i18n.core :refer [locale-negotiator]]
             [ring.adapter.jetty :as ring-jetty]
             [ring.middleware
              [cookies :refer [wrap-cookies]]
diff --git a/src/metabase/db.clj b/src/metabase/db.clj
index 10cfd8cbf70ab11493b8e4a28d472b250e8173db..8769090962cc2d9b5e7f73dab8b417a542e6e98c 100644
--- a/src/metabase/db.clj
+++ b/src/metabase/db.clj
@@ -1,5 +1,5 @@
 (ns metabase.db
-  "Database definition and helper functions for interacting with the database."
+  "Application database definition, and setup logic, and helper functions for interacting with it."
   (:require [clojure
              [string :as s]
              [walk :as walk]]
@@ -11,6 +11,7 @@
              [config :as config]
              [util :as u]]
             [metabase.db.spec :as dbspec]
+            [metabase.util.i18n :refer [trs]]
             [ring.util.codec :as codec]
             [toucan.db :as db])
   (:import com.mchange.v2.c3p0.ComboPooledDataSource
@@ -19,6 +20,7 @@
            [liquibase.database Database DatabaseFactory]
            liquibase.database.jvm.JdbcConnection
            liquibase.Liquibase
+           liquibase.exception.LockException
            liquibase.resource.ClassLoaderResourceAccessor))
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
@@ -34,7 +36,10 @@
            ;; File-based DB
            (let [db-file-name (config/config-str :mb-db-file)
                  db-file      (io/file db-file-name)
-                 options      ";DB_CLOSE_DELAY=-1"]
+                 ;; we need to enable MVCC for Quartz JDBC backend to work! Quartz depends on row-level locking, which
+                 ;; means without MVCC we "will experience dead-locks". MVCC is the default for everyone using the
+                 ;; MVStore engine anyway so this only affects people still with legacy PageStore databases
+                 options      ";DB_CLOSE_DELAY=-1;MVCC=TRUE;"]
              (apply str "file:" (if (.isAbsolute db-file)
                                   ;; when an absolute path is given for the db file then don't mess with it
                                   [db-file-name options]
@@ -42,8 +47,9 @@
                                   [(System/getProperty "user.dir") "/" db-file-name options]))))))
 
 (defn- parse-connection-string
-  "Parse a DB connection URI like `postgres://cam@localhost.com:5432/cams_cool_db?ssl=true&sslfactory=org.postgresql.ssl.NonValidatingFactory`
-  and return a broken-out map."
+  "Parse a DB connection URI like
+  `postgres://cam@localhost.com:5432/cams_cool_db?ssl=true&sslfactory=org.postgresql.ssl.NonValidatingFactory` and
+  return a broken-out map."
   [uri]
   (when-let [[_ protocol user pass host port db query] (re-matches #"^([^:/@]+)://(?:([^:/@]+)(?::([^:@]+))?@)?([^:@]+)(?::(\d+))?/([^/?]+)(?:\?(.*))?$" uri)]
     (merge {:type     (case (keyword protocol)
@@ -70,8 +76,8 @@
       (config/config-kw :mb-db-type)))
 
 (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)."
+  "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 (or @connection-string-details
              (case (db-type)
                :h2       {:type     :h2                               ; TODO - we probably don't need to specifc `:type` here since we can just call (db-type)
@@ -109,19 +115,19 @@
 (def ^:private ^:const ^String changelog-file "liquibase.yaml")
 
 (defn- migrations-sql
-  "Return a string of SQL containing the DDL statements needed to perform unrun LIQUIBASE migrations."
+  "Return a string of SQL containing the DDL statements needed to perform unrun `liquibase` migrations."
   ^String [^Liquibase liquibase]
   (let [writer (StringWriter.)]
     (.update liquibase "" writer)
     (.toString writer)))
 
 (defn- migrations-lines
-  "Return a sequnce of DDL statements that should be used to perform migrations for LIQUIBASE.
+  "Return a sequnce of DDL statements that should be used to perform migrations for `liquibase`.
 
-   MySQL gets snippy if we try to run the entire DB migration as one single string; it seems to only like it if we run
-   one statement at a time; Liquibase puts each DDL statement on its own line automatically so just split by lines and
-   filter out blank / comment lines. Even though this is not neccesary for H2 or Postgres go ahead and do it anyway
-   because it keeps the code simple and doesn't make a significant performance difference."
+  MySQL gets snippy if we try to run the entire DB migration as one single string; it seems to only like it if we run
+  one statement at a time; Liquibase puts each DDL statement on its own line automatically so just split by lines and
+  filter out blank / comment lines. Even though this is not neccesary for H2 or Postgres go ahead and do it anyway
+  because it keeps the code simple and doesn't make a significant performance difference."
   [^Liquibase liquibase]
   (for [line  (s/split-lines (migrations-sql liquibase))
         :when (not (or (s/blank? line)
@@ -129,57 +135,67 @@
     line))
 
 (defn- has-unrun-migrations?
-  "Does LIQUIBASE have migration change sets that haven't been run yet?
+  "Does `liquibase` have migration change sets that haven't been run yet?
 
-   It's a good idea to Check to make sure there's actually something to do before running `(migrate :up)` because
-   `migrations-sql` will always contain SQL to create and release migration locks, which is both slightly dangerous
-   and a waste of time when we won't be using them."
+  It's a good idea to Check to make sure there's actually something to do before running `(migrate :up)` because
+  `migrations-sql` will always contain SQL to create and release migration locks, which is both slightly dangerous and
+  a waste of time when we won't be using them."
   ^Boolean [^Liquibase liquibase]
   (boolean (seq (.listUnrunChangeSets liquibase nil))))
 
-(defn- has-migration-lock?
+(defn- migration-lock-exists?
   "Is a migration lock in place for LIQUIBASE?"
   ^Boolean [^Liquibase liquibase]
   (boolean (seq (.listLocks liquibase))))
 
 (defn- wait-for-migration-lock-to-be-cleared
-  "Check and make sure the database isn't locked. If it is, sleep for 2 seconds and then retry several times.
-   There's a chance the lock will end up clearing up so we can run migrations normally."
+  "Check and make sure the database isn't locked. If it is, sleep for 2 seconds and then retry several times. There's a
+  chance the lock will end up clearing up so we can run migrations normally."
   [^Liquibase liquibase]
   (u/auto-retry 5
-    (when (has-migration-lock? liquibase)
+    (when (migration-lock-exists? liquibase)
       (Thread/sleep 2000)
-      (throw (Exception. (str "Database has migration lock; cannot run migrations. You can force-release these locks "
-                              "by running `java -jar metabase.jar migrate release-locks`."))))))
+      (throw
+       (LockException.
+        (str
+         (trs "Database has migration lock; cannot run migrations.")
+         " "
+         (trs "You can force-release these locks by running `java -jar metabase.jar migrate release-locks`.")))))))
 
 (defn- migrate-up-if-needed!
-  "Run any unrun LIQUIBASE migrations, if needed.
+  "Run any unrun `liquibase` migrations, if needed.
 
-   This creates SQL for the migrations to be performed, then executes each DDL statement.
-   Running `.update` directly doesn't seem to work as we'd expect; it ends up commiting the changes made and they
-   can't be rolled back at the end of the transaction block. Converting the migration to SQL string and running that
-   via `jdbc/execute!` seems to do the trick."
+  This creates SQL for the migrations to be performed, then executes each DDL statement. Running `.update` directly
+  doesn't seem to work as we'd expect; it ends up commiting the changes made and they can't be rolled back at the end
+  of the transaction block. Converting the migration to SQL string and running that via `jdbc/execute!` seems to do
+  the trick."
   [conn, ^Liquibase liquibase]
-  (log/info "Checking if Database has unrun migrations...")
+  (log/info (trs "Checking if Database has unrun migrations..."))
   (when (has-unrun-migrations? liquibase)
-    (log/info "Database has unrun migrations. Waiting for migration lock to be cleared...")
+    (log/info (trs "Database has unrun migrations. Waiting for migration lock to be cleared..."))
     (wait-for-migration-lock-to-be-cleared liquibase)
-    (log/info "Migration lock is cleared. Running migrations...")
-    (doseq [line (migrations-lines liquibase)]
-      (jdbc/execute! conn [line]))))
+    ;; while we were waiting for the lock, it was possible that another instance finished the migration(s), so make
+    ;; sure something still needs to be done...
+    (if (has-unrun-migrations? liquibase)
+      (do
+        (log/info (trs "Migration lock is cleared. Running migrations..."))
+        (doseq [line (migrations-lines liquibase)]
+          (jdbc/execute! conn [line])))
+      (log/info
+       (trs "Migration lock cleared, but nothing to do here! Migrations were finished by another instance.")))))
 
 (defn- force-migrate-up-if-needed!
   "Force migrating up. This does two things differently from `migrate-up-if-needed!`:
 
-   1.  This doesn't check to make sure the DB locks are cleared
-   2.  Any DDL statements that fail are ignored
+  1.  This doesn't check to make sure the DB locks are cleared
+  2.  Any DDL statements that fail are ignored
 
-   It can be used to fix situations where the database got into a weird state, as was common before the fixes made in
-   #3295.
+  It can be used to fix situations where the database got into a weird state, as was common before the fixes made in
+  #3295.
 
-   Each DDL statement is ran inside a nested transaction; that way if the nested transaction fails we can roll it back
-   without rolling back the entirety of changes that were made. (If a single statement in a transaction fails you
-   can't do anything futher until you clear the error state by doing something like calling `.rollback`.)"
+  Each DDL statement is ran inside a nested transaction; that way if the nested transaction fails we can roll it back
+  without rolling back the entirety of changes that were made. (If a single statement in a transaction fails you can't
+  do anything futher until you clear the error state by doing something like calling `.rollback`.)"
   [conn, ^Liquibase liquibase]
   (.clearCheckSums liquibase)
   (when (has-unrun-migrations? liquibase)
@@ -194,7 +210,7 @@
 
 (def ^{:arglists '([])} ^DatabaseFactory database-factory
   "Return an instance of the Liquibase `DatabaseFactory`. This is done on a background thread at launch because
-   otherwise it adds 2 seconds to startup time."
+  otherwise it adds 2 seconds to startup time."
   (partial deref (future (DatabaseFactory/getInstance))))
 
 (defn- conn->liquibase
@@ -219,11 +235,22 @@
                                 "DATABASECHANGELOG"
                                 "databasechangelog")
         fresh-install? (jdbc/with-db-metadata [meta (jdbc-details)] ;; don't migrate on fresh install
-                         (empty? (jdbc/metadata-query (.getTables meta nil nil liquibases-table-name (into-array String ["TABLE"])))))
+                         (empty? (jdbc/metadata-query
+                                  (.getTables meta nil nil liquibases-table-name (into-array String ["TABLE"])))))
         query (format "UPDATE %s SET FILENAME = ?" liquibases-table-name)]
     (when-not fresh-install?
       (jdbc/execute! conn [query "migrations/000_migrations.yaml"]))))
 
+(defn- release-lock-if-needed!
+  "Attempts to release the liquibase lock if present. Logs but does not bubble up the exception if one occurs as it's
+  intended to be used when a failure has occurred and bubbling up this exception would hide the real exception."
+  [^Liquibase liquibase]
+  (when (migration-lock-exists? liquibase)
+    (try
+      (.forceReleaseLocks liquibase)
+      (catch Exception e
+        (log/error e (trs "Unable to release the Liquibase lock after a migration failure"))))))
+
 (defn migrate!
   "Migrate the database (this can also be ran via command line like `java -jar metabase.jar migrate up` or `lein run
   migrate up`):
@@ -249,26 +276,37 @@
      ;; Disable auto-commit. This should already be off but set it just to be safe
      (.setAutoCommit (jdbc/get-connection conn) false)
      ;; Set up liquibase and let it do its thing
-     (log/info "Setting up Liquibase...")
-     (try
-       (let [liquibase (conn->liquibase conn)]
+     (log/info (trs "Setting up Liquibase..."))
+     (let [liquibase (conn->liquibase conn)]
+       (try
          (consolidate-liquibase-changesets conn)
-         (log/info "Liquibase is ready.")
+         (log/info (trs "Liquibase is ready."))
          (case direction
            :up            (migrate-up-if-needed! conn liquibase)
            :force         (force-migrate-up-if-needed! conn liquibase)
            :down-one      (.rollback liquibase 1 "")
            :print         (println (migrations-sql liquibase))
-           :release-locks (.forceReleaseLocks liquibase)))
-       ;; Migrations were successful; disable rollback-only so `.commit` will be called instead of `.rollback`
-       (jdbc/db-unset-rollback-only! conn)
-       :done
-       ;; If for any reason any part of the migrations fail then rollback all changes
-       (catch Throwable e
-         ;; This should already be happening as a result of `db-set-rollback-only!` but running it twice won't hurt so
-         ;; better safe than sorry
-         (.rollback (jdbc/get-connection conn))
-         (throw e))))))
+           :release-locks (.forceReleaseLocks liquibase))
+         ;; Migrations were successful; disable rollback-only so `.commit` will be called instead of `.rollback`
+         (jdbc/db-unset-rollback-only! conn)
+         :done
+         ;; In the Throwable block, we're releasing the lock assuming we have the lock and we failed while in the
+         ;; middle of a migration. It's possible that we failed because we couldn't get the lock. We don't want to
+         ;; clear the lock in that case, so handle that case separately
+         (catch LockException e
+           ;; This should already be happening as a result of `db-set-rollback-only!` but running it twice won't hurt so
+           ;; better safe than sorry
+           (.rollback (jdbc/get-connection conn))
+           (throw e))
+         ;; If for any reason any part of the migrations fail then rollback all changes
+         (catch Throwable e
+           ;; This should already be happening as a result of `db-set-rollback-only!` but running it twice won't hurt so
+           ;; better safe than sorry
+           (.rollback (jdbc/get-connection conn))
+           ;; With some failures, it's possible that the lock won't be released. To make this worse, if we retry the
+           ;; operation without releasing the lock first, the real error will get hidden behind a lock error
+           (release-lock-if-needed! liquibase)
+           (throw e)))))))
 
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
@@ -276,7 +314,7 @@
 ;;; +----------------------------------------------------------------------------------------------------------------+
 
 (defn connection-pool
-  "Create a C3P0 connection pool for the given database SPEC."
+  "Create a C3P0 connection pool for the given database `spec`."
   [{:keys [subprotocol subname classname minimum-pool-size idle-connection-test-period excess-timeout]
     :or   {minimum-pool-size           3
            idle-connection-test-period 0
@@ -345,12 +383,12 @@
    (verify-db-connection (:type db-details) db-details))
   ([engine details]
    {:pre [(keyword? engine) (map? details)]}
-   (log/info (u/format-color 'cyan "Verifying %s Database Connection ..." (name engine)))
+   (log/info (u/format-color 'cyan (trs "Verifying {0} Database Connection ..." (name engine))))
    (assert (binding [*allow-potentailly-unsafe-connections* true]
              (require 'metabase.driver)
              ((resolve 'metabase.driver/can-connect-with-details?) engine details))
      (format "Unable to connect to Metabase %s DB." (name engine)))
-   (log/info "Verify Database Connection ... " (u/emoji "✅"))))
+   (log/info (trs "Verify Database Connection ... ") (u/emoji "✅"))))
 
 
 (def ^:dynamic ^Boolean *disable-data-migrations*
@@ -376,11 +414,24 @@
 (defn- run-schema-migrations!
   "Run through our DB migration process and make sure DB is fully prepared"
   [auto-migrate? db-details]
-  (log/info "Running Database Migrations...")
+  (log/info (trs "Running Database Migrations..."))
   (if auto-migrate?
-    (migrate! db-details :up)
+    ;; There is a weird situation where running the migrations can cause a race condition: if two (or more) instances
+    ;; in a horizontal cluster are started at the exact same time, they can all start running migrations (and all
+    ;; acquire a lock) at the exact same moment. Since they all acquire a lock at the same time, none of them would
+    ;; have been blocked from starting by the lock being in place. (Yes, this not working sort of defeats the whole
+    ;; purpose of the lock in the first place, but this *is* Liquibase.)
+    ;;
+    ;; So what happens is one instance will ultimately end up commiting the transaction first (and succeed), while the
+    ;; others will fail due to duplicate tables or the like and have their transactions rolled back.
+    ;;
+    ;; However, we don't want to have that instance killed because its migrations failed for that reason, so retry a
+    ;; second time; this time, it will either run into the lock, or see that there are no migrations to run in the
+    ;; first place, and launch normally.
+    (u/auto-retry 1
+      (migrate! db-details :up))
     (print-migrations-and-quit! db-details))
-  (log/info "Database Migrations Current ... " (u/emoji "✅")))
+  (log/info (trs "Database Migrations Current ... ") (u/emoji "✅")))
 
 (defn- run-data-migrations!
   "Do any custom code-based migrations now that the db structure is up to date."
diff --git a/src/metabase/db/metadata_queries.clj b/src/metabase/db/metadata_queries.clj
index 7dc23248e6e32ab127ba3eec332ef2284a06f591..8995d97478d3599746da0f0531b9a1eaab6f94db 100644
--- a/src/metabase/db/metadata_queries.clj
+++ b/src/metabase/db/metadata_queries.clj
@@ -6,36 +6,34 @@
              [util :as u]]
             [metabase.models.table :refer [Table]]
             [metabase.query-processor.interface :as qpi]
-            [metabase.query-processor.middleware.expand :as ql]
             [metabase.util.schema :as su]
             [schema.core :as s]
             [toucan.db :as db]))
 
-(defn- qp-query [db-id query]
+(defn- qp-query [db-id mbql-query]
   {:pre [(integer? db-id)]}
   (-> (binding [qpi/*disable-qp-logging* true]
         (qp/process-query
           {:type     :query
            :database db-id
-           :query    query}))
+           :query    mbql-query}))
       :data
       :rows))
 
-(defn- field-query [{table-id :table_id} query]
+(defn- field-query [{table-id :table_id} mbql-query]
   {:pre [(integer? table-id)]}
   (qp-query (db/select-one-field :db_id Table, :id table-id)
             ;; this seeming useless `merge` statement IS in fact doing something important. `ql/query` is a threading
             ;; macro for building queries. Do not remove
-            (ql/query (merge query)
-                      (ql/source-table table-id))))
+            (assoc mbql-query :source-table table-id)))
 
 (defn table-row-count
   "Fetch the row count of TABLE via the query processor."
   [table]
   {:pre  [(map? table)]
    :post [(integer? %)]}
-  (let [results (qp-query (:db_id table) (ql/query (ql/source-table (u/get-id table))
-                                                   (ql/aggregation (ql/count))))]
+  (let [results (qp-query (:db_id table) {:source-table (u/get-id table)
+                                          :aggregation  [[:count]]})]
     (try (-> results first first long)
          (catch Throwable e
            (log/error "Error fetching table row count. Query returned:\n"
@@ -68,22 +66,20 @@
   ([field]
    (field-distinct-values field absolute-max-distinct-values-limit))
   ([field, max-results :- su/IntGreaterThanZero]
-   (mapv first (field-query field (-> {}
-                                      (ql/breakout (ql/field-id (u/get-id field)))
-                                      (ql/limit max-results))))))
+   (mapv first (field-query field {:breakout [[:field-id (u/get-id field)]]
+                                   :limit    max-results}))))
 
 (defn field-distinct-count
   "Return the distinct count of FIELD."
   [field & [limit]]
-  (-> (field-query field (-> {}
-                             (ql/aggregation (ql/distinct (ql/field-id (u/get-id field))))
-                             (ql/limit limit)))
+  (-> (field-query field {:aggregation [[:distinct [:field-id (u/get-id field)]]]
+                          :limit       limit})
       first first int))
 
 (defn field-count
   "Return the count of FIELD."
   [field]
-  (-> (field-query field (ql/aggregation {} (ql/count (ql/field-id (u/get-id field)))))
+  (-> (field-query field {:aggregation [[:count [:field-id (u/get-id field)]]]})
       first first int))
 
 (defn db-id
diff --git a/src/metabase/db/migrations.clj b/src/metabase/db/migrations.clj
index 1aeed2b266b17732a92614d5ed061272f0564256..da2a911c1b378b157f03f45ea1fac20c2ff0911c 100644
--- a/src/metabase/db/migrations.clj
+++ b/src/metabase/db/migrations.clj
@@ -15,23 +15,22 @@
              [db :as mdb]
              [public-settings :as public-settings]
              [util :as u]]
-            [metabase.events.activity-feed :refer [activity-feed-topics]]
             [metabase.models
-             [activity :refer [Activity]]
              [card :refer [Card]]
-             [dashboard-card :refer [DashboardCard]]
+             [collection :as collection :refer [Collection]]
+             [dashboard :refer [Dashboard]]
              [database :refer [Database virtual-id]]
              [field :refer [Field]]
              [humanization :as humanization]
              [permissions :as perms :refer [Permissions]]
-             [permissions-group :as perm-group]
+             [permissions-group :as perm-group :refer [PermissionsGroup]]
              [permissions-group-membership :as perm-membership :refer [PermissionsGroupMembership]]
-             [query-execution :as query-execution :refer [QueryExecution]]
+             [pulse :refer [Pulse]]
              [setting :as setting :refer [Setting]]
-             [table :as table :refer [Table]]
              [user :refer [User]]]
-            [metabase.query-processor.util :as qputil]
-            [metabase.util.date :as du]
+            [metabase.util
+             [date :as du]
+             [i18n :refer [trs]]]
             [toucan
              [db :as db]
              [models :as models]])
@@ -74,73 +73,6 @@
   (log/info "Finished running data migrations."))
 
 
-;;; +----------------------------------------------------------------------------------------------------------------+
-;;; |                                                   MIGRATIONS                                                   |
-;;; +----------------------------------------------------------------------------------------------------------------+
-
-;; Set the `:ssl` key in `details` to `false` for all existing MongoDB `Databases`.
-;; UI was automatically setting `:ssl` to `true` for every database added as part of the auto-SSL detection.
-;; Since Mongo did *not* support SSL, all existing Mongo DBs should actually have this key set to `false`.
-(defmigration ^{:author "camsaul", :added "0.13.0"} set-mongodb-databases-ssl-false
-  (doseq [{:keys [id details]} (db/select [Database :id :details], :engine "mongo")]
-    (db/update! Database id, :details (assoc details :ssl false))))
-
-
-;; Set default values for :schema in existing tables now that we've added the column
-;; That way sync won't get confused next time around
-(defmigration ^{:author "camsaul", :added "0.13.0"} set-default-schemas
-  (doseq [[engine default-schema] [["postgres" "public"]
-                                   ["h2"       "PUBLIC"]]]
-    (when-let [db-ids (db/select-ids Database :engine engine)]
-      (db/update-where! Table {:schema nil
-                               :db_id  [:in db-ids]}
-        :schema default-schema))))
-
-
-;; Populate the initial value for the `:admin-email` setting for anyone who hasn't done it yet
-(defmigration ^{:author "agilliland", :added "0.13.0"} set-admin-email
-  (when-not (setting/get :admin-email)
-    (when-let [email (db/select-one-field :email 'User
-                       :is_superuser true
-                       :is_active    true)]
-      (setting/set! :admin-email email))))
-
-
-;; Remove old `database-sync` activity feed entries
-(defmigration ^{:author "agilliland", :added "0.13.0"} remove-database-sync-activity-entries
-  (when-not (contains? activity-feed-topics :database-sync-begin)
-    (db/simple-delete! Activity :topic "database-sync")))
-
-
-;; Migrate dashboards to the new grid
-;; NOTE: this scales the dashboards by 4x in the Y-scale and 3x in the X-scale
-(defmigration ^{:author "agilliland",:added "0.16.0"} update-dashboards-to-new-grid
-  (doseq [{:keys [id row col sizeX sizeY]} (db/select DashboardCard)]
-    (db/update! DashboardCard id
-      :row   (when row (* row 4))
-      :col   (when col (* col 3))
-      :sizeX (when sizeX (* sizeX 3))
-      :sizeY (when sizeY (* sizeY 4)))))
-
-
-;; migrate data to new visibility_type column on field
-(defmigration ^{:author "agilliland",:added "0.16.0"} migrate-field-visibility-type
-  (when-not (zero? (db/count Field :visibility_type "unset"))
-    ;; start by marking all inactive fields as :retired
-    (db/update-where! Field {:visibility_type "unset"
-                             :active          false}
-      :visibility_type "retired")
-    ;; if field is active but preview_display = false then it becomes :details-only
-    (db/update-where! Field {:visibility_type "unset"
-                             :active          true
-                             :preview_display false}
-      :visibility_type "details-only")
-    ;; everything else should end up as a :normal field
-    (db/update-where! Field {:visibility_type "unset"
-                             :active          true}
-      :visibility_type "normal")))
-
-
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                                 PERMISSIONS v1                                                 |
 ;;; +----------------------------------------------------------------------------------------------------------------+
@@ -271,40 +203,6 @@
 ;;; |                                           Migrating QueryExecutions                                            |
 ;;; +----------------------------------------------------------------------------------------------------------------+
 
-;; We're copying over data from the legacy `query_queryexecution` table to the new `query_execution` table; see #4522
-;; and #4531 for details
-
-;; model definition for the old table to facilitate the data copying process
-(models/defmodel ^:private ^:deprecated LegacyQueryExecution :query_queryexecution)
-
-(u/strict-extend (class LegacyQueryExecution)
-  models/IModel
-  (merge models/IModelDefaults
-         {:default-fields (constantly [:executor_id :result_rows :started_at :json_query :error :running_time])
-          :types          (constantly {:json_query :json, :error :clob})}))
-
-(defn- LegacyQueryExecution->QueryExecution
-  "Convert a LegacyQueryExecution to a format suitable for insertion as a new-format QueryExecution."
-  [{query :json_query, :as query-execution}]
-  (-> (assoc query-execution
-        :hash   (qputil/query-hash query)
-        :native (not (qputil/mbql-query? query)))
-      ;; since error is nullable now remove the old blank error message strings
-      (update :error (fn [error-message]
-                       (when-not (str/blank? error-message)
-                         error-message)))
-      (dissoc :json_query)))
-
-;; Migrate entries from the old query execution table to the new one. This might take a few minutes
-(defmigration ^{:author "camsaul", :added "0.23.0"} migrate-query-executions
-  ;; migrate the most recent 100,000 entries. Make sure the DB doesn't get snippy by trying to insert too many records
-  ;; at once. Divide the INSERT statements into chunks of 1,000
-  (binding [query-execution/*validate-context* false]
-    (doseq [chunk (partition-all 1000 (db/select LegacyQueryExecution {:limit 100000, :order-by [[:id :desc]]}))]
-      (db/insert-many! QueryExecution
-        (for [query-execution chunk]
-          (LegacyQueryExecution->QueryExecution query-execution))))))
-
 ;; drop the legacy QueryExecution table now that we don't need it anymore
 (defmigration ^{:author "camsaul", :added "0.23.0"} drop-old-query-execution-table
   ;; DROP TABLE IF EXISTS should work on Postgres, MySQL, and H2
@@ -382,7 +280,7 @@
     ;; For each Card belonging to that BigQuery database...
     (doseq [{query :dataset_query, card-id :id} (db/select [Card :id :dataset_query] :database_id database-id)]
       ;; If the Card isn't native, ignore it
-      (when (= (:type query) "native")
+      (when (= (keyword (:type query)) :native)
         (let [sql (get-in query [:native :query])]
           ;; if the Card already contains a #standardSQL or #legacySQL (both are case-insenstive) directive, ignore it
           (when-not (re-find #"(?i)#(standard|legacy)sql" sql)
@@ -400,3 +298,47 @@
   (db/transaction
     (doseq [user (db/select [User :id :password_salt] :ldap_auth [:= true])]
       (db/update! User (u/get-id user) :password (creds/hash-bcrypt (str (:password_salt user) (UUID/randomUUID)))))))
+
+
+;; In 0.30 dashboards and pulses will be saved in collections rather than on separate list pages. Additionally, there
+;; will no longer be any notion of saved questions existing outside of a collection (i.e. in the weird "Everything
+;; Else" area where they can currently be saved).
+;;
+;; Consequently we'll need to move existing dashboards, pulses, and questions-not-in-a-collection to a new location
+;; when users upgrade their instance to 0.30 from a previous version.
+;;
+;; The user feedback we've received points to a UX that would do the following:
+;;
+;; 1. Set permissions to the Root Collection to readwrite perms access for *all* Groups.
+;;
+;; 2. Create three new collections within the root collection: "Migrated dashboards," "Migrated pulses," and "Migrated
+;;    questions."
+;;
+;; 3. The permissions settings for these new collections should be set to No Access for all user groups except
+;;    Administrators.
+;;
+;; 4. Existing Dashboards, Pulses, and Questions from the "Everything Else" area should now be present within these
+;;    new collections.
+;;
+(defmigration ^{:author "camsaul", :added "0.30.0"} add-migrated-collections
+  (let [non-admin-group-ids (db/select-ids PermissionsGroup :id [:not= (u/get-id (perm-group/admin))])]
+    ;; 1. Grant Root Collection readwrite perms to all Groups. Except for admin since they already have root (`/`)
+    ;; perms, and we don't want to put extra entries in there that confuse things
+    (doseq [group-id non-admin-group-ids]
+      (perms/grant-collection-readwrite-permissions! group-id collection/root-collection))
+    ;; 2. Create the new collections.
+    (doseq [[model new-collection-name] {Dashboard (str (trs "Migrated Dashboards"))
+                                         Pulse     (str (trs "Migrated Pulses"))
+                                         Card      (str (trs "Migrated Questions"))}
+            :when                       (db/exists? model :collection_id nil)
+            :let                        [new-collection (db/insert! Collection
+                                                          :name  new-collection-name
+                                                          :color "#509ee3")]] ; MB brand color
+      ;; 3. make sure the non-admin groups don't have any perms for this Collection.
+      (doseq [group-id non-admin-group-ids]
+        (perms/revoke-collection-permissions! group-id new-collection))
+      ;; 4. move everything not in this Collection to a new Collection
+      (log/info (trs "Moving instances of {0} that aren't in a Collection to {1} Collection {2}"
+                     (name model) new-collection-name (u/get-id new-collection)))
+      (db/update-where! model {:collection_id nil}
+        :collection_id (u/get-id new-collection)))))
diff --git a/src/metabase/driver.clj b/src/metabase/driver.clj
index 6abe80896205973191342c2439635d582da25622..c15665872720200567df67121725c4cdc7134618 100644
--- a/src/metabase/driver.clj
+++ b/src/metabase/driver.clj
@@ -23,8 +23,9 @@
              [database :refer [Database]]
              [setting :refer [defsetting]]]
             [metabase.sync.interface :as si]
-            [metabase.util.date :as du]
-            [puppetlabs.i18n.core :refer [trs tru]]
+            [metabase.util
+             [date :as du]
+             [i18n :refer [trs tru]]]
             [schema.core :as s]
             [toucan.db :as db])
   (:import clojure.lang.Keyword
@@ -54,6 +55,53 @@
    :username-incorrect                 (tru "Looks like your username is incorrect.")
    :username-or-password-incorrect     (tru "Looks like the username or password is incorrect.")})
 
+(def default-host-details
+  "Map of the db host details field, useful for `details-fields` implementations"
+  {:name         "host"
+   :display-name (tru "Host")
+   :default      "localhost"})
+
+(def default-port-details
+  "Map of the db port details field, useful for `details-fields` implementations. Implementations should assoc a
+  `:default` key."
+  {:name         "port"
+   :display-name (tru "Port")
+   :type         :integer})
+
+(def default-user-details
+  "Map of the db user details field, useful for `details-fields` implementations"
+  {:name         "user"
+   :display-name (tru "Database username")
+   :placeholder  (tru "What username do you use to login to the database?")
+   :required     true})
+
+(def default-password-details
+  "Map of the db password details field, useful for `details-fields` implementations"
+  {:name         "password"
+   :display-name (tru "Database password")
+   :type         :password
+   :placeholder  "*******"})
+
+(def default-dbname-details
+  "Map of the db name details field, useful for `details-fields` implementations"
+  {:name         "dbname"
+   :display-name (tru "Database name")
+   :placeholder  (tru "birds_of_the_world")
+   :required     true})
+
+(def default-ssl-details
+  "Map of the db ssl details field, useful for `details-fields` implementations"
+  {:name         "ssl"
+   :display-name (tru "Use a secure connection (SSL)?")
+   :type         :boolean
+   :default      false})
+
+(def default-additional-options-details
+  "Map of the db `additional-options` details field, useful for `details-fields` implementations. Should assoc a
+  `:placeholder` key"
+  {:name         "additional-options"
+   :display-name (tru "Additional JDBC connection string options")})
+
 (defprotocol IDriver
   "Methods that Metabase drivers must implement. Methods marked *OPTIONAL* have default implementations in
    `IDriverDefaultsMixin`. Drivers should also implement `getName` form `clojure.lang.Named`, so we can call `name` on
@@ -392,7 +440,8 @@
              [clojure.lang.IPersistentMap    :type/Dictionary]
              [clojure.lang.IPersistentVector :type/Array]
              [org.bson.types.ObjectId        :type/MongoBSONID]
-             [org.postgresql.util.PGobject   :type/*]])
+             [org.postgresql.util.PGobject   :type/*]
+             [nil                            :type/*]]) ; all-NULL columns in DBs like Mongo w/o explicit types
       (log/warn (trs "Don''t know how to map class ''{0}'' to a Field base_type, falling back to :type/*." klass))
       :type/*))
 
@@ -401,7 +450,7 @@
   [values]
   (->> values
        (take 100)                                   ; take up to 100 values
-       (filter (complement nil?))                   ; filter out `nil` values
+       (remove nil?)                                ; filter out `nil` values
        (group-by (comp class->base-type class))     ; now group by their base-type
        (sort-by (comp (partial * -1) count second)) ; sort the map into pairs of [base-type count] with highest count as first pair
        ffirst))                                     ; take the base-type from the first pair
@@ -484,10 +533,12 @@
   "Run a basic MBQL query to fetch a sample of rows belonging to a Table."
   [table :- si/TableInstance, fields :- [si/FieldInstance]]
   (let [results ((resolve 'metabase.query-processor/process-query)
-                 {:database (:db_id table)
-                  :type     :query
-                  :query    {:source-table (u/get-id table)
-                             :fields       (vec (for [field fields]
-                                                  [:field-id (u/get-id field)]))
-                             :limit        max-sample-rows}})]
+                 {:database   (:db_id table)
+                  :type       :query
+                  :query      {:source-table (u/get-id table)
+                               :fields       (vec (for [field fields]
+                                                    [:field-id (u/get-id field)]))
+                               :limit        max-sample-rows}
+                  :middleware {:format-rows?           false
+                               :skip-results-metadata? true}})]
     (get-in results [:data :rows])))
diff --git a/src/metabase/driver/bigquery.clj b/src/metabase/driver/bigquery.clj
index 1fffeece2ea36e47a6782788976c9382bd9302f6..b3292c3f978c740189260166e1d7f91e7fe0c699 100644
--- a/src/metabase/driver/bigquery.clj
+++ b/src/metabase/driver/bigquery.clj
@@ -1,6 +1,5 @@
 (ns metabase.driver.bigquery
-  (:require [cheshire.core :as json]
-            [clj-time
+  (:require [clj-time
              [coerce :as tcoerce]
              [core :as time]
              [format :as tformat]]
@@ -27,8 +26,8 @@
              [util :as qputil]]
             [metabase.util
              [date :as du]
-             [honeysql-extensions :as hx]]
-            [puppetlabs.i18n.core :refer [tru]]
+             [honeysql-extensions :as hx]
+             [i18n :refer [tru]]]
             [toucan.db :as db])
   (:import com.google.api.client.googleapis.auth.oauth2.GoogleCredential
            com.google.api.client.http.HttpRequestInitializer
@@ -178,34 +177,45 @@
                    (.setQuery query-string))]
      (google/execute (.query (.jobs client) project-id request)))))
 
-(defn- parse-timestamp-str [s]
-  ;; Timestamp strings either come back as ISO-8601 strings or Unix timestamps in µs, e.g. "1.3963104E9"
-  (or
-   (du/->Timestamp s time/utc)
-   ;; If parsing as ISO-8601 fails parse as a double then convert to ms. This is ms since epoch in UTC. By using
-   ;; `->Timestamp`, it will convert from ms in UTC to a timestamp object in the JVM timezone
-   (du/->Timestamp (* (Double/parseDouble s) 1000))))
-
-(def ^:private bigquery-time-format (tformat/formatter "HH:mm:SS" time/utc))
-
-(defn- parse-bigquery-time [time-string]
-  (->> time-string
-       (tformat/parse bigquery-time-format)
-       tcoerce/to-long
-       Time.))
-
-(defn- unparse-bigquery-time [coercible-to-dt]
+(def ^:private ^:dynamic *bigquery-timezone*
+  "BigQuery stores all of it's timestamps in UTC. That timezone can be changed via a SQL function invocation in a
+  native query, but that change in timezone is not conveyed through the BigQuery API. In most situations
+  `*bigquery-timezone*` will just be UTC. If the user is always changing the timezone via native SQL function
+  invocation, they can set their JVM TZ to the correct timezone, mark `use-jvm-timezone` to `true` and that will bind
+  this dynamic var to the JVM TZ rather than UTC"
+  time/utc)
+
+(defn- parse-timestamp-str [timezone]
+  (fn [s]
+    ;; Timestamp strings either come back as ISO-8601 strings or Unix timestamps in µs, e.g. "1.3963104E9"
+    (or
+     (du/->Timestamp s timezone)
+     ;; If parsing as ISO-8601 fails parse as a double then convert to ms. This is ms since epoch in UTC. By using
+     ;; `->Timestamp`, it will convert from ms in UTC to a timestamp object in the JVM timezone
+     (du/->Timestamp (* (Double/parseDouble s) 1000)))))
+
+(defn- bigquery-time-format [timezone]
+  (tformat/formatter "HH:mm:SS" timezone))
+
+(defn- parse-bigquery-time [timezone]
+  (fn [time-string]
+    (->> time-string
+         (tformat/parse (bigquery-time-format timezone))
+         tcoerce/to-long
+         Time.)))
+
+(defn- unparse-bigquery-time [timezone coercible-to-dt]
   (->> coercible-to-dt
        tcoerce/to-date-time
-       (tformat/unparse bigquery-time-format)))
+       (tformat/unparse (bigquery-time-format timezone))))
 
 (def ^:private type->parser
   "Functions that should be used to coerce string values in responses to the appropriate type for their column."
-  {"BOOLEAN"   #(Boolean/parseBoolean %)
-   "FLOAT"     #(Double/parseDouble %)
-   "INTEGER"   #(Long/parseLong %)
-   "RECORD"    identity
-   "STRING"    identity
+  {"BOOLEAN"   (constantly #(Boolean/parseBoolean %))
+   "FLOAT"     (constantly #(Double/parseDouble %))
+   "INTEGER"   (constantly #(Long/parseLong %))
+   "RECORD"    (constantly identity)
+   "STRING"    (constantly identity)
    "DATE"      parse-timestamp-str
    "DATETIME"  parse-timestamp-str
    "TIMESTAMP" parse-timestamp-str
@@ -225,8 +235,10 @@
        (post-process-native response (dec timeout-seconds)))
      ;; Otherwise the job *is* complete
      (let [^TableSchema schema (.getSchema response)
-           parsers             (for [^TableFieldSchema field (.getFields schema)]
-                                 (type->parser (.getType field)))
+           parsers             (doall
+                                (for [^TableFieldSchema field (.getFields schema)
+                                      :let [parser-fn (type->parser (.getType field))]]
+                                  (parser-fn *bigquery-timezone*)))
            columns             (for [column (table-schema->metabase-field-info schema)]
                                  (set/rename-keys column {:base-type :base_type}))]
        {:columns (map :name columns)
@@ -394,7 +406,7 @@
 (defmethod sqlqp/->honeysql [BigQueryDriver TimeValue]
   [driver {:keys [value]}]
   (->> value
-       unparse-bigquery-time
+       (unparse-bigquery-time *bigquery-timezone*)
        (sqlqp/->honeysql driver)
        hx/->time))
 
@@ -433,16 +445,11 @@
 
 (defn- field->identifier
   "Generate appropriate identifier for a Field for SQL parameters. (NOTE: THIS IS ONLY USED FOR SQL PARAMETERS!)"
-  ;; TODO - Making a DB call for each field to fetch its dataset is inefficient and makes me cry, but this method is
+  ;; TODO - Making 2 DB calls for each field to fetch its dataset is inefficient and makes me cry, but this method is
   ;; currently only used for SQL params so it's not a huge deal at this point
   [{table-id :table_id, :as field}]
-  ;; manually write the query here to save us from having to do 2 seperate queries...
-  (let [[{:keys [details table-name]}] (db/query {:select    [[:database.details :details] [:table.name :table-name]]
-                                                  :from      [[:metabase_table :table]]
-                                                  :left-join [[:metabase_database :database]
-                                                              [:= :database.id :table.db_id]]
-                                                  :where     [:= :table.id (u/get-id table-id)]})
-        details (json/parse-string (u/jdbc-clob->str details) keyword)]
+  (let [{table-name :name, database-id :db_id} (db/select-one ['Table :name :db_id], :id (u/get-id table-id))
+        details                                (db/select-one-field :details 'Database, :id (u/get-id database-id))]
     (map->BigQueryIdentifier {:dataset-name (:dataset-id details), :table-name table-name, :field-name (:name field)})))
 
 (defn- field->breakout-identifier [driver field]
@@ -519,17 +526,24 @@
        :table-name table-name
        :mbql?      true})))
 
+(defn- effective-query-timezone [database]
+  (if-let [^java.util.TimeZone jvm-tz (and (get-in database [:details :use-jvm-timezone])
+                                           @du/jvm-timezone)]
+    (time/time-zone-for-id (.getID jvm-tz))
+    time/utc))
+
 (defn- execute-query [{database                                               :database
                        {sql :query, params :params, :keys [table-name mbql?]} :native
                        :as                                                    outer-query}]
-  (let [sql     (str "-- " (qputil/query->remark outer-query) "\n" (if (seq params)
-                                                                     (unprepare/unprepare (cons sql params))
-                                                                     sql))
-        results (process-native* database sql)
-        results (if mbql?
-                  (post-process-mbql table-name results)
-                  (update results :columns (partial map keyword)))]
-    (assoc results :annotate? mbql?)))
+  (binding [*bigquery-timezone* (effective-query-timezone database)]
+    (let [sql     (str "-- " (qputil/query->remark outer-query) "\n" (if (seq params)
+                                                                       (unprepare/unprepare (cons sql params))
+                                                                       sql))
+          results (process-native* database sql)
+          results (if mbql?
+                    (post-process-mbql table-name results)
+                    (update results :columns (partial map keyword)))]
+      (assoc results :annotate? mbql?))))
 
 
 ;; BigQuery doesn't return a timezone with it's time strings as it's always UTC, JodaTime parsing also defaults to UTC
@@ -561,26 +575,30 @@
           :describe-database        (u/drop-first-arg describe-database)
           :describe-table           (u/drop-first-arg describe-table)
           :details-fields           (constantly [{:name         "project-id"
-                                                  :display-name "Project ID"
-                                                  :placeholder  "praxis-beacon-120871"
+                                                  :display-name (tru "Project ID")
+                                                  :placeholder  (tru "praxis-beacon-120871")
                                                   :required     true}
                                                  {:name         "dataset-id"
-                                                  :display-name "Dataset ID"
-                                                  :placeholder  "toucanSightings"
+                                                  :display-name (tru "Dataset ID")
+                                                  :placeholder  (tru "toucanSightings")
                                                   :required     true}
                                                  {:name         "client-id"
-                                                  :display-name "Client ID"
+                                                  :display-name (tru "Client ID")
                                                   :placeholder  "1201327674725-y6ferb0feo1hfssr7t40o4aikqll46d4.apps.googleusercontent.com"
                                                   :required     true}
                                                  {:name         "client-secret"
-                                                  :display-name "Client Secret"
+                                                  :display-name (tru "Client Secret")
                                                   :placeholder  "dJNi4utWgMzyIFo2JbnsK6Np"
                                                   :required     true}
                                                  {:name         "auth-code"
-                                                  :display-name "Auth Code"
+                                                  :display-name (tru "Auth Code")
                                                   :placeholder  "4/HSk-KtxkSzTt61j5zcbee2Rmm5JHkRFbL5gD5lgkXek"
-                                                  :required     true}])
-          :execute-query            (u/drop-first-arg execute-query)
+                                                  :required     true}
+                                                 {:name         "use-jvm-timezone"
+                                                  :display-name (tru "Use JVM Time Zone")
+                                                  :default      "false"
+                                                  :type         :boolean}])
+          :execute-query            (u/drop-first-arg #'execute-query)
           ;; Don't enable foreign keys when testing because BigQuery *doesn't* have a notion of foreign keys. Joins
           ;; are still allowed, which puts us in a weird position, however; people can manually specifiy "foreign key"
           ;; relationships in admin and everything should work correctly. Since we can't infer any "FK" relationships
diff --git a/src/metabase/driver/crate.clj b/src/metabase/driver/crate.clj
index 3a7fcd8832df0ecd2a6be56150afffd0c970b633..0f31c8259a046bc2399f6633127210538dfd4a44 100644
--- a/src/metabase/driver/crate.clj
+++ b/src/metabase/driver/crate.clj
@@ -6,7 +6,8 @@
              [driver :as driver]
              [util :as u]]
             [metabase.driver.crate.util :as crate-util]
-            [metabase.driver.generic-sql :as sql])
+            [metabase.driver.generic-sql :as sql]
+            [metabase.util.i18n :refer [tru]])
   (:import java.sql.DatabaseMetaData))
 
 (def ^:private ^:const column->base-type
@@ -109,7 +110,7 @@
           :date-interval   crate-util/date-interval
           :describe-table  describe-table
           :details-fields  (constantly [{:name         "hosts"
-                                         :display-name "Hosts"
+                                         :display-name (tru "Hosts")
                                          :default      "localhost:5432/"}])
           :features        (comp (u/rpartial disj :foreign-keys) sql/features)
           :current-db-time (driver/make-current-db-time-fn crate-db-time-query crate-date-formatters)})
diff --git a/src/metabase/driver/druid.clj b/src/metabase/driver/druid.clj
index 1672961854538d5331d02780e143b52c6c79b52b..11018b9cb091567aaf6e06bf62d2d4e194142eac 100644
--- a/src/metabase/driver/druid.clj
+++ b/src/metabase/driver/druid.clj
@@ -7,7 +7,9 @@
              [driver :as driver]
              [util :as u]]
             [metabase.driver.druid.query-processor :as qp]
-            [metabase.util.ssh :as ssh]))
+            [metabase.util
+             [i18n :refer [tru]]
+             [ssh :as ssh]]))
 
 ;;; ### Request helper fns
 
@@ -151,13 +153,10 @@
           :describe-database (u/drop-first-arg describe-database)
           :describe-table    (u/drop-first-arg describe-table)
           :details-fields    (constantly (ssh/with-tunnel-config
-                                           [{:name         "host"
-                                             :display-name "Host"
-                                             :default      "http://localhost"}
-                                            {:name         "port"
-                                             :display-name "Broker node port"
-                                             :type         :integer
-                                             :default      8082}]))
+                                           [(assoc driver/default-host-details :default "http://localhost")
+                                            (assoc driver/default-port-details
+                                              :display-name (tru "Broker node port")
+                                              :default      8082)]))
           :execute-query     (fn [_ query] (qp/execute-query do-query-with-cancellation query))
           :features          (constantly #{:basic-aggregations :set-timezone :expression-aggregations})
           :mbql->native      (u/drop-first-arg qp/mbql->native)}))
diff --git a/src/metabase/driver/druid/query_processor.clj b/src/metabase/driver/druid/query_processor.clj
index d46d0c4069868ce7bc5ffb48b640f30136d18675..1f0ebff8d8f7a6216d95ec36553caab3c0d8d26d 100644
--- a/src/metabase/driver/druid/query_processor.clj
+++ b/src/metabase/driver/druid/query_processor.clj
@@ -270,8 +270,7 @@
   (let [output-name (or custom-name output-name)]
     (if-not (isa? query-type ::ag-query)
       query-context
-      (let [ag-type (when-not (= ag-type :rows) ag-type)
-            [projections ag-clauses] (create-aggregation-clause output-name ag-type ag-field)]
+      (let [[projections ag-clauses] (create-aggregation-clause output-name ag-type ag-field)]
         (-> query-context
             (update :projections #(vec (concat % projections)))
             (update :query #(merge-with concat % ag-clauses)))))))
@@ -785,7 +784,7 @@
                     0 :none
                     1 :one
                       :many)
-        agg?      (boolean (and ag-type (not= ag-type :rows)))
+        agg?      (boolean ag-type)
         ts?       (and (instance? DateTimeField (first breakout-fields))            ; Checks whether the query is a timeseries
                        (contains? timeseries-units (:unit (first breakout-fields))) ; (excludes x-of-y type breakouts)
                        (nil? limit))]                                               ; (excludes queries with LIMIT)
diff --git a/src/metabase/driver/generic_sql/query_processor.clj b/src/metabase/driver/generic_sql/query_processor.clj
index e29e0b7d9eb14f2da784a942ae6ecf5a0422a0b3..cc790d658475af4e400a5f74f7547acb4c56af42 100644
--- a/src/metabase/driver/generic_sql/query_processor.clj
+++ b/src/metabase/driver/generic_sql/query_processor.clj
@@ -16,12 +16,12 @@
              [util :as qputil]]
             [metabase.util
              [date :as du]
-             [honeysql-extensions :as hx]]
-            [puppetlabs.i18n.core :refer [trs]])
+             [honeysql-extensions :as hx]
+             [i18n :refer [trs]]])
   (:import [java.sql PreparedStatement ResultSet ResultSetMetaData SQLException]
            [java.util Calendar Date TimeZone]
            [metabase.query_processor.interface AgFieldRef BinnedField DateTimeField DateTimeValue Expression
-            ExpressionRef Field FieldLiteral RelativeDateTimeValue TimeField TimeValue Value]))
+            ExpressionRef Field FieldLiteral JoinQuery JoinTable RelativeDateTimeValue TimeField TimeValue Value]))
 
 (def ^:dynamic *query*
   "The outer query currently being processed."
@@ -29,7 +29,7 @@
 
 (def ^:private ^:dynamic *nested-query-level*
   "How many levels deep are we into nested queries? (0 = top level.) We keep track of this so we know what level to
-  find referenced aggregations (otherwise something like [:aggregate-field 0] could be ambiguous in a nested query).
+  find referenced aggregations (otherwise something like [:aggregation 0] could be ambiguous in a nested query).
   Each nested query increments this counter by 1."
   0)
 
@@ -57,7 +57,7 @@
       (throw (Exception. (format "No expression named '%s'." (name expression-name))))))
 
 (defn- aggregation-at-index
-  "Fetch the aggregation at index. This is intended to power aggregate field references (e.g. [:aggregate-field 0]).
+  "Fetch the aggregation at index. This is intended to power aggregate field references (e.g. [:aggregation 0]).
    This also handles nested queries, which could be potentially ambiguous if multiple levels had aggregations."
   ([index]
    (aggregation-at-index index (:query *query*) *nested-query-level*))
@@ -277,19 +277,32 @@
   [driver honeysql-form {clause :filter}]
   (h/where honeysql-form (filter-clause->predicate driver clause)))
 
+(declare build-honeysql-form)
+
+(defn- make-honeysql-join-clauses
+  "Returns a seq of honeysql join clauses, joining to `table-or-query-expr`. `jt-or-jq` can be either a `JoinTable` or
+  a `JoinQuery`"
+  [table-or-query-expr {:keys [table-name pk-field source-field schema join-alias] :as jt-or-jq}]
+  (let [{{source-table-name :name, source-schema :schema} :source-table} *query*]
+    [[table-or-query-expr (keyword join-alias)]
+     [:= (hx/qualify-and-escape-dots source-schema source-table-name (:field-name source-field))
+      (hx/qualify-and-escape-dots join-alias (:field-name pk-field))]]))
+
+(defmethod ->honeysql [Object JoinTable]
+  ;; Returns a seq of clauses used in a honeysql join clause
+  [driver {:keys [schema table-name] :as jt} ]
+  (make-honeysql-join-clauses (hx/qualify-and-escape-dots schema table-name) jt))
+
+(defmethod ->honeysql [Object JoinQuery]
+  ;; Returns a seq of clauses used in a honeysql join clause
+  [driver {:keys [query] :as jq}]
+  (make-honeysql-join-clauses (build-honeysql-form driver query) jq))
+
 (defn apply-join-tables
   "Apply expanded query `join-tables` clause to `honeysql-form`. Default implementation of `apply-join-tables` for SQL
   drivers."
-  [_ honeysql-form {join-tables :join-tables, {source-table-name :name, source-schema :schema} :source-table}]
-  ;; TODO - why doesn't this use ->honeysql like mostly everything else does?
-  (loop [honeysql-form honeysql-form, [{:keys [table-name pk-field source-field schema join-alias]} & more] join-tables]
-    (let [honeysql-form (h/merge-left-join honeysql-form
-                          [(hx/qualify-and-escape-dots schema table-name) (keyword join-alias)]
-                          [:= (hx/qualify-and-escape-dots source-schema source-table-name (:field-name source-field))
-                              (hx/qualify-and-escape-dots join-alias                      (:field-name pk-field))])]
-      (if (seq more)
-        (recur honeysql-form more)
-        honeysql-form))))
+  [driver honeysql-form {:keys [join-tables]}]
+  (reduce (partial apply h/merge-left-join) honeysql-form (map #(->honeysql driver %) join-tables)))
 
 (defn apply-limit
   "Apply `limit` clause to HONEYSQL-FORM. Default implementation of `apply-limit` for SQL drivers."
diff --git a/src/metabase/driver/google.clj b/src/metabase/driver/google.clj
index e3d805c0461883816fd5ef4d54d18c22c00041a4..0d4c43f03cb780fc95a59b5520c906e8588cf127 100644
--- a/src/metabase/driver/google.clj
+++ b/src/metabase/driver/google.clj
@@ -46,11 +46,18 @@
   (u/auto-retry 2
     (execute-no-auto-retry request)))
 
+(defn- create-application-name
+  "Creates the application name string, separated out from the `def` below so it's testable with different values"
+  [{:keys [tag ^String hash branch]}]
+  (let [encoded-hash (some-> hash (.getBytes "UTF-8") codec/base64-encode)]
+    (format "Metabase/%s (GPN:Metabse; %s %s)"
+            (or tag "?")
+            (or encoded-hash "?")
+            (or branch "?"))))
+
 (def ^:const ^String application-name
   "The application name we should use for Google drivers. Requested by Google themselves -- see #2627"
-  (let [{:keys [tag ^String hash branch]} config/mb-version-info
-        encoded-hash                      (-> hash (.getBytes "UTF-8") codec/base64-encode)]
-    (format "Metabase/%s (GPN:Metabse; %s %s)" tag encoded-hash branch)))
+  (create-application-name config/mb-version-info))
 
 
 (defn- fetch-access-and-refresh-tokens* [scopes, ^String client-id, ^String client-secret, ^String auth-code]
diff --git a/src/metabase/driver/googleanalytics.clj b/src/metabase/driver/googleanalytics.clj
index c3866c5db660e07900743bf292304c003d21f6c0..10b9ceed69741666d5c7527395a8234ded275fa2 100644
--- a/src/metabase/driver/googleanalytics.clj
+++ b/src/metabase/driver/googleanalytics.clj
@@ -7,7 +7,7 @@
             [metabase.driver.google :as google]
             [metabase.driver.googleanalytics.query-processor :as qp]
             [metabase.models.database :refer [Database]]
-            [puppetlabs.i18n.core :refer [tru]])
+            [metabase.util.i18n :refer [tru]])
   (:import com.google.api.client.googleapis.auth.oauth2.GoogleCredential
            [com.google.api.services.analytics Analytics Analytics$Builder Analytics$Data$Ga$Get AnalyticsScopes]
            [com.google.api.services.analytics.model Column Columns Profile Profiles Webproperties Webproperty]
@@ -228,8 +228,8 @@
   ;; if we get a big long message about how we need to enable the GA API, then replace it with a short message about
   ;; how we need to enable the API
   (if-let [[_ enable-api-url] (re-find #"Enable it by visiting ([^\s]+) then retry." message)]
-    (tru "You must enable the Google Analytics API. Use this link to go to the Google Developers Console: {0}"
-         enable-api-url)
+    (str (tru "You must enable the Google Analytics API. Use this link to go to the Google Developers Console: {0}"
+              enable-api-url))
     message))
 
 
@@ -245,19 +245,19 @@
           :describe-database                 (u/drop-first-arg describe-database)
           :describe-table                    (u/drop-first-arg describe-table)
           :details-fields                    (constantly [{:name         "account-id"
-                                                           :display-name "Google Analytics Account ID"
+                                                           :display-name (tru "Google Analytics Account ID")
                                                            :placeholder  "1234567"
                                                            :required     true}
                                                           {:name         "client-id"
-                                                           :display-name "Client ID"
+                                                           :display-name (tru "Client ID")
                                                            :placeholder  "1201327674725-y6ferb0feo1hfssr7t40o4aikqll46d4.apps.googleusercontent.com"
                                                            :required     true}
                                                           {:name         "client-secret"
-                                                           :display-name "Client Secret"
+                                                           :display-name (tru "Client Secret")
                                                            :placeholder  "dJNi4utWgMzyIFo2JbnsK6Np"
                                                            :required     true}
                                                           {:name         "auth-code"
-                                                           :display-name "Auth Code"
+                                                           :display-name (tru "Auth Code")
                                                            :placeholder  "4/HSk-KtxkSzTt61j5zcbee2Rmm5JHkRFbL5gD5lgkXek"
                                                            :required     true}])
           :execute-query                     (u/drop-first-arg (partial qp/execute-query do-query))
diff --git a/src/metabase/driver/h2.clj b/src/metabase/driver/h2.clj
index 5d0bbca85a1b11de1dc3f31555f41f0b7278c902..c08570eece5b9b9cf448aef5d9a3476f90b2b5ad 100644
--- a/src/metabase/driver/h2.clj
+++ b/src/metabase/driver/h2.clj
@@ -8,8 +8,9 @@
             [metabase.db.spec :as dbspec]
             [metabase.driver.generic-sql :as sql]
             [metabase.models.database :refer [Database]]
-            [metabase.util.honeysql-extensions :as hx]
-            [puppetlabs.i18n.core :refer [tru]]
+            [metabase.util
+             [honeysql-extensions :as hx]
+             [i18n :refer [tru]]]
             [toucan.db :as db]))
 
 (def ^:private ^:const column->base-type
@@ -218,8 +219,9 @@
   (merge (sql/IDriverSQLDefaultsMixin)
          {:date-interval                     (u/drop-first-arg date-interval)
           :details-fields                    (constantly [{:name         "db"
-                                                           :display-name "Connection String"
-                                                           :placeholder  "file:/Users/camsaul/bird_sightings/toucans"
+                                                           :display-name (tru "Connection String")
+                                                           :placeholder  (str "file:/"
+                                                                              (tru "Users/camsaul/bird_sightings/toucans"))
                                                            :required     true}])
           :humanize-connection-error-message (u/drop-first-arg humanize-connection-error-message)
           :process-query-in-context          (u/drop-first-arg process-query-in-context)
diff --git a/src/metabase/driver/mongo.clj b/src/metabase/driver/mongo.clj
index 5296b2d0db7ecbe4504a4c088a461a802c6258e4..2eadd139720e03f5c455e2e7a708b59e007d721e 100644
--- a/src/metabase/driver/mongo.clj
+++ b/src/metabase/driver/mongo.clj
@@ -15,6 +15,7 @@
              [command :as cmd]
              [conversion :as conv]
              [db :as mdb]]
+            [metabase.util.i18n :refer [tru]]
             [schema.core :as s]
             [toucan.db :as db])
   (:import com.mongodb.DB))
@@ -23,9 +24,9 @@
 
 (defn- can-connect? [details]
   (with-mongo-connection [^DB conn, details]
-    (= (-> (cmd/db-stats conn)
-           (conv/from-db-object :keywordize)
-           :ok)
+    (= (float (-> (cmd/db-stats conn)
+                  (conv/from-db-object :keywordize)
+                  :ok))
        1.0)))
 
 (defn- humanize-connection-error-message [message]
@@ -66,14 +67,17 @@
   (cond
     ;; 1. url?
     (and (string? field-value)
-         (u/url? field-value)) :type/URL
+         (u/url? field-value))
+    :type/URL
+
     ;; 2. json?
     (and (string? field-value)
          (or (.startsWith "{" field-value)
-             (.startsWith "[" field-value))) (when-let [j (u/try-apply json/parse-string field-value)]
-                                               (when (or (map? j)
-                                                         (sequential? j))
-                                                 :type/SerializedJSON))))
+             (.startsWith "[" field-value)))
+    (when-let [j (u/ignore-exceptions (json/parse-string field-value))]
+      (when (or (map? j)
+                (sequential? j))
+        :type/SerializedJSON))))
 
 (defn- find-nested-fields [field-value nested-fields]
   (loop [[k & more-keys] (keys field-value)
@@ -113,7 +117,7 @@
 (defn- describe-table-field [field-kw field-info]
   (let [most-common-object-type (most-common-object-type (vec (:types field-info)))]
     (cond-> {:name          (name field-kw)
-             :database-type (.getName most-common-object-type)
+             :database-type (some-> most-common-object-type .getName)
              :base-type     (driver/class->base-type most-common-object-type)}
       (= :_id field-kw)           (assoc :pk? true)
       (:special-types field-info) (assoc :special-type (->> (vec (:special-types field-info))
@@ -170,34 +174,19 @@
           :describe-database                 (u/drop-first-arg describe-database)
           :describe-table                    (u/drop-first-arg describe-table)
           :details-fields                    (constantly (ssh/with-tunnel-config
-                                                           [{:name         "host"
-                                                             :display-name "Host"
-                                                             :default      "localhost"}
-                                                            {:name         "port"
-                                                             :display-name "Port"
-                                                             :type         :integer
-                                                             :default      27017}
-                                                            {:name         "dbname"
-                                                             :display-name "Database name"
-                                                             :placeholder  "carrierPigeonDeliveries"
-                                                             :required     true}
-                                                            {:name         "user"
-                                                             :display-name "Database username"
-                                                             :placeholder  "What username do you use to login to the database?"}
-                                                            {:name         "pass"
-                                                             :display-name "Database password"
-                                                             :type         :password
-                                                             :placeholder  "******"}
+                                                           [driver/default-host-details
+                                                            (assoc driver/default-port-details :default 27017)
+                                                            (assoc driver/default-dbname-details
+                                                              :placeholder  (tru "carrierPigeonDeliveries"))
+                                                            driver/default-user-details
+                                                            (assoc driver/default-password-details :name "pass")
                                                             {:name         "authdb"
-                                                             :display-name "Authentication Database"
-                                                             :placeholder  "Optional database to use when authenticating"}
-                                                            {:name         "ssl"
-                                                             :display-name "Use a secure connection (SSL)?"
-                                                             :type         :boolean
-                                                             :default      false}
-                                                            {:name         "additional-options"
-                                                             :display-name "Additional Mongo connection string options"
-                                                             :placeholder  "readPreference=nearest&replicaSet=test"}]))
+                                                             :display-name (tru "Authentication Database")
+                                                             :placeholder  (tru "Optional database to use when authenticating")}
+                                                            driver/default-ssl-details
+                                                            (assoc driver/default-additional-options-details
+                                                              :display-name (tru "Additional Mongo connection string options")
+                                                              :placeholder  "readPreference=nearest&replicaSet=test")]))
           :execute-query                     (u/drop-first-arg qp/execute-query)
           :features                          (constantly #{:basic-aggregations :nested-fields})
           :humanize-connection-error-message (u/drop-first-arg humanize-connection-error-message)
diff --git a/src/metabase/driver/mongo/query_processor.clj b/src/metabase/driver/mongo/query_processor.clj
index 9a267dcb10dea8a2c5248e51ac21ef316aa6e273..444774ddccc7a0eec2529d77938a212c71397de7 100644
--- a/src/metabase/driver/mongo/query_processor.clj
+++ b/src/metabase/driver/mongo/query_processor.clj
@@ -424,7 +424,7 @@
   (for [row results]
     (into {} (for [[k v] row]
                {k (if (and (map? v)
-                           (:___date v))
+                           (contains? v :___date))
                     (du/->Timestamp (:___date v) (TimeZone/getDefault))
                     v)}))))
 
diff --git a/src/metabase/driver/mysql.clj b/src/metabase/driver/mysql.clj
index b0e7e4a0d1fb852747026339e5addba811cf8bf2..cdda6f1dadf97b8d91fd53eae6ab799ae9b4e2af 100644
--- a/src/metabase/driver/mysql.clj
+++ b/src/metabase/driver/mysql.clj
@@ -124,11 +124,8 @@
   (when timezone-id
     (time/unparse (.withZone timezone-offset-formatter (t/time-zone-for-id timezone-id)) date-time)))
 
-(defn- ^String system-timezone->offset-str
-  "Get the system/JVM timezone offset specified at `date-time`. The time is needed here as offsets can change for a
-  given timezone based on the time of year (i.e. daylight savings time)."
-  [date-time]
-  (timezone-id->offset-str (.getID (TimeZone/getDefault)) date-time))
+(def ^:private ^TimeZone utc   (TimeZone/getTimeZone "UTC"))
+(def ^:private utc-hsql-offset (hx/literal "+00:00"))
 
 (s/defn ^:private create-hsql-for-date
   "Returns an HoneySQL structure representing the date for MySQL. If there's a report timezone, we need to ensure the
@@ -138,14 +135,14 @@
   [date-obj :- java.util.Date
    date-literal-or-string :- (s/either s/Str Literal)]
   (let [date-as-dt                 (tcoerce/from-date date-obj)
-        report-timezone-offset-str (timezone-id->offset-str (driver/report-timezone) date-as-dt)
-        system-timezone-offset-str (system-timezone->offset-str date-as-dt)]
+        report-timezone-offset-str (timezone-id->offset-str (driver/report-timezone) date-as-dt)]
     (if (and report-timezone-offset-str
-             (not= report-timezone-offset-str system-timezone-offset-str))
+             (not (.hasSameRules utc (TimeZone/getTimeZone (driver/report-timezone)))))
       ;; if we have a report timezone we want to generate SQL like convert_tz('2004-01-01T12:00:00','-8:00','-2:00')
-      ;; to convert our timestamp from system timezone -> report timezone.
+      ;; to convert our timestamp from the UTC timezone -> report timezone. Note `date-object-literal` is assumed to be
+      ;; in UTC as `du/format-date` is being used which defaults to UTC.
       ;; See https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_convert-tz
-      ;; (We're using raw offsets for the JVM timezone instead of the timezone ID because we can't be 100% sure that
+      ;; (We're using raw offsets for the JVM/report timezone instead of the timezone ID because we can't be 100% sure that
       ;; MySQL will accept either of our timezone IDs as valid.)
       ;;
       ;; Note there's a small chance that report timezone will never be set on the MySQL connection, if attempting to
@@ -157,7 +154,7 @@
       ;; was the previous behavior.
       (hsql/call :convert_tz
         date-literal-or-string
-        (hx/literal system-timezone-offset-str)
+        utc-hsql-offset
         (hx/literal report-timezone-offset-str))
       ;; otherwise if we don't have a report timezone we can continue to pass the object as-is, e.g. as a prepared
       ;; statement param
@@ -178,7 +175,7 @@
                             hx/->date
                             (hsql/format :quoting (sql/quote-style (MySQLDriver.)))
                             first)
-                        [(du/format-date :date-hour-minute-second-ms date)])))
+                        [date-str])))
 
 (defmethod sqlqp/->honeysql [MySQLDriver Time]
   [_ time-value]
@@ -269,28 +266,13 @@
    (sql/IDriverSQLDefaultsMixin)
    {:date-interval                     (u/drop-first-arg date-interval)
     :details-fields                    (constantly (ssh/with-tunnel-config
-                                                     [{:name         "host"
-                                                       :display-name "Host"
-                                                       :default      "localhost"}
-                                                      {:name         "port"
-                                                       :display-name "Port"
-                                                       :type         :integer
-                                                       :default      3306}
-                                                      {:name         "dbname"
-                                                       :display-name "Database name"
-                                                       :placeholder  "birds_of_the_word"
-                                                       :required     true}
-                                                      {:name         "user"
-                                                       :display-name "Database username"
-                                                       :placeholder  "What username do you use to login to the database?"
-                                                       :required     true}
-                                                      {:name         "password"
-                                                       :display-name "Database password"
-                                                       :type         :password
-                                                       :placeholder  "*******"}
-                                                      {:name         "additional-options"
-                                                       :display-name "Additional JDBC connection string options"
-                                                       :placeholder  "tinyInt1isBit=false"}]))
+                                                     [driver/default-host-details
+                                                      (assoc driver/default-port-details :default 3306)
+                                                      driver/default-dbname-details
+                                                      driver/default-user-details
+                                                      driver/default-password-details
+                                                      (assoc driver/default-additional-options-details
+                                                        :placeholder  "tinyInt1isBit=false")]))
     :humanize-connection-error-message (u/drop-first-arg humanize-connection-error-message)
     :current-db-time                   (driver/make-current-db-time-fn mysql-db-time-query mysql-date-formatters)
     :features                          (fn [this]
diff --git a/src/metabase/driver/oracle.clj b/src/metabase/driver/oracle.clj
index d003766f75e94a99a3eb8ce663754ab3313df61b..76d9ba1574a40dd6050b2ea5f6f8753623d26098 100644
--- a/src/metabase/driver/oracle.clj
+++ b/src/metabase/driver/oracle.clj
@@ -12,6 +12,7 @@
             [metabase.driver.generic-sql.query-processor :as sqlqp]
             [metabase.util
              [honeysql-extensions :as hx]
+             [i18n :refer [tru]]
              [ssh :as ssh]]))
 
 (defrecord OracleDriver []
@@ -265,27 +266,18 @@
          {:can-connect?                      (u/drop-first-arg can-connect?)
           :date-interval                     (u/drop-first-arg date-interval)
           :details-fields                    (constantly (ssh/with-tunnel-config
-                                                           [{:name         "host"
-                                                             :display-name "Host"
-                                                             :default      "localhost"}
-                                                            {:name         "port"
-                                                             :display-name "Port"
-                                                             :type         :integer
-                                                             :default      1521}
+                                                           [driver/default-host-details
+                                                            (assoc driver/default-port-details :default 1521)
                                                             {:name         "sid"
-                                                             :display-name "Oracle system ID (SID)"
-                                                             :placeholder  "Usually something like ORCL or XE. Optional if using service name"}
+                                                             :display-name (tru "Oracle system ID (SID)")
+                                                             :placeholder  (str (tru "Usually something like ORCL or XE.")
+                                                                                " "
+                                                                                (tru "Optional if using service name"))}
                                                             {:name         "service-name"
-                                                             :display-name "Oracle service name"
-                                                             :placeholder  "Optional TNS alias"}
-                                                            {:name         "user"
-                                                             :display-name "Database username"
-                                                             :placeholder  "What username do you use to login to the database?"
-                                                             :required     true}
-                                                            {:name         "password"
-                                                             :display-name "Database password"
-                                                             :type         :password
-                                                             :placeholder  "*******"}]))
+                                                             :display-name (tru "Oracle service name")
+                                                             :placeholder  (tru "Optional TNS alias")}
+                                                            driver/default-user-details
+                                                            driver/default-password-details]))
           :execute-query                     (comp remove-rownum-column sqlqp/execute-query)
           :humanize-connection-error-message (u/drop-first-arg humanize-connection-error-message)
           :current-db-time                   (driver/make-current-db-time-fn oracle-db-time-query oracle-date-formatters)})
diff --git a/src/metabase/driver/postgres.clj b/src/metabase/driver/postgres.clj
index c8dec1a7990993b2463bbab9dcb9e7f70cb03884..ab31364f5018edb895ac2ef74fd826a3fa75dec7 100644
--- a/src/metabase/driver/postgres.clj
+++ b/src/metabase/driver/postgres.clj
@@ -251,32 +251,14 @@
           :date-interval                     (u/drop-first-arg date-interval)
           :describe-table                    describe-table
           :details-fields                    (constantly (ssh/with-tunnel-config
-                                                           [{:name         "host"
-                                                             :display-name "Host"
-                                                             :default      "localhost"}
-                                                            {:name         "port"
-                                                             :display-name "Port"
-                                                             :type         :integer
-                                                             :default      5432}
-                                                            {:name         "dbname"
-                                                             :display-name "Database name"
-                                                             :placeholder  "birds_of_the_word"
-                                                             :required     true}
-                                                            {:name         "user"
-                                                             :display-name "Database username"
-                                                             :placeholder  "What username do you use to login to the database?"
-                                                             :required     true}
-                                                            {:name         "password"
-                                                             :display-name "Database password"
-                                                             :type         :password
-                                                             :placeholder  "*******"}
-                                                            {:name         "ssl"
-                                                             :display-name "Use a secure connection (SSL)?"
-                                                             :type         :boolean
-                                                             :default      false}
-                                                            {:name         "additional-options"
-                                                             :display-name "Additional JDBC connection string options"
-                                                             :placeholder  "prepareThreshold=0"}]))
+                                                           [driver/default-host-details
+                                                            (assoc driver/default-port-details :default 5432)
+                                                            driver/default-dbname-details
+                                                            driver/default-user-details
+                                                            driver/default-password-details
+                                                            driver/default-ssl-details
+                                                            (assoc driver/default-additional-options-details
+                                                              :placeholder "prepareThreshold=0")]))
           :humanize-connection-error-message (u/drop-first-arg humanize-connection-error-message)})
 
   sql/ISQLDriver PostgresISQLDriverMixin)
diff --git a/src/metabase/driver/presto.clj b/src/metabase/driver/presto.clj
index f379345132bcf1b1559c11d70d970fc7da29bad4..0d904ca291f4a1c2a0bd39bbb024f2bf2e2e641d 100644
--- a/src/metabase/driver/presto.clj
+++ b/src/metabase/driver/presto.clj
@@ -22,10 +22,11 @@
             [metabase.util
              [date :as du]
              [honeysql-extensions :as hx]
+             [i18n :refer [tru]]
              [ssh :as ssh]])
   (:import java.sql.Time
            java.util.Date
-           [metabase.query_processor.interface TimeValue]))
+           metabase.query_processor.interface.TimeValue))
 
 (defrecord PrestoDriver []
   :load-ns true
@@ -326,29 +327,14 @@
           :describe-table                    (u/drop-first-arg describe-table)
           :describe-table-fks                (constantly nil) ; no FKs in Presto
           :details-fields                    (constantly (ssh/with-tunnel-config
-                                                           [{:name         "host"
-                                                             :display-name "Host"
-                                                             :default      "localhost"}
-                                                            {:name         "port"
-                                                             :display-name "Port"
-                                                             :type         :integer
-                                                             :default      8080}
-                                                            {:name         "catalog"
-                                                             :display-name "Database name"
-                                                             :placeholder  "hive"
-                                                             :required     true}
-                                                            {:name         "user"
-                                                             :display-name "Database username"
-                                                             :placeholder  "What username do you use to login to the database"
-                                                             :default      "metabase"}
-                                                            {:name         "password"
-                                                             :display-name "Database password"
-                                                             :type         :password
-                                                             :placeholder  "*******"}
-                                                            {:name         "ssl"
-                                                             :display-name "Use a secure connection (SSL)?"
-                                                             :type         :boolean
-                                                             :default      false}]))
+                                                           [driver/default-host-details
+                                                            (assoc driver/default-port-details :default 8080)
+                                                            (assoc driver/default-dbname-details
+                                                              :name         "catalog"
+                                                              :placeholder  (tru "hive"))
+                                                            driver/default-user-details
+                                                            driver/default-password-details
+                                                            driver/default-ssl-details]))
           :execute-query                     (u/drop-first-arg execute-query)
           :features                          (constantly (set/union #{:set-timezone
                                                                       :basic-aggregations
diff --git a/src/metabase/driver/redshift.clj b/src/metabase/driver/redshift.clj
index 3a8db69856ca7d6d68c4956e8b0e21ce252b1148..09a8416949a1806763f94999a9e118e0b76177c2 100644
--- a/src/metabase/driver/redshift.clj
+++ b/src/metabase/driver/redshift.clj
@@ -12,6 +12,7 @@
              [postgres :as postgres]]
             [metabase.util
              [honeysql-extensions :as hx]
+             [i18n :refer [tru]]
              [ssh :as ssh]]))
 
 (defn- connection-details->spec
@@ -82,27 +83,14 @@
          {:date-interval            (u/drop-first-arg date-interval)
           :describe-table-fks       (u/drop-first-arg describe-table-fks)
           :details-fields           (constantly (ssh/with-tunnel-config
-                                                  [{:name         "host"
-                                                    :display-name "Host"
-                                                    :placeholder  "my-cluster-name.abcd1234.us-east-1.redshift.amazonaws.com"
-                                                    :required     true}
-                                                   {:name         "port"
-                                                    :display-name "Port"
-                                                    :type         :integer
-                                                    :default      5439}
-                                                   {:name         "db"
-                                                    :display-name "Database name"
-                                                    :placeholder  "toucan_sightings"
-                                                    :required     true}
-                                                   {:name         "user"
-                                                    :display-name "Database username"
-                                                    :placeholder  "cam"
-                                                    :required     true}
-                                                   {:name         "password"
-                                                    :display-name "Database user password"
-                                                    :type         :password
-                                                    :placeholder  "*******"
-                                                    :required     true}]))
+                                                  [(assoc driver/default-host-details
+                                                     :placeholder (tru "my-cluster-name.abcd1234.us-east-1.redshift.amazonaws.com"))
+                                                   (assoc driver/default-port-details :default 5439)
+                                                   (assoc driver/default-dbname-details
+                                                     :name         "db"
+                                                     :placeholder  (tru "toucan_sightings"))
+                                                   driver/default-user-details
+                                                   driver/default-password-details]))
           :format-custom-field-name (u/drop-first-arg str/lower-case)
           :current-db-time          (driver/make-current-db-time-fn redshift-db-time-query redshift-date-formatters)})
 
diff --git a/src/metabase/driver/sparksql.clj b/src/metabase/driver/sparksql.clj
index 1b797de9d3c41a167ecd9a3aeac04cc1f84092b3..30287b96a4a398c4f6b6f5acd6e933096b1cb3cc 100644
--- a/src/metabase/driver/sparksql.clj
+++ b/src/metabase/driver/sparksql.clj
@@ -15,9 +15,11 @@
              [generic-sql :as sql]
              [hive-like :as hive-like]]
             [metabase.driver.generic-sql.query-processor :as sqlqp]
+            [metabase.models.table :refer [Table]]
             [metabase.query-processor.util :as qputil]
-            [metabase.util.honeysql-extensions :as hx]
-            [puppetlabs.i18n.core :refer [trs]])
+            [metabase.util
+             [honeysql-extensions :as hx]
+             [i18n :refer [trs tru]]])
   (:import clojure.lang.Reflector
            java.sql.DriverManager
            metabase.query_processor.interface.Field))
@@ -32,9 +34,13 @@
 
 (def ^:private source-table-alias "t1")
 
+(defn- find-source-table [query]
+  (first (qputil/postwalk-collect #(instance? (type Table) %)
+                                  identity
+                                  query)))
+
 (defn- resolve-table-alias [{:keys [schema-name table-name special-type field-name] :as field}]
-  (let [source-table (or (get-in sqlqp/*query* [:query :source-table])
-                         (get-in sqlqp/*query* [:query :source-query :source-table]))]
+  (let [source-table (find-source-table sqlqp/*query*)]
     (if (and (= schema-name (:schema source-table))
              (= table-name (:name source-table)))
       (-> (assoc field :schema-name nil)
@@ -49,7 +55,7 @@
 
 (defmethod  sqlqp/->honeysql [SparkSQLDriver Field]
   [driver field-before-aliasing]
-  (let [{:keys [schema-name table-name special-type field-name]} (resolve-table-alias field-before-aliasing)
+  (let [{:keys [schema-name table-name special-type field-name] :as foo} (resolve-table-alias field-before-aliasing)
         field (keyword (hx/qualify-and-escape-dots schema-name table-name field-name))]
     (cond
       (isa? special-type :type/UNIXTimestampSeconds)      (sql/unix-timestamp->timestamp driver field :seconds)
@@ -163,26 +169,14 @@
           :describe-database  describe-database
           :describe-table     describe-table
           :describe-table-fks (constantly #{})
-          :details-fields     (constantly [{:name         "host"
-                                            :display-name "Host"
-                                            :default      "localhost"}
-                                           {:name         "port"
-                                            :display-name "Port"
-                                            :type         :integer
-                                            :default      10000}
-                                           {:name         "dbname"
-                                            :display-name "Database name"
-                                            :placeholder  "default"}
-                                           {:name         "user"
-                                            :display-name "Database username"
-                                            :placeholder  "What username do you use to login to the database?"}
-                                           {:name         "password"
-                                            :display-name "Database password"
-                                            :type         :password
-                                            :placeholder  "*******"}
-                                           {:name         "jdbc-flags"
-                                            :display-name "Additional JDBC settings, appended to the connection string"
-                                            :placeholder  ";transportMode=http"}])
+          :details-fields     (constantly [driver/default-host-details
+                                           (assoc driver/default-port-details :default 10000)
+                                           (assoc driver/default-dbname-details :placeholder (tru "default"))
+                                           driver/default-user-details
+                                           driver/default-password-details
+                                           (assoc driver/default-additional-options-details
+                                             :name        "jdbc-flags"
+                                             :placeholder ";transportMode=http")])
           :execute-query      execute-query
           :features           (constantly (set/union #{:basic-aggregations
                                                        :binning
diff --git a/src/metabase/driver/sqlite.clj b/src/metabase/driver/sqlite.clj
index 85ecc89455d11c6df080bc1143898cc630038496..a15923faeb3345eace80985a5f299be8b6767ffb 100644
--- a/src/metabase/driver/sqlite.clj
+++ b/src/metabase/driver/sqlite.clj
@@ -14,7 +14,8 @@
             [metabase.driver.generic-sql.query-processor :as sqlqp]
             [metabase.util
              [date :as du]
-             [honeysql-extensions :as hx]]
+             [honeysql-extensions :as hx]
+             [i18n :refer [tru]]]
             [schema.core :as s])
   (:import [java.sql Time Timestamp]))
 
@@ -178,8 +179,8 @@
    (sql/IDriverSQLDefaultsMixin)
    {:date-interval   (u/drop-first-arg date-interval)
     :details-fields  (constantly [{:name         "db"
-                                   :display-name "Filename"
-                                   :placeholder  "/home/camsaul/toucan_sightings.sqlite 😋"
+                                   :display-name (tru "Filename")
+                                   :placeholder  (tru "/home/camsaul/toucan_sightings.sqlite 😋")
                                    :required     true}])
     :features        (fn [this]
                        (-> (sql/features this)
diff --git a/src/metabase/driver/sqlserver.clj b/src/metabase/driver/sqlserver.clj
index 726c46c14d50cc588ee6aa4a39f4e04c8e6c94a6..348e28c1a6358472e2bc6d2394bd550326c1fe39 100644
--- a/src/metabase/driver/sqlserver.clj
+++ b/src/metabase/driver/sqlserver.clj
@@ -9,6 +9,7 @@
             [metabase.driver.generic-sql.query-processor :as sqlqp]
             [metabase.util
              [honeysql-extensions :as hx]
+             [i18n :refer [tru]]
              [ssh :as ssh]])
   (:import java.sql.Time))
 
@@ -63,7 +64,7 @@
   "Build the connection spec for a SQL Server database from the DETAILS set in the admin panel.
    Check out the full list of options here: `https://technet.microsoft.com/en-us/library/ms378988(v=sql.105).aspx`"
   [{:keys [user password db host port instance domain ssl]
-    :or   {user "dbuser", password "dbpassword", db "", host "localhost", port 1433}
+    :or   {user "dbuser", password "dbpassword", db "", host "localhost"}
     :as   details}]
   (-> {:applicationName config/mb-app-id-string
        :classname       "com.microsoft.sqlserver.jdbc.SQLServerDriver"
@@ -75,7 +76,6 @@
        ;; instead of part of the `:subname` is preferable because they support things like passwords with special
        ;; characters)
        :database        db
-       :port            port
        :password        password
        ;; Wait up to 10 seconds for connection success. If we get no response by then, consider the connection failed
        :loginTimeout    10
@@ -86,6 +86,9 @@
                              user)
        :instanceName    instance
        :encrypt         (boolean ssl)}
+      ;; only include `port` if it is specified; leave out for dynamic port: see
+      ;; https://github.com/metabase/metabase/issues/7597
+      (merge (when port {:port port}))
       (sql/handle-additional-options details, :seperator-style :semicolon)))
 
 
@@ -174,38 +177,22 @@
    (sql/IDriverSQLDefaultsMixin)
    {:date-interval  (u/drop-first-arg date-interval)
     :details-fields (constantly (ssh/with-tunnel-config
-                                  [{:name         "host"
-                                    :display-name "Host"
-                                    :default      "localhost"}
-                                   {:name         "port"
-                                    :display-name "Port"
-                                    :type         :integer
-                                    :default      1433}
-                                   {:name         "db"
-                                    :display-name "Database name"
-                                    :placeholder  "BirdsOfTheWorld"
-                                    :required     true}
+                                  [driver/default-host-details
+                                   (assoc driver/default-port-details :placeholder "1433")
+                                   (assoc driver/default-dbname-details
+                                     :name         "db"
+                                     :placeholder  (tru "BirdsOfTheWorld"))
                                    {:name         "instance"
-                                    :display-name "Database instance name"
-                                    :placeholder  "N/A"}
+                                    :display-name (tru "Database instance name")
+                                    :placeholder  (tru "N/A")}
                                    {:name         "domain"
-                                    :display-name "Windows domain"
-                                    :placeholder  "N/A"}
-                                   {:name         "user"
-                                    :display-name "Database username"
-                                    :placeholder  "What username do you use to login to the database?"
-                                    :required     true}
-                                   {:name         "password"
-                                    :display-name "Database password"
-                                    :type         :password
-                                    :placeholder  "*******"}
-                                   {:name         "ssl"
-                                    :display-name "Use a secure connection (SSL)?"
-                                    :type         :boolean
-                                    :default      false}
-                                   {:name         "additional-options"
-                                    :display-name "Additional JDBC connection string options"
-                                    :placeholder  "trustServerCertificate=false"}]))
+                                    :display-name (tru "Windows domain")
+                                    :placeholder  (tru "N/A")}
+                                   driver/default-user-details
+                                   driver/default-password-details
+                                   driver/default-ssl-details
+                                   (assoc driver/default-additional-options-details
+                                     :placeholder  "trustServerCertificate=false")]))
     :current-db-time (driver/make-current-db-time-fn sqlserver-db-time-query sqlserver-date-formatters)
     :features        (fn [this]
                        ;; SQLServer LIKE clauses are case-sensitive or not based on whether the collation of the
diff --git a/src/metabase/driver/vertica.clj b/src/metabase/driver/vertica.clj
index befc4237338206ad8bf6cf608c6164606135e605..4b5a54c45bb5894e74a3fea7e04d3e6c4bdd9fa7 100644
--- a/src/metabase/driver/vertica.clj
+++ b/src/metabase/driver/vertica.clj
@@ -121,28 +121,13 @@
          {:date-interval     (u/drop-first-arg date-interval)
           :describe-database describe-database
           :details-fields    (constantly (ssh/with-tunnel-config
-                                           [{:name         "host"
-                                             :display-name "Host"
-                                             :default      "localhost"}
-                                            {:name         "port"
-                                             :display-name "Port"
-                                             :type         :integer
-                                             :default      5433}
-                                            {:name         "dbname"
-                                             :display-name "Database name"
-                                             :placeholder  "birds_of_the_word"
-                                             :required     true}
-                                            {:name         "user"
-                                             :display-name "Database username"
-                                             :placeholder  "What username do you use to login to the database?"
-                                             :required     true}
-                                            {:name         "password"
-                                             :display-name "Database password"
-                                             :type         :password
-                                             :placeholder  "*******"}
-                                            {:name         "additional-options"
-                                             :display-name "Additional JDBC connection string options"
-                                             :placeholder  "ConnectionLoadBalance=1"}]))
+                                           [driver/default-host-details
+                                            (assoc driver/default-port-details :default 5433)
+                                            driver/default-dbname-details
+                                            driver/default-user-details
+                                            driver/default-password-details
+                                            (assoc driver/default-additional-options-details
+                                              :placeholder "ConnectionLoadBalance=1")]))
           :current-db-time   (driver/make-current-db-time-fn vertica-db-time-query vertica-date-formatters)})
   sql/ISQLDriver
   (merge (sql/ISQLDriverDefaultsMixin)
diff --git a/src/metabase/email.clj b/src/metabase/email.clj
index bed7e78cdefe3a68612ff4a26b2144481ffcc931..829f25d2d07a85b5c93cb1181731dc5a7e83e593 100644
--- a/src/metabase/email.clj
+++ b/src/metabase/email.clj
@@ -2,11 +2,12 @@
   (:require [clojure.tools.logging :as log]
             [metabase.models.setting :as setting :refer [defsetting]]
             [metabase.util :as u]
-            [metabase.util.schema :as su]
+            [metabase.util
+             [i18n :refer [tru trs]]
+             [schema :as su]]
             [postal
              [core :as postal]
              [support :refer [make-props]]]
-            [puppetlabs.i18n.core :refer [tru trs]]
             [schema.core :as s])
   (:import javax.mail.Session))
 
@@ -72,7 +73,7 @@
   {:style/indent 0}
   [{:keys [subject recipients message-type message]} :- EmailMessage]
   (when-not (email-smtp-host)
-    (let [^String msg (tru "SMTP host is not set.")]
+    (let [^String msg (str (tru "SMTP host is not set."))]
       (throw (Exception. msg))))
   ;; Now send the email
   (send-email! (smtp-settings)
diff --git a/src/metabase/events.clj b/src/metabase/events.clj
index 28e245f5b2f51a7ec29fa2fa4ef552f88184e2e1..11613ae0531e4acf49ef59b94100d8843c0b6219 100644
--- a/src/metabase/events.clj
+++ b/src/metabase/events.clj
@@ -55,6 +55,7 @@
 
 (defn publish-event!
   "Publish an item into the events stream. Returns the published item to allow for chaining."
+  {:style/indent 1}
   [topic event-item]
   {:pre [(keyword topic)]}
   (async/go (async/>! events-channel {:topic (keyword topic), :item event-item}))
diff --git a/src/metabase/events/activity_feed.clj b/src/metabase/events/activity_feed.clj
index 5c2ca9550adc4a98077ff7e5d868cb3899b3318e..c7cd977d409b738d17db3eb59d173ff2175503af 100644
--- a/src/metabase/events/activity_feed.clj
+++ b/src/metabase/events/activity_feed.clj
@@ -10,7 +10,6 @@
              [card :refer [Card]]
              [dashboard :refer [Dashboard]]
              [table :as table]]
-            [metabase.query-processor.util :as qputil]
             [toucan.db :as db]))
 
 (def activity-feed-topics
@@ -44,10 +43,10 @@
 
 (defn- inner-query->source-table-id
   "Recurse through INNER-QUERY source-queries as needed until we can return the ID of this query's source-table."
-  [inner-query]
-  (or (when-let [source-table (qputil/get-normalized inner-query :source-table)]
+  [{:keys [source-table source-query]}]
+  (or (when source-table
         (u/get-id source-table))
-      (when-let [source-query (qputil/get-normalized inner-query :source-query)]
+      (when source-query
         (recur source-query))))
 
 (defn- process-card-activity! [topic object]
diff --git a/src/metabase/integrations/ldap.clj b/src/metabase/integrations/ldap.clj
index 27ba7f9cefbb92c18d28fd65708282b066d8e993..e2a9992b8edd4996e6b0b19b2ab280969d7a1bc2 100644
--- a/src/metabase/integrations/ldap.clj
+++ b/src/metabase/integrations/ldap.clj
@@ -8,7 +8,7 @@
              [setting :as setting :refer [defsetting]]
              [user :as user :refer [User]]]
             [metabase.util :as u]
-            [puppetlabs.i18n.core :refer [tru]]
+            [metabase.util.i18n :refer [tru]]
             [toucan.db :as db])
   (:import [com.unboundid.ldap.sdk LDAPConnectionPool LDAPException]))
 
diff --git a/src/metabase/integrations/slack.clj b/src/metabase/integrations/slack.clj
index 9d130ec3ea48fd1f23ad549b8e52e1e881f37aac..97df78f092a442ec7b71a0ec5bdb8d8b0459303c 100644
--- a/src/metabase/integrations/slack.clj
+++ b/src/metabase/integrations/slack.clj
@@ -3,7 +3,7 @@
             [clj-http.client :as http]
             [clojure.tools.logging :as log]
             [metabase.models.setting :as setting :refer [defsetting]]
-            [puppetlabs.i18n.core :refer [tru]]
+            [metabase.util.i18n :refer [tru]]
             [metabase.util :as u]))
 
 ;; Define a setting which captures our Slack api token
diff --git a/src/metabase/mbql/normalize.clj b/src/metabase/mbql/normalize.clj
new file mode 100644
index 0000000000000000000000000000000000000000..313f6cba0c4f96c7212cda7ba9a8e87e3148945d
--- /dev/null
+++ b/src/metabase/mbql/normalize.clj
@@ -0,0 +1,542 @@
+(ns metabase.mbql.normalize
+  "Logic for taking any sort of weird MBQL query and normalizing it into a standardized, canonical form. You can think
+  of this like taking any 'valid' MBQL query and rewriting it as-if it was written in perfect up-to-date MBQL in the
+  latest version. There are two main things done here, done as three separate steps:
+
+  #### NORMALIZING TOKENS
+
+  Converting all identifiers to lower-case, lisp-case keywords. e.g. `{\"SOURCE_TABLE\" 10}` becomes `{:source-table
+  10}`.
+
+  #### CANONICALIZING THE QUERY
+
+  Rewriting deprecated MBQL 95 syntax and other things that are still supported for backwards-compatibility in
+  canonical MBQL 98 syntax. For example `{:breakout [:count 10]}` becomes `{:breakout [[:count [:field-id 10]]]}`.
+
+  #### REMOVING EMPTY CLAUSES
+
+  Removing empty clauses like `{:aggregation nil}` or `{:breakout []}`.
+
+  Token normalization occurs first, followed by canonicalization, followed by removing empty clauses."
+  (:require [clojure.walk :as walk]
+            [medley.core :as m]
+            [metabase.mbql.util :as mbql.u]
+            [metabase.util :as u]
+            [metabase.util.i18n :refer [tru]]))
+
+(defn- mbql-clause?
+  "True if `x` is an MBQL clause (a sequence with a token as its first arg). (This is different from the implementation
+  in `mbql.u` because it also supports un-normalized clauses. You shouldn't need to use this outside of this
+  namespace.)"
+  [x]
+  (and (sequential? x)
+       ((some-fn keyword? string?) (first x))))
+
+(defn is-clause?
+  "If `x` an MBQL clause, and an instance of clauses defined by keyword(s) `k-or-ks`?
+
+    (is-clause? :count [:count 10])        ; -> true
+    (is-clause? #{:+ :- :* :/} [:+ 10 20]) ; -> true
+
+  (This is different from the implementation in `mbql.u` because it also supports un-normalized clauses. You shouldn't
+  need to use this outside of this namespace.)"
+  [k-or-ks x]
+  (and
+   (mbql-clause? x)
+   (let [clause-name (mbql.u/normalize-token (first x))]
+     (if (coll? k-or-ks)
+       ((set k-or-ks) clause-name)
+       (= k-or-ks clause-name)))))
+
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                                NORMALIZE TOKENS                                                |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+(declare normalize-tokens)
+
+(defn- normalize-expression-ref-tokens
+  "For expression references (`[:expression \"my_expression\"]`) keep the arg as is but make sure it is a string."
+  [_ expression-name]
+  [:expression (if (keyword? expression-name)
+                 (u/keyword->qualified-name expression-name)
+                 expression-name)])
+
+(defn- normalize-binning-strategy-tokens
+  "For `:binning-strategy` clauses (which wrap other Field clauses) normalize the strategy-name and recursively
+  normalize the Field it bins."
+  ([_ field strategy-name]
+   [:binning-strategy (normalize-tokens field :ignore-path) (mbql.u/normalize-token strategy-name)])
+  ([_ field strategy-name strategy-param]
+   (conj (normalize-binning-strategy-tokens nil field strategy-name)
+         strategy-param)))
+
+(defn- normalize-field-literal-tokens
+  "Similarly, for Field literals, keep the arg as-is, but make sure it is a string."
+  [_ field-name field-type]
+  [:field-literal
+   (if (keyword? field-name)
+     (u/keyword->qualified-name field-name)
+     field-name)
+   (keyword field-type)])
+
+(defn- normalize-datetime-field-tokens
+  "Datetime fields look like `[:datetime-field <field> <unit>]` or `[:datetime-field <field> :as <unit>]`; normalize the
+  unit, and `:as` (if present) tokens, and the Field."
+  ([_ field unit]
+   [:datetime-field (normalize-tokens field :ignore-path) (mbql.u/normalize-token unit)])
+  ([_ field _ unit]
+   [:datetime-field (normalize-tokens field :ignore-path) :as (mbql.u/normalize-token unit)]))
+
+(defn- normalize-time-interval-tokens
+  "`time-interval`'s `unit` should get normalized, and `amount` if it's not an integer."
+  ([_ field amount unit]
+   [:time-interval
+    (normalize-tokens field :ignore-path)
+    (if (integer? amount)
+      amount
+      (mbql.u/normalize-token amount))
+    (mbql.u/normalize-token unit)])
+  ([_ field amount unit options]
+   (conj (normalize-time-interval-tokens nil field amount unit) (normalize-tokens options :ignore-path))))
+
+(defn- normalize-relative-datetime-tokens
+  "Normalize a `relative-datetime` clause. `relative-datetime` comes in two flavors:
+
+     [:relative-datetime :current]
+     [:relative-datetime -10 :day] ; amount & unit"
+  ([_ _]
+   [:relative-datetime :current])
+  ([_ amount unit]
+   [:relative-datetime amount (mbql.u/normalize-token unit)]))
+
+(def ^:private mbql-clause->special-token-normalization-fn
+  "Special fns to handle token normalization for different MBQL clauses."
+  {:expression        normalize-expression-ref-tokens
+   :field-literal     normalize-field-literal-tokens
+   :datetime-field    normalize-datetime-field-tokens
+   :binning-strategy  normalize-binning-strategy-tokens
+   :time-interval     normalize-time-interval-tokens
+   :relative-datetime normalize-relative-datetime-tokens})
+
+(defn- normalize-mbql-clause-tokens
+  "MBQL clauses by default get just the clause name normalized (e.g. `[\"COUNT\" ...]` becomes `[:count ...]`) and the
+  args are left as-is. If we need to do something special on top of that implement a fn in
+  `mbql-clause->special-token-normalization-fn` above to handle the special normalization rules"
+  [[clause-name & args]]
+  (let [clause-name (mbql.u/normalize-token clause-name)]
+    (if-let [f (mbql-clause->special-token-normalization-fn clause-name)]
+      (apply f clause-name args)
+      (vec (cons clause-name (map #(normalize-tokens % :ignore-path) args))))))
+
+
+(defn- aggregation-subclause? [x]
+  (or (when ((some-fn keyword? string?) x)
+        (#{:avg :count :cum-count :distinct :stddev :sum :min :max :+ :- :/ :*} (mbql.u/normalize-token x)))
+      (when (mbql-clause? x)
+        (aggregation-subclause? (first x)))))
+
+(defn- normalize-ag-clause-tokens
+  "For old-style aggregations like `{:aggregation :count}` make sure we normalize the ag type (`:count`). Other wacky
+  clauses like `{:aggregation [:count :count]}` need to be handled as well :("
+  [ag-clause]
+  (cond
+    ;; something like {:aggregations :count}
+    ((some-fn keyword? string?) ag-clause)
+    (mbql.u/normalize-token ag-clause)
+
+    ;; named aggregation ([:named <ag> <name>])
+    (is-clause? :named ag-clause)
+    (let [[_ ag ag-name] ag-clause]
+      [:named (normalize-ag-clause-tokens ag) ag-name])
+
+    ;; something wack like {:aggregations [:count [:sum 10]]} or {:aggregations [:count :count]}
+    (when (mbql-clause? ag-clause)
+      (aggregation-subclause? (second ag-clause)))
+    (mapv normalize-ag-clause-tokens ag-clause)
+
+    :else
+    (normalize-tokens ag-clause :ignore-path)))
+
+(defn- normalize-expressions-tokens
+  "For expressions, we don't want to normalize the name of the expression; keep that as is, but make it a keyword;
+   normalize the definitions as normal."
+  [expressions-clause]
+  (into {} (for [[expression-name definition] expressions-clause]
+             [(keyword expression-name)
+              (normalize-tokens definition :ignore-path)])))
+
+(defn- normalize-order-by-tokens
+  "Normalize tokens in the order-by clause, which can have different syntax when using MBQL 95 or 98
+  rules (`[<field> :asc]` vs `[:asc <field>]`, for example)."
+  [clauses]
+  (vec (for [subclause clauses]
+         (if (mbql-clause? subclause)
+           ;; MBQL 98 [direction field] style: normalize as normal
+           (normalize-mbql-clause-tokens subclause)
+           ;; otherwise it's MBQL 95 [field direction] style: flip the args and *then* normalize the clause. And then
+           ;; flip it back to put it back the way we found it.
+           (reverse (normalize-mbql-clause-tokens (reverse subclause)))))))
+
+(defn- normalize-template-tags
+  "Normalize native-query template tags. Like `expressions` we want to preserve the original name rather than normalize
+  it."
+  [template-tags]
+  (into {} (for [[tag-name tag-def] template-tags]
+             [(u/keyword->qualified-name tag-name)
+              (let [tag-def (-> (normalize-tokens tag-def :ignore-path)
+                                (update :type mbql.u/normalize-token))]
+                (cond-> tag-def
+                  (:widget-type tag-def) (update :widget-type #(when % (mbql.u/normalize-token %)))))])))
+
+(defn- normalize-query-parameter [param]
+  (-> param
+      (update :type mbql.u/normalize-token)
+      (update :target #(normalize-tokens % :ignore-path))))
+
+(defn- normalize-source-query [{native? :native, :as source-query}]
+  (normalize-tokens source-query [(if native? :native :query)]))
+
+(def ^:private path->special-token-normalization-fn
+  "Map of special functions that should be used to perform token normalization for a given path. For example, the
+  `:expressions` key in an MBQL query should preserve the case of the expression names; this custom behavior is
+  defined below."
+  {:type       mbql.u/normalize-token
+   ;; don't normalize native queries
+   :native     {:query         identity
+                :template-tags normalize-template-tags}
+   :query      {:aggregation  normalize-ag-clause-tokens
+                :expressions  normalize-expressions-tokens
+                :order-by     normalize-order-by-tokens
+                :source-query normalize-source-query}
+   :parameters {::sequence normalize-query-parameter}
+   :context    #(some-> % mbql.u/normalize-token)})
+
+(defn normalize-tokens
+  "Recursively normalize tokens in `x`.
+
+  Every time this function recurses (thru a map value) it adds a new (normalized) key to key path, e.g. `path` will be
+  `[:query :order-by]` when we're in the MBQL order-by clause. If we need to handle these top-level clauses in special
+  ways add a function to `path->special-token-normalization-fn` above.
+
+  In some cases, dealing with the path isn't desirable, but we don't want to accidentally trigger normalization
+  functions (such as accidentally normalizing the `:type` key in something other than the top-level of the query), so
+  by convention please pass `:ignore-path` to avoid accidentally triggering path functions."
+  [x & [path]]
+  (let [path       (if (keyword? path)
+                     [path]
+                     (vec path))
+        special-fn (when (seq path)
+                     (get-in path->special-token-normalization-fn path))]
+    (cond
+      (fn? special-fn)
+      (special-fn x)
+
+      ;; Skip record types because this query is an `expanded` query, which is not going to play nice here. Hopefully we
+      ;; can remove expanded queries entirely soon.
+      (record? x)
+      x
+
+      ;; maps should just get the keys normalized and then recursively call normalize-tokens on the values.
+      ;; Each recursive call appends to the keypath above so we can handle top-level clauses in a special way if needed
+      (map? x)
+      (into {} (for [[k v] x
+                     :let  [k (mbql.u/normalize-token k)]]
+                 [k (normalize-tokens v (conj (vec path) k))]))
+
+      ;; MBQL clauses handled above because of special cases
+      (mbql-clause? x)
+      (normalize-mbql-clause-tokens x)
+
+      ;; for non-mbql sequential collections (probably something like the subclauses of :order-by or something like
+      ;; that) recurse on all the args.
+      ;;
+      ;; To signify that we're recursing into a sequential collection, this appends `::sequence` to path
+      (sequential? x)
+      (mapv #(normalize-tokens % (conj (vec path) ::sequence)) x)
+
+      :else
+      x)))
+
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                                  CANONICALIZE                                                  |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+(declare canonicalize-mbql-clauses)
+
+(defn- wrap-implicit-field-id
+  "Wrap raw integer Field IDs (i.e., those in an implicit 'field' position) in a `:field-id` clause if they're not
+  already. Done for MBQL 95 backwards-compatibility. e.g.:
+
+    {:filter [:= 10 20]} ; -> {:filter [:= [:field-id 10] 20]}"
+  [field]
+  (if (integer? field)
+    [:field-id field]
+    field))
+
+(defn- canonicalize-aggregation-subclause
+  "Remove `:rows` type aggregation (long-since deprecated; simpliy means no aggregation) if present, and wrap
+  `:field-ids` where appropriate."
+  [[ag-type :as ag-subclause]]
+  (cond
+    (= ag-type :rows)
+    nil
+
+    ;; For named aggregations (`[:named <ag> <name>]`) we want to leave as-is an just canonicalize the ag it names
+    (= ag-type :named)
+    (let [[_ ag ag-name] ag-subclause]
+      [:named (canonicalize-aggregation-subclause ag) ag-name])
+
+    (#{:+ :- :* :/} ag-type)
+    (vec
+     (cons
+      ag-type
+      ;; if args are also ag subclauses normalize those, but things like numbers are allowed too so leave them as-is
+      (for [arg (rest ag-subclause)]
+        (cond-> arg
+          (mbql-clause? arg) canonicalize-aggregation-subclause))))
+
+    ;; for metric and segment macros (e.g. [:metric <metric-id>]) do not wrap the metric/segment ID in a :field-id
+    ;; clause
+    (#{:metric :segment} ag-type)
+    ag-subclause
+
+    ;; something with an arg like [:sum [:field-id 41]]
+    (second ag-subclause)
+    (let [[_ field] ag-subclause]
+      [ag-type (wrap-implicit-field-id field)])
+
+    :else
+    ag-subclause))
+
+(defn- wrap-single-aggregations
+  "Convert old MBQL 95 single-aggregations like `{:aggregation :count}` or `{:aggregation [:count]}` to MBQL 98
+  multiple-aggregation syntax (e.g. `{:aggregation [[:count]]}`)."
+  [aggregations]
+  (cond
+    ;; something like {:aggregations :count} -- MBQL 95 single aggregation
+    (keyword? aggregations)
+    [[aggregations]]
+
+    ;; special-case: MBQL 98 multiple aggregations using unwrapped :count or :rows
+    ;; e.g. {:aggregations [:count [:sum 10]]} or {:aggregations [:count :count]}
+    (and (mbql-clause? aggregations)
+         (aggregation-subclause? (second aggregations))
+         (not (is-clause? #{:named :+ :- :* :/} aggregations)))
+    (reduce concat (map wrap-single-aggregations aggregations))
+
+    ;; something like {:aggregations [:sum 10]} -- MBQL 95 single aggregation
+    (mbql-clause? aggregations)
+    [(vec aggregations)]
+
+    :else
+    (vec aggregations)))
+
+(defn- canonicalize-aggregations
+  "Canonicalize subclauses (see above) and make sure `:aggregation` is a sequence of clauses instead of a single
+  clause."
+  [aggregations]
+  (->> (wrap-single-aggregations aggregations)
+       (map canonicalize-aggregation-subclause)
+       (filterv identity)))
+
+(defn- canonicalize-filter [[filter-name & args, :as filter-clause]]
+  (cond
+    ;; for other `and`/`or`/`not` compound filters, recurse on the arg(s), then simplify the whole thing
+    (#{:and :or :not} filter-name)
+    (mbql.u/simplify-compound-filter (vec (cons
+                                           filter-name
+                                           ;; we need to canonicalize any other mbql clauses that might show up in
+                                           ;; args like datetime-field here because simplify-compund-filter validates
+                                           ;; its output :(
+                                           (map (comp canonicalize-mbql-clauses canonicalize-filter)
+                                                args))))
+
+    ;; string filters should get the string implict filter options added if not specified explicitly
+    (#{:starts-with :ends-with :contains :does-not-contain} filter-name)
+    (let [[field arg options] args]
+      (cond-> [filter-name (wrap-implicit-field-id field) arg]
+        (seq options) (conj options)))
+
+    (= :inside filter-name)
+    (let [[field-1 field-2 & coordinates] args]
+      (vec
+       (concat
+        [:inside (wrap-implicit-field-id field-1) (wrap-implicit-field-id field-2)]
+        coordinates)))
+
+    ;; all the other filter types have an implict field ID for the first arg
+    ;; (e.g. [:= 10 20] gets canonicalized to [:= [:field-id 10] 20]
+    (#{:= :!= :< :<= :> :>= :is-null :not-null :between :inside :time-interval} filter-name)
+    (apply vector filter-name (wrap-implicit-field-id (first args)) (rest args))
+
+    ;; don't wrap segment IDs in `:field-id`
+    (= filter-name :segment)
+    filter-clause
+
+    :else
+    (throw (IllegalArgumentException. (str (tru "Illegal filter clause: {0}" filter-clause))))))
+
+(defn- canonicalize-order-by
+  "Make sure order by clauses like `[:asc 10]` get `:field-id` added where appropriate, e.g. `[:asc [:field-id 10]]`"
+  [order-by-clause]
+  (vec (for [subclause order-by-clause
+             :let      [[direction field-id] (if (#{:asc :desc :ascending :descending} (first subclause))
+                                               ;; normal [<direction> <field>] clause
+                                               subclause
+                                               ;; MBQL 95 reversed [<field> <direction>] clause
+                                               (reverse subclause))]]
+         [(case direction
+            :asc        :asc
+            :desc       :desc
+            ;; old MBQL 95 names
+            :ascending  :asc
+            :descending :desc)
+          (wrap-implicit-field-id field-id)])))
+
+(declare canonicalize-inner-mbql-query)
+
+(defn- canonicalize-source-query [{native? :native, :as source-query}]
+  (cond-> source-query
+    (not native?) canonicalize-inner-mbql-query))
+
+(defn- non-empty? [x]
+  (if (coll? x)
+    (seq x)
+    (some? x)))
+
+(defn- canonicalize-top-level-mbql-clauses
+  "Perform specific steps to canonicalize the various top-level clauses in an MBQL query."
+  [mbql-query]
+  (cond-> mbql-query
+    (non-empty? (:aggregation  mbql-query)) (update :aggregation  canonicalize-aggregations)
+    (non-empty? (:breakout     mbql-query)) (update :breakout     (partial mapv wrap-implicit-field-id))
+    (non-empty? (:fields       mbql-query)) (update :fields       (partial mapv wrap-implicit-field-id))
+    (non-empty? (:filter       mbql-query)) (update :filter       canonicalize-filter)
+    (non-empty? (:order-by     mbql-query)) (update :order-by     canonicalize-order-by)
+    (non-empty? (:source-query mbql-query)) (update :source-query canonicalize-source-query)))
+
+
+(def ^:private mbql-clause->canonicalization-fn
+  {:fk->
+   (fn [_ field-1 field-2]
+     [:fk-> (wrap-implicit-field-id field-1) (wrap-implicit-field-id field-2)])
+
+   :datetime-field
+   (fn
+     ([_ field unit]
+      [:datetime-field (wrap-implicit-field-id field) unit])
+     ([_ field _ unit]
+      [:datetime-field (wrap-implicit-field-id field) unit]))
+
+   :field-id
+   (fn [_ id]
+     ;; if someone is dumb and does something like [:field-id [:field-literal ...]] be nice and fix it for them.
+     (if (mbql-clause? id)
+       id
+       [:field-id id]))
+
+   :binning-strategy
+   (fn canonicalize-binning-strategy
+     ([_ field strategy-name]
+      [:binning-strategy (wrap-implicit-field-id field) strategy-name])
+     ([_ field strategy-name strategy-param]
+      (conj (canonicalize-binning-strategy nil field strategy-name) strategy-param)))})
+
+(defn- canonicalize-mbql-clauses
+  "Walk an `mbql-query` an canonicalize non-top-level clauses like `:fk->`."
+  [mbql-query]
+  (walk/prewalk
+   (fn [clause]
+     (if-not (mbql-clause? clause)
+       clause
+       (let [[clause-name & args] clause
+             f                    (mbql-clause->canonicalization-fn clause-name)]
+         (if f
+           (apply f clause)
+           clause))))
+   mbql-query))
+
+(def ^:private ^{:arglists '([query])} canonicalize-inner-mbql-query
+  (comp canonicalize-mbql-clauses canonicalize-top-level-mbql-clauses))
+
+(defn- canonicalize
+  "Canonicalize a query [MBQL query], rewriting the query as if you perfectly followed the recommended style guides for
+  writing MBQL. Does things like removes unneeded and empty clauses, converts older MBQL '95 syntax to MBQL '98, etc."
+  [outer-query]
+  (cond-> outer-query
+    (:query outer-query) (update :query canonicalize-inner-mbql-query)))
+
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                             REMOVING EMPTY CLAUSES                                             |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+;; TODO - can't we combine these functions into a single fn?
+
+(defn- non-empty-value?
+  "Is this 'value' in a query map considered non-empty (e.g., should we refrain from removing that key entirely?) e.g.:
+
+    {:aggregation nil} ; -> remove this, value is nil
+    {:filter []}       ; -> remove this, also empty
+    {:limit 100}       ; -> keep this"
+  [x]
+  (cond
+    ;; record types could be something like `driver` and should always be considered non-empty
+    (record? x)
+    true
+
+    (map? x)
+    (seq x)
+
+    ;; a sequence is considered non-empty if it has some non-nil values
+    (sequential? x)
+    (and (seq x)
+         (some some? x))
+
+    ;; any other value is considered non-empty if it is not nil
+    :else
+    (some? x)))
+
+(defn- remove-empty-clauses
+  "Remove any empty or `nil` clauses in a query."
+  [query]
+  (walk/postwalk
+   (fn [x]
+     (cond
+       ;; TODO - we can remove this part once we take out the `expand` namespace. This is here just to prevent
+       ;; double-expansion from barfing
+       (record? x)
+       x
+
+       (map? x)
+       (m/filter-vals non-empty-value? x)
+
+       :else
+       x))
+   query))
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                            PUTTING IT ALL TOGETHER                                             |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+;; TODO - should have this validate against the MBQL schema afterwards, right? Maybe once we get closer to making this
+;; all mergable
+(def ^{:arglists '([outer-query])} normalize
+  "Normalize the tokens in a Metabase query (i.e., make them all `lisp-case` keywords), rewrite deprecated clauses as
+  up-to-date MBQL 98, and remove empty clauses."
+  (comp remove-empty-clauses
+        canonicalize
+        normalize-tokens))
+
+(defn normalize-fragment
+  "Normalize just a specific fragment of a query, such as just the inner MBQL part or just a filter clause. `path` is
+  where this fragment would normally live in a full query.
+
+    (normalize-fragment [:query :filter] [\"=\" 100 200])
+    ;;-> [:= [:field-id 100] 200]"
+  [path x]
+  (if-not (seq path)
+    (normalize x)
+    (get (normalize-fragment (butlast path) {(last path) x}) (last path))))
diff --git a/src/metabase/mbql/schema.clj b/src/metabase/mbql/schema.clj
new file mode 100644
index 0000000000000000000000000000000000000000..0394770a66eef31bcc667402c93e06b8fd9695c1
--- /dev/null
+++ b/src/metabase/mbql/schema.clj
@@ -0,0 +1,477 @@
+(ns metabase.mbql.schema
+  "Schema for validating a *normalized* MBQL query. This is also the definitive grammar for MBQL, wow!"
+  (:refer-clojure :exclude [count distinct min max + - / * and or not = < > <= >=])
+  (:require [clojure
+             [core :as core]
+             [set :as set]]
+            [metabase.mbql.schema.helpers :refer [defclause is-clause? one-of]]
+            [metabase.util
+             [date :as du]
+             [schema :as su]]
+            [schema.core :as s]))
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                                  MBQL Clauses                                                  |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+;;; ------------------------------------------------- Datetime Stuff -------------------------------------------------
+
+(def ^:private DatetimeFieldUnit
+  (s/named
+   (apply s/enum #{:default :minute :minute-of-hour :hour :hour-of-day :day :day-of-week :day-of-month :day-of-year
+                   :week :week-of-year :month :month-of-year :quarter :quarter-of-year :year})
+   "datetime-unit"))
+
+(def ^:private RelativeDatetimeUnit
+  (s/named
+   (apply s/enum #{:minute :hour :day :week :month :quarter :year})
+   "relative-datetime-unit"))
+
+(def ^:private LiteralDatetimeString
+  "Schema for an MBQL datetime string literal, in ISO-8601 format."
+  (s/constrained su/NonBlankString du/date-string? "datetime-literal"))
+
+(defclause relative-datetime
+  n    (s/cond-pre (s/eq :current) s/Int)
+  unit (optional RelativeDatetimeUnit))
+
+(def ^:private DatetimeLiteral
+  "Schema for valid absoulute datetime literals."
+  (s/cond-pre
+   LiteralDatetimeString
+   java.sql.Date
+   java.util.Date))
+
+
+;;; ----------------------------------------------------- Fields -----------------------------------------------------
+
+;; Normal lowest-level Field clauses refer to a Field either by ID or by name
+
+(defclause field-id, id su/IntGreaterThanZero)
+
+(defclause field-literal, field-name su/NonBlankString, field-type su/FieldType)
+
+;; Both args in `[:fk-> <source-field> <dest-field>]` are implict `:field-ids`. E.g.
+;;
+;;   [:fk-> 10 20] --[NORMALIZE]--> [:fk-> [:field-id 10] [:field-id 20]]
+(defclause fk->
+  source-field (one-of field-id field-literal)
+  dest-field   (one-of field-id field-literal))
+
+;; Expression *references* refer to a something in the `:expressions` clause, e.g. something like `[:+ [:field-id 1]
+;; [:field-id 2]]`
+(defclause expression, expression-name su/NonBlankString)
+
+;; Datetime Field can wrap any of the lowest-level Field clauses or expression references, but not other
+;; datetime-field clauses, because that wouldn't make sense
+;;
+;; Field is an implicit Field ID
+(defclause datetime-field
+  field (one-of field-id field-literal fk-> expression)
+  unit  DatetimeFieldUnit)
+
+;; binning strategy can wrap any of the above clauses, but again, not another binning strategy clause
+(def ^:private BinningStrategyName
+  (s/enum :num-bins :bin-width :default))
+
+(defclause binning-strategy
+  field                     (one-of field-id field-literal fk-> expression datetime-field)
+  strategy-name             BinningStrategyName
+  strategy-param            (optional s/Num))
+
+(def Field
+  "Schema for anything that refers to a Field, from the common `[:field-id <id>]` to variants like `:datetime-field` or
+  `:fk->`."
+  (one-of field-id field-literal fk-> datetime-field expression binning-strategy))
+
+;; aggregate field reference refers to an aggregation, e.g.
+;;
+;;    {:aggregation [[:count]]
+;;     :order-by    [[:asc [:aggregation 0]]]} ;; refers to the 0th aggregation, `:count`
+;;
+;; Currently aggregate Field references can only be used inside order-by clauses. In the future once we support SQL
+;; `HAVING` we can allow them in filter clauses too
+;;
+;; TODO - shouldn't we allow composing aggregations in expressions? e.g.
+;;
+;;    {:order-by [[:asc [:+ [:aggregation 0] [:aggregation 1]]]]}
+;;
+;; TODO - it would be nice if we could check that there's actually an aggregation with the corresponding index,
+;; wouldn't it
+(defclause aggregation, aggregation-clause-index s/Int)
+
+(def ^:private FieldOrAggregationReference
+  (s/if (partial is-clause? :aggregation)
+    aggregation
+    Field))
+
+
+;;; -------------------------------------------------- Expressions ---------------------------------------------------
+
+(declare ExpressionDef)
+
+(def ^:private ExpressionArg
+  (s/conditional
+   number?
+   s/Num
+
+   (partial is-clause? #{:+ :- :/ :*})
+   (s/recursive #'ExpressionDef)
+
+   :else
+   Field))
+
+(defclause +, x ExpressionArg, y ExpressionArg, more (rest ExpressionArg))
+(defclause -, x ExpressionArg, y ExpressionArg, more (rest ExpressionArg))
+(defclause /, x ExpressionArg, y ExpressionArg, more (rest ExpressionArg))
+(defclause *, x ExpressionArg, y ExpressionArg, more (rest ExpressionArg))
+
+(def ^:private ExpressionDef
+  (one-of + - / *))
+
+
+;;; -------------------------------------------------- Aggregations --------------------------------------------------
+
+(def ^:private FieldOrExpressionDef
+  (s/if (partial is-clause? #{:+ :- :* :/})
+    ExpressionDef
+    Field))
+
+;; For all of the 'normal' Aggregations below (excluding Metrics) fields are implicit Field IDs
+
+(defclause count,     field (optional Field))
+(defclause cum-count, field (optional Field))
+
+;; technically aggregations besides count can also accept expressions as args, e.g.
+;;
+;;    [[:sum [:+ [:field-id 1] [:field-id 2]]]]
+;;
+;; Which is equivalent to SQL:
+;;
+;;    SUM(field_1 + field_2)
+
+(defclause avg,       field-or-expression FieldOrExpressionDef)
+(defclause cum-sum,   field-or-expression FieldOrExpressionDef)
+(defclause distinct,  field-or-expression FieldOrExpressionDef)
+(defclause stddev,    field-or-expression FieldOrExpressionDef)
+(defclause sum,       field-or-expression FieldOrExpressionDef)
+(defclause min,       field-or-expression FieldOrExpressionDef)
+(defclause max,       field-or-expression FieldOrExpressionDef)
+
+;; Metrics are just 'macros' (placeholders for other aggregations with optional filter and breakout clauses) that get
+;; expanded to other aggregations/etc. in the expand-macros middleware
+;;
+;; METRICS WITH STRING IDS, e.g. `[:metric "ga:sessions"]`, are Google Analytics metrics, not Metabase metrics! They
+;; pass straight thru to the GA query processor.
+(defclause metric, metric-id (s/cond-pre su/IntGreaterThanZero su/NonBlankString))
+
+;; the following are definitions for expression aggregations, e.g. [:+ [:sum [:field-id 10]] [:sum [:field-id 20]]]
+
+(declare UnnamedAggregation)
+
+(def ^:private ExpressionAggregationArg
+  (s/if number?
+    s/Num
+    (s/recursive #'UnnamedAggregation)))
+
+(defclause [ag:+   +],  x ExpressionAggregationArg, y ExpressionAggregationArg, more (rest ExpressionAggregationArg))
+(defclause [ag:-   -],  x ExpressionAggregationArg, y ExpressionAggregationArg, more (rest ExpressionAggregationArg))
+(defclause [ag:*   *],  x ExpressionAggregationArg, y ExpressionAggregationArg, more (rest ExpressionAggregationArg))
+(defclause [ag:div /],  x ExpressionAggregationArg, y ExpressionAggregationArg, more (rest ExpressionAggregationArg))
+;; ag:/ isn't a valid token
+
+(def ^:private UnnamedAggregation
+  (one-of count avg cum-count cum-sum distinct stddev sum min max ag:+ ag:- ag:* ag:div metric))
+
+;; any sort of aggregation can be wrapped in a `[:named <ag> <custom-name>]` clause, but you cannot wrap a `:named` in
+;; a `:named`
+
+(defclause named, aggregation UnnamedAggregation, aggregation-name su/NonBlankString)
+
+(def ^:private Aggregation
+  (s/if (partial is-clause? :named)
+    named
+    UnnamedAggregation))
+
+
+;;; ---------------------------------------------------- Order-By ----------------------------------------------------
+
+;; order-by is just a series of `[<direction> <field>]` clauses like
+;;
+;;    {:order-by [[:asc [:field-id 1]], [:desc [:field-id 2]]]}
+;;
+;; Field ID is implicit in these clauses
+
+(defclause asc,  field FieldOrAggregationReference)
+(defclause desc, field FieldOrAggregationReference)
+
+(def ^:private OrderBy
+  (one-of asc desc))
+
+
+;;; ----------------------------------------------------- Filter -----------------------------------------------------
+
+(declare Filter)
+
+(defclause and
+  first-clause  (s/recursive #'Filter)
+  second-clause (s/recursive #'Filter)
+  other-clauses (rest (s/recursive #'Filter)))
+
+(defclause or
+  first-clause  (s/recursive #'Filter)
+  second-clause (s/recursive #'Filter)
+  other-clauses (rest (s/recursive #'Filter)))
+
+(defclause not, clause (s/recursive #'Filter))
+
+(def ^:private FieldOrRelativeDatetime
+  (s/if (partial is-clause? :relative-datetime)
+   relative-datetime
+   Field))
+
+(def ^:private EqualityComparible
+  "Schema for things things that make sense in a `=` or `!=` filter, i.e. things that can be compared for equality."
+  (s/maybe
+   (s/cond-pre
+    s/Bool
+    s/Num
+    s/Str
+    DatetimeLiteral
+    FieldOrRelativeDatetime)))
+
+(def ^:private OrderComparible
+  "Schema for things that make sense in a filter like `>` or `<`, i.e. things that can be sorted."
+  (s/cond-pre
+   s/Num
+   DatetimeLiteral
+   FieldOrRelativeDatetime))
+
+;; For all of the non-compound Filter clauses below the first arg is an implicit Field ID
+
+(defclause =,  field Field, value-or-field EqualityComparible, more-values-or-fields (rest EqualityComparible))
+(defclause !=, field Field, value-or-field EqualityComparible, more-values-or-fields (rest EqualityComparible))
+
+(defclause <,  field Field, value-or-field OrderComparible)
+(defclause >,  field Field, value-or-field OrderComparible)
+(defclause <=, field Field, value-or-field OrderComparible)
+(defclause >=, field Field, value-or-field OrderComparible)
+
+(defclause between field Field, min OrderComparible, max OrderComparible)
+
+(defclause inside
+  lat-field Field
+  lon-field Field
+  lat-max   OrderComparible
+  lon-min   OrderComparible
+  lat-min   OrderComparible
+  lon-max   OrderComparible)
+
+(defclause is-null,  field Field)
+(defclause not-null, field Field)
+
+(def ^:private StringFilterOptions
+  {(s/optional-key :case-sensitive) s/Bool}) ; default true
+
+(def ^:private StringOrField
+  (s/cond-pre
+   s/Str
+   Field))
+
+(defclause starts-with,      field Field, string-or-field StringOrField, options (optional StringFilterOptions))
+(defclause ends-with,        field Field, string-or-field StringOrField, options (optional StringFilterOptions))
+(defclause contains,         field Field, string-or-field StringOrField, options (optional StringFilterOptions))
+(defclause does-not-contain, field Field, string-or-field StringOrField, options (optional StringFilterOptions))
+
+(def ^:private TimeIntervalOptions
+  {(s/optional-key :include-current) s/Bool}) ; default false
+
+(defclause time-interval
+  field   Field
+  n       (s/cond-pre
+           s/Int
+           (s/enum :current :last :next))
+  unit    RelativeDatetimeUnit
+  options (optional TimeIntervalOptions))
+
+;; A segment is a special `macro` that saves some pre-definied filter clause, e.g. [:segment 1]
+;; this gets replaced by a normal Filter clause in MBQL macroexpansion
+(defclause segment, segment-id su/IntGreaterThanZero)
+
+(def Filter
+  "Schema for a valid MBQL `:filter` clause."
+  (one-of and or not = != < > <= >= between inside is-null not-null starts-with ends-with contains does-not-contain
+          time-interval segment))
+
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                                    Queries                                                     |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+;;; ---------------------------------------------- Native [Inner] Query ----------------------------------------------
+
+;; TODO - schemas for template tags and dimensions live in `metabase.query-processor.middleware.parameters.sql`. Move
+;; them here when we get the chance.
+
+(def ^:private TemplateTag
+  s/Any) ; s/Any for now until we move over the stuff from the parameters middleware
+
+(def ^:private NativeQuery
+  "Schema for a valid, normalized native [inner] query."
+  {:query                          s/Any
+   (s/optional-key :template-tags) {su/NonBlankString TemplateTag}})
+
+
+;;; ----------------------------------------------- MBQL [Inner] Query -----------------------------------------------
+
+(declare MBQLQuery)
+
+(def ^:private SourceQuery
+  "Schema for a valid value for a `:source-query` clause."
+  (s/if :native
+    ;; when using native queries as source queries the schema is exactly the same except use `:native` in place of
+    ;; `:query` for reasons I do not fully remember (perhaps to make it easier to differentiate them from MBQL source
+    ;; queries).
+    (set/rename-keys NativeQuery {:query :native})
+    (s/recursive #'MBQLQuery)))
+
+(def MBQLQuery
+  "Schema for a valid, normalized MBQL [inner] query."
+  (s/constrained
+   {(s/optional-key :source-query) SourceQuery
+    (s/optional-key :source-table) (s/cond-pre su/IntGreaterThanZero #"^card__[1-9]\d*$")
+    (s/optional-key :aggregation)  (su/non-empty [Aggregation])
+    (s/optional-key :breakout)     (su/non-empty [Field])
+    (s/optional-key :expressions)  {s/Keyword ExpressionDef} ; TODO - I think expressions keys should be strings
+    (s/optional-key :fields)       (su/non-empty [Field])
+    (s/optional-key :filter)       Filter
+    (s/optional-key :limit)        su/IntGreaterThanZero
+    (s/optional-key :order-by)     (su/non-empty [OrderBy])
+    (s/optional-key :page)         {:page  su/IntGreaterThanOrEqualToZero
+                                    :items su/IntGreaterThanZero}}
+   (fn [query]
+     (core/= 1 (core/count (select-keys query [:source-query :source-table]))))
+   "Query must specify either `:source-table` or `:source-query`, but not both."))
+
+
+;;; ----------------------------------------------------- Params -----------------------------------------------------
+
+(def ^:private Parameter
+  "Schema for a valid, normalized query parameter."
+  s/Any) ; s/Any for now until we move over the stuff from the parameters middleware
+
+
+;;; ---------------------------------------------------- Options -----------------------------------------------------
+
+(def ^:private Settings
+  "Options that tweak the behavior of the query processor."
+  ;; The timezone the query should be ran in, overriding the default report timezone for the instance.
+  {(s/optional-key :report-timezone) su/NonBlankString})
+;; TODO - should we add s/Any keys here? What if someone wants to add custom middleware!
+
+(def ^:private Constraints
+  "Additional constraints added to a query limiting the maximum number of rows that can be returned. Mostly useful
+  because native queries don't support the MBQL `:limit` clause. For MBQL queries, if `:limit` is set, it will
+  override these values."
+  {;; maximum number of results to allow for a query with aggregations
+   (s/optional-key :max-results)           su/IntGreaterThanOrEqualToZero
+   ;; maximum number of results to allow for a query with no aggregations
+   (s/optional-key :max-results-bare-rows) su/IntGreaterThanOrEqualToZero})
+
+(def ^:private MiddlewareOptions
+  "Additional options that can be used to toggle middleware on or off."
+  {;; should we skip adding results_metadata to query results after running the query? Used by
+   ;; `metabase.query-processor.middleware.results-metadata`; default `false`
+   (s/optional-key :skip-results-metadata?) s/Bool
+   ;; should we skip converting datetime types to ISO-8601 strings with appropriate timezone when post-processing
+   ;; results? Used by `metabase.query-processor.middleware.format-rows`; default `false`
+   (s/optional-key :format-rows?)           s/Bool})
+
+
+;;; ------------------------------------------------------ Info ------------------------------------------------------
+
+;; This stuff is used for informational purposes, primarily to record QueryExecution entries when a query is ran. Pass
+;; them along if applicable when writing code that creates queries, but when working on middleware and the like you
+;; can most likely ignore this stuff entirely.
+
+(def Context
+  "Schema for `info.context`; used for informational purposes to record how a query was executed."
+  (s/enum :ad-hoc
+          :csv-download
+          :dashboard
+          :embedded-dashboard
+          :embedded-question
+          :json-download
+          :map-tiles
+          :metabot
+          :public-dashboard
+          :public-question
+          :pulse
+          :question
+          :xlsx-download))
+
+(def Info
+  "Schema for query `:info` dictionary, which is used for informational purposes to record information about how a query
+  was executed in QueryExecution and other places. It is considered bad form for middleware to change its behavior
+  based on this information, don't do it!"
+  {;; These keys are nice to pass in if you're running queries on the backend and you know these values. They aren't
+   ;; used for permissions checking or anything like that so don't try to be sneaky
+   (s/optional-key :context)      (s/maybe Context)
+   (s/optional-key :executed-by)  (s/maybe su/IntGreaterThanZero)
+   (s/optional-key :card-id)      (s/maybe su/IntGreaterThanZero)
+   (s/optional-key :dashboard-id) (s/maybe su/IntGreaterThanZero)
+   (s/optional-key :pulse-id)     (s/maybe su/IntGreaterThanZero)
+   (s/optional-key :nested?)      (s/maybe s/Bool)
+   ;; `:hash` and `:query-type` get added automatically by `process-query-and-save-execution!`, so don't try passing
+   ;; these in yourself. In fact, I would like this a lot better if we could take these keys out of `:info` entirely
+   ;; and have the code that saves QueryExceutions figure out their values when it goes to save them
+   (s/optional-key :query-hash)   (s/maybe (Class/forName "[B"))
+   ;; TODO - this key is pointless since we can just look at `:type`; let's normalize it out and remove it entirely
+   ;; when we get a chance
+   (s/optional-key :query-type)   (s/enum "MBQL" "native")})
+
+
+;;; --------------------------------------------- Metabase [Outer] Query ---------------------------------------------
+
+(def Query
+  "Schema for an [outer] query, e.g. the sort of thing you'd pass to the query processor or save in
+  `Card.dataset_query`."
+  (s/constrained
+   ;; TODO - move database/virtual-id into this namespace so we don't have to use the magic number here
+   {:database                     (s/cond-pre (s/eq -1337) su/IntGreaterThanZero)
+    ;; Type of query. `:query` = MBQL; `:native` = native. TODO - consider normalizing `:query` to `:mbql`
+    :type                         (s/enum :query :native)
+    (s/optional-key :native)      NativeQuery
+    (s/optional-key :query)       MBQLQuery
+    (s/optional-key :parameters)  [Parameter]
+    ;;
+    ;; -------------------- OPTIONS --------------------
+    ;;
+    ;; These keys are used to tweak behavior of the Query Processor.
+    ;; TODO - can we combine these all into a single `:options` map?
+    ;;
+    (s/optional-key :settings)    (s/maybe Settings)
+    (s/optional-key :constraints) (s/maybe Constraints)
+    (s/optional-key :middleware)  (s/maybe MiddlewareOptions)
+    ;;
+    ;; -------------------- INFO --------------------
+    ;;
+    (s/optional-key :info)        (s/maybe Info)
+    ;;
+    ;; not even really info, but in some cases `:driver` gets added to the query even though we have middleware that
+    ;; is supposed to resolve driver, and we also have the `*driver*` dynamic var where we should probably be stashing
+    ;; the resolved driver anyway. It might make sense to take this out in the future.
+    (s/optional-key :driver)      {}}
+   (fn [{native :native, mbql :query, query-type :type}]
+     (case query-type
+       :native (core/and native (core/not mbql))
+       :query  (core/and mbql   (core/not native))))
+   "Native queries should specify `:native` but not `:query`; MBQL queries should specify `:query` but not `:native`."))
+
+
+;;; --------------------------------------------------- Validators ---------------------------------------------------
+
+(def ^{:arglists '([query])} validate-query
+  "Compiled schema validator for an [outer] Metabase query. (Pre-compling a validator is more efficient; use this
+  instead of calling `(s/validate Query query)` or similar."
+  (s/validator Query))
diff --git a/src/metabase/mbql/schema/helpers.clj b/src/metabase/mbql/schema/helpers.clj
new file mode 100644
index 0000000000000000000000000000000000000000..0905f6e42d070c4da74067cce0b05a4d2d9aaa31
--- /dev/null
+++ b/src/metabase/mbql/schema/helpers.clj
@@ -0,0 +1,89 @@
+(ns metabase.mbql.schema.helpers
+  (:require [clojure.string :as str]
+            [schema.core :as s]))
+
+;;; --------------------------------------------------- defclause ----------------------------------------------------
+
+(defn- clause-arg-schema [arg-name arg-schema]
+  ;; for things like optional schemas
+  (if-not (vector? arg-schema)
+    (s/one arg-schema arg-name)
+    (let [[option arg-schema] arg-schema]
+      (case option
+        :optional (s/optional arg-schema arg-name)
+        :rest     (s/named arg-schema arg-name)))))
+
+(defn clause
+  "Impl of `defclause` macro."
+  [clause-name & arg-schemas]
+  (vec
+   (cons
+    (s/one (s/eq clause-name) clause-name)
+    (for [[arg-name arg-schema] (partition 2 arg-schemas)]
+      (clause-arg-schema arg-name arg-schema)))))
+
+(defn- stringify-names [arg-names-and-schemas]
+  (reduce concat (for [[arg-name schema] (partition 2 arg-names-and-schemas)]
+                   [(name arg-name) (if (and (list? schema)
+                                             (#{:optional :rest} (keyword (first schema))))
+                                      (vec (cons (keyword (first schema)) (rest schema)))
+                                      schema)])))
+
+(defmacro defclause
+  "Define a new MBQL clause.
+
+    (defclause field-id, id su/IntGreaterThanZero)
+
+  The first arg is the name of the clause, and should be followed by pairs of arg name, arg schema. Arg schemas may
+  optionally be wrapped in `optional` or `rest` to signify that the arg is optional, or to accept varargs:
+
+    (defclause count, field (optional Field))
+    (defclause and, filters (rest Filter))
+
+  Since there are some cases where clauses should be parsed differently in MBQL (such as expressions in the
+  `expressions` clause vs in aggregations), you can give the actual symbol produced by this macro a different name as
+  follows:
+
+    (defclause [ag:+ +] ...) ; define symbol `ag:+` to be used for a `[:+ ...]` clause"
+  [clause-name & arg-names-and-schemas]
+  (let [[symb-name clause-name] (if (vector? clause-name)
+                                  clause-name
+                                  [clause-name clause-name])]
+    `(def ~(vary-meta symb-name assoc :private true, :clause-name (keyword clause-name))
+       (clause ~(keyword clause-name) ~@(stringify-names arg-names-and-schemas)))))
+
+
+;;; ----------------------------------------------------- one-of -----------------------------------------------------
+
+;; TODO - this is a copy of the one in the `metabase.mbql.util` namespace. We need to reorganize things a bit so we
+;; can use the same fn and avoid circular refs
+(defn ^:deprecated is-clause?
+  "If `x` an MBQL clause, and an instance of clauses defined by keyword(s) `k-or-ks`?
+
+    (is-clause? :count [:count 10])        ; -> true
+    (is-clause? #{:+ :- :* :/} [:+ 10 20]) ; -> true"
+  [k-or-ks x]
+  (and
+   (sequential? x)
+   (keyword? (first x))
+   (if (coll? k-or-ks)
+     ((set k-or-ks) (first x))
+     (= k-or-ks (first x)))))
+
+(defn one-of*
+  "Interal impl of `one-of` macro."
+  [& clause-names+schemas]
+  (s/named
+   (apply
+    s/conditional
+    (reduce concat (for [[clause-name schema] clause-names+schemas]
+                     [(partial is-clause? clause-name) schema])))
+   (str "Must be a valid instance of one of these clauses: " (str/join ", " (map first clause-names+schemas)))))
+
+(defmacro one-of
+  "Define a schema that accepts one of several different MBQL clauses.
+
+    (one-of field-id field-literal)"
+  [& clauses]
+  `(one-of* ~@(for [clause clauses]
+                [`(:clause-name (meta (var ~clause))) clause])))
diff --git a/src/metabase/mbql/util.clj b/src/metabase/mbql/util.clj
new file mode 100644
index 0000000000000000000000000000000000000000..7ee6f40756b0a9512ac926bada7c07e361fd1bc3
--- /dev/null
+++ b/src/metabase/mbql/util.clj
@@ -0,0 +1,142 @@
+(ns metabase.mbql.util
+  "Utilitiy functions for working with MBQL queries."
+  (:require [clojure
+             [string :as str]
+             [walk :as walk]]
+            [metabase.mbql.schema :as mbql.s]
+            [metabase.util :as u]
+            [metabase.util.schema :as su]
+            [schema.core :as s]))
+
+(s/defn normalize-token :- s/Keyword
+  "Convert a string or keyword in various cases (`lisp-case`, `snake_case`, or `SCREAMING_SNAKE_CASE`) to a lisp-cased
+  keyword."
+  [token :- su/KeywordOrString]
+  (-> (u/keyword->qualified-name token)
+      str/lower-case
+      (str/replace #"_" "-")
+      keyword))
+
+(defn mbql-clause?
+  "True if `x` is an MBQL clause (a sequence with a keyword as its first arg). (Since this is used by the code in
+  `normalize` this handles pre-normalized clauses as well.)"
+  [x]
+  (and (sequential? x)
+       (keyword? (first x))))
+
+(defn is-clause?
+  "If `x` an MBQL clause, and an instance of clauses defined by keyword(s) `k-or-ks`?
+
+    (is-clause? :count [:count 10])        ; -> true
+    (is-clause? #{:+ :- :* :/} [:+ 10 20]) ; -> true"
+  [k-or-ks x]
+  (and
+   (mbql-clause? x)
+   (if (coll? k-or-ks)
+     ((set k-or-ks) (first x))
+     (= k-or-ks (first x)))))
+
+(defn clause-instances
+  "Return a sequence of all the instances of clause(s) in `x`. Like `is-clause?`, you can either look for instances of a
+  single clause by passing a single keyword or for instances of multiple clauses by passing a set of keywords. Returns
+  `nil` if no instances were found.
+
+    ;; look for :field-id clauses
+    (clause-instances :field-id {:query {:filter [:= [:field-id 10] 20]}})
+    ;;-> [[:field-id 10]]
+
+    ;; look for :+ or :- clauses
+    (clause-instances #{:+ :-} ...)"
+  {:style/indent 1}
+  [k-or-ks x]
+  (let [instances (atom [])]
+    (walk/postwalk
+     (fn [clause]
+       (u/prog1 clause
+         (when (is-clause? k-or-ks clause)
+           (swap! instances conj clause))))
+     x)
+    (seq @instances)))
+
+(defn replace-clauses
+  "Walk a query looking for clauses named by keyword or set of keywords `k-or-ks` and replace them the results of a call
+  to `(f clause)`.
+
+    (replace-clauses {:filter [:= [:field-id 10] 100]} :field-id (constantly 200))
+    ;; -> {:filter [:= 200 100]}"
+  {:style/indent 2}
+  [query k-or-ks f]
+  (walk/postwalk
+   (fn [clause]
+     (if (is-clause? k-or-ks clause)
+       (f clause)
+       clause))
+   query))
+
+(defn replace-clauses-in
+  "Replace clauses only in a subset of `query`, defined by `keypath`.
+
+    (replace-clauses-in {:filter [:= [:field-id 10] 100], :breakout [:field-id 100]} [:filter] :field-id
+      (constantly 200))
+    ;; -> {:filter [:= 200 100], :breakout [:field-id 100]}"
+  {:style/indent 3}
+  [query keypath k-or-ks f]
+  (update-in query keypath #(replace-clauses % k-or-ks f)))
+
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                       Functions for manipulating queries                                       |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+;; TODO - I think we actually should move this stuff into a `mbql.helpers` namespace so we can use the util functions
+;; above in the `schema.helpers` namespace instead of duplicating them
+
+(s/defn simplify-compound-filter :- mbql.s/Filter
+  "Simplify compound `:and`, `:or`, and `:not` compound filters, combining or eliminating them where possible. This
+  also fixes theoretically disallowed compound filters like `:and` with only a single subclause."
+  [[filter-name & args :as filter-clause]]
+  (cond
+    ;; for `and` or `not` compound filters with only one subclase, just unnest the subclause
+    (and (#{:and :or} filter-name)
+         (= (count args) 1))
+    (recur (first args))
+
+    ;; for `and` and `not` compound filters with subclauses of the same type pull up any compounds of the same type
+    ;; e.g. [:and :a [:and b c]] ; -> [:and a b c]
+    (and (#{:and :or} filter-name)
+         (some (partial is-clause? filter-name) args))
+    (recur
+     (vec (cons filter-name (mapcat (fn [subclause]
+                                      (if (is-clause? filter-name subclause)
+                                        (rest subclause)
+                                        [subclause]))
+                                    args))))
+
+    ;; for `and` or `or` clauses with duplicate args, remove the duplicates and recur
+    (and (#{:and :or} filter-name)
+         (not= (count args) (count (distinct args))))
+    (recur (vec (cons filter-name (distinct args))))
+
+    ;; for `not` that wraps another `not`, eliminate both
+    (and (= :not filter-name)
+         (is-clause? :not (first args)))
+    (recur (second (first args)))
+
+    :else
+    filter-clause))
+
+;; TODO - we should validate the query against the Query schema and the output as well. Flip that on once the schema
+;; is locked-in 100%
+
+(s/defn combine-filter-clauses :- mbql.s/Filter
+  "Combine two filter clauses into a single clause in a way that minimizes slapping a bunch of `:and`s together if
+  possible."
+  [filter-clause & more-filter-clauses]
+  (simplify-compound-filter (vec (cons :and (filter identity (cons filter-clause more-filter-clauses))))))
+
+(s/defn add-filter-clause
+  "Add an additional filter clause to an `outer-query`. If `new-clause` is `nil` this is a no-op."
+  [outer-query :- su/Map, new-clause :- (s/maybe mbql.s/Filter)]
+  (if-not new-clause
+    outer-query
+    (update-in outer-query [:query :filter] combine-filter-clauses new-clause)))
diff --git a/src/metabase/metabot.clj b/src/metabase/metabot.clj
index 6b034ba180fadd26adee745cc7062b8800d8baf8..90f25595b0d9a9c42ae4fed61f64d1f240a07a7c 100644
--- a/src/metabase/metabot.clj
+++ b/src/metabase/metabot.clj
@@ -7,6 +7,7 @@
              [string :as str]]
             [clojure.java.io :as io]
             [clojure.tools.logging :as log]
+            [honeysql.core :as hsql]
             [manifold
              [deferred :as d]
              [stream :as s]]
@@ -21,8 +22,10 @@
              [permissions :refer [Permissions]]
              [permissions-group :as perms-group]
              [setting :as setting :refer [defsetting]]]
-            [metabase.util.urls :as urls]
-            [puppetlabs.i18n.core :refer [tru trs]]
+            [metabase.util
+             [date :as du]
+             [i18n :refer [trs tru]]
+             [urls :as urls]]
             [throttle.core :as throttle]
             [toucan.db :as db]))
 
@@ -32,7 +35,106 @@
   :default false)
 
 
-;;; ------------------------------------------------------------ Perms Checking ------------------------------------------------------------
+;;; ------------------------------------- Deciding which instance is the MetaBot -------------------------------------
+
+;; Close your eyes, and imagine a scenario: someone is running multiple Metabase instances in a horizontal cluster.
+;; Good for them, but how do we make sure one, and only one, of those instances, replies to incoming MetaBot commands?
+;; It would certainly be too much if someone ran, say, 4 instances, and typing `metabot kanye` into Slack gave them 4
+;; Kanye West quotes, wouldn't it?
+;;
+;; Luckily, we have an "elegant" solution: we'll use the Settings framework to keep track of which instance is
+;; currently serving as the MetaBot. We'll have that instance periodically check in; if it doesn't check in for some
+;; timeout interval, we'll consider the job of MetaBot up for grabs. Each instance will periodically check if the
+;; MetaBot job is open, and, if so, whoever discovers it first will take it.
+
+
+;; How do we uniquiely identify each instance?
+;;
+;; `local-process-uuid` is randomly-generated upon launch and used to identify this specific Metabase instance during
+;; this specifc run. Restarting the server will change this UUID, and each server in a hortizontal cluster will have
+;; its own ID, making this different from the `site-uuid` Setting. The local process UUID is used to differentiate
+;; different horizontally clustered MB instances so we can determine which of them will handle MetaBot duties.
+;;
+;; TODO - if we ever want to use this elsewhere, we need to move it to `metabase.config` or somewhere else central
+;; like that.
+(defonce ^:private local-process-uuid
+  (str (java.util.UUID/randomUUID)))
+
+(defsetting ^:private metabot-instance-uuid
+  "UUID of the active MetaBot instance (the Metabase process currently handling MetaBot duties.)"
+  ;; This should be cached because we'll be checking it fairly often, basically every 2 seconds as part of the
+  ;; websocket monitor thread to see whether we're MetaBot (the thread won't open the WebSocket unless that instance
+  ;; is handling MetaBot duties)
+  :internal? true)
+
+(defsetting ^:private metabot-instance-last-checkin
+  "Timestamp of the last time the active MetaBot instance checked in."
+  :internal? true
+  ;; caching is disabled for this, since it is intended to be updated frequently (once a minute or so) If we use the
+  ;; cache, it will trigger cache invalidation for all the other instances (wasteful), and possibly at any rate be
+  ;; incorrect (for example, if another instance checked in a minute ago, our local cache might not get updated right
+  ;; away, causing us to falsely assume the MetaBot role is up for grabs.)
+  :cache?    false
+  :type      :timestamp)
+
+(defn- current-timestamp-from-db
+  "Fetch the current timestamp from the DB. Why do this from the DB? It's not safe to assume multiple instances have
+  clocks exactly in sync; but since each instance is using the same application DB, we can use it as a cannonical
+  source of truth."
+  ^java.sql.Timestamp []
+  (-> (db/query {:select [[(hsql/raw "current_timestamp") :current_timestamp]]})
+      first
+      :current_timestamp))
+
+(defn- update-last-checkin!
+  "Update the last checkin timestamp recorded in the DB."
+  []
+  (metabot-instance-last-checkin (current-timestamp-from-db)))
+
+(defn- seconds-since-last-checkin
+  "Return the number of seconds since the active MetaBot instance last checked in (updated the
+  `metabot-instance-last-checkin` Setting). If a MetaBot instance has *never* checked in, this returns `nil`. (Since
+  `last-checkin` is one of the few Settings that isn't cached, this always requires a DB call.)"
+  []
+  (when-let [last-checkin (metabot-instance-last-checkin)]
+    (u/prog1 (-> (- (.getTime (current-timestamp-from-db))
+                    (.getTime last-checkin))
+                 (/ 1000))
+      (log/debug (u/format-color 'magenta (trs "Last MetaBot checkin was {0} ago." (du/format-seconds <>)))))))
+
+(def ^:private ^Integer recent-checkin-timeout-interval-seconds
+  "Number of seconds since the last MetaBot checkin that we will consider the MetaBot job to be 'up for grabs',
+  currently 3 minutes. (i.e. if the current MetaBot job holder doesn't check in for more than 3 minutes, it's up for
+  grabs.)"
+  (int (* 60 3)))
+
+(defn- last-checkin-was-not-recent?
+  "`true` if the last checkin of the active MetaBot instance was more than 3 minutes ago, or if there has never been a
+  checkin. (This requires DB calls, so it should not be called too often -- once a minute [at the time of this
+  writing] should be sufficient.)"
+  []
+  (if-let [seconds-since-last-checkin (seconds-since-last-checkin)]
+    (> seconds-since-last-checkin
+       recent-checkin-timeout-interval-seconds)
+    true))
+
+(defn- am-i-the-metabot?
+  "Does this instance currently have the MetaBot job? (Does not require any DB calls, so may safely be called
+  often (i.e. in the websocket monitor thread loop.)"
+  []
+  (= (metabot-instance-uuid)
+     local-process-uuid))
+
+(defn- become-metabot!
+  "Direct this instance to assume the duties of acting as MetaBot, and update the Settings we use to track assignment
+  accordingly."
+  []
+  (log/info (u/format-color 'green (trs "This instance will now handle MetaBot duties.")))
+  (metabot-instance-uuid local-process-uuid)
+  (update-last-checkin!))
+
+
+;;; ------------------------------------------------- Perms Checking -------------------------------------------------
 
 (defn- metabot-permissions
   "Return the set of permissions granted to the MetaBot."
@@ -50,7 +152,7 @@
   `(do-with-metabot-permissions (fn [] ~@body)))
 
 
-;;; # ------------------------------------------------------------ Metabot Command Handlers ------------------------------------------------------------
+;;; -------------------------------------------- Metabot Command Handlers --------------------------------------------
 
 (def ^:private ^:dynamic *channel-id* nil)
 
@@ -71,21 +173,21 @@
                              dispatch-token) varr}))]
     (fn dispatch*
       ([]
-       (keys-description (tru "Here''s what I can {0}:" verb) fn-map))
+       (keys-description (str (tru "Here''s what I can {0}:" verb)) fn-map))
       ([what & args]
        (if-let [f (fn-map (keyword what))]
          (apply f args)
-         (tru "I don''t know how to {0} `{1}`.\n{2}"
-                 verb
-                 (if (instance? clojure.lang.Named what)
-                   (name what)
-                   what)
-                 (dispatch*)))))))
+         (str (tru "I don''t know how to {0} `{1}`.\n{2}"
+                   verb
+                   (if (instance? clojure.lang.Named what)
+                     (name what)
+                     what)
+                   (dispatch*))))))))
 
 (defn- format-exception
   "Format a `Throwable` the way we'd like for posting it on slack."
   [^Throwable e]
-  (tru "Uh oh! :cry:\n> {0}" (.getMessage e)))
+  (str (tru "Uh oh! :cry:\n> {0}" (.getMessage e))))
 
 (defmacro ^:private do-async {:style/indent 0} [& body]
   `(future (try ~@body
@@ -104,39 +206,45 @@
   "Implementation of the `metabot list cards` command."
   [& _]
   (let [cards (with-metabot-permissions
-                (filterv mi/can-read? (db/select [Card :id :name :dataset_query], {:order-by [[:id :desc]], :limit 20})))]
-    (tru "Here''s your {0} most recent cards:\n{1}" (count cards) (format-cards cards))))
+                (filterv mi/can-read? (db/select [Card :id :name :dataset_query :collection_id], {:order-by [[:id :desc]], :limit 20})))]
+    (str (tru "Here''s your {0} most recent cards:\n{1}" (count cards) (format-cards cards)))))
 
 (defn- card-with-name [card-name]
   (first (u/prog1 (db/select [Card :id :name], :%lower.name [:like (str \% (str/lower-case card-name) \%)])
            (when (> (count <>) 1)
-             (throw (Exception. (str (tru "Could you be a little more specific? I found these cards with names that matched:\n{0}"
-                                          (format-cards <>)))))))))
+             (throw (Exception.
+                     (str (tru "Could you be a little more specific? I found these cards with names that matched:\n{0}"
+                               (format-cards <>)))))))))
 
 (defn- id-or-name->card [card-id-or-name]
   (cond
     (integer? card-id-or-name)     (db/select-one [Card :id :name], :id card-id-or-name)
     (or (string? card-id-or-name)
         (symbol? card-id-or-name)) (card-with-name card-id-or-name)
-    :else                          (throw (Exception. (str (tru "I don''t know what Card `{0}` is. Give me a Card ID or name." card-id-or-name))))))
+    :else                          (throw (Exception.
+                                           (str (tru "I don''t know what Card `{0}` is. Give me a Card ID or name."
+                                                     card-id-or-name))))))
 
 
 (defn ^:metabot show
   "Implementation of the `metabot show card <name-or-id>` command."
   ([]
-   (tru "Show which card? Give me a part of a card name or its ID and I can show it to you. If you don''t know which card you want, try `metabot list`."))
+   (str (tru "Show which card? Give me a part of a card name or its ID and I can show it to you. If you don''t know which card you want, try `metabot list`.")))
   ([card-id-or-name]
    (if-let [{card-id :id} (id-or-name->card card-id-or-name)]
      (do
        (with-metabot-permissions
          (read-check Card card-id))
-       (do-async (let [attachments (pulse/create-and-upload-slack-attachments! (pulse/create-slack-attachment-data [(pulse/execute-card card-id, :context :metabot)]))]
+       (do-async (let [attachments (pulse/create-and-upload-slack-attachments!
+                                    (pulse/create-slack-attachment-data
+                                     [(pulse/execute-card card-id, :context :metabot)]))]
                    (slack/post-chat-message! *channel-id*
                                              nil
                                              attachments)))
-       (tru "Ok, just a second..."))
+       (str (tru "Ok, just a second...")))
      (throw (Exception. (str (tru "Not Found"))))))
-  ;; If the card name comes without spaces, e.g. (show 'my 'wacky 'card) turn it into a string an recur: (show "my wacky card")
+  ;; If the card name comes without spaces, e.g. (show 'my 'wacky 'card) turn it into a string an recur: (show "my
+  ;; wacky card")
   ([word & more]
    (show (str/join " " (cons word more)))))
 
@@ -171,7 +279,7 @@
   (str ":kanye:\n> " (rand-nth @kanye-quotes)))
 
 
-;;; # ------------------------------------------------------------ Metabot Command Dispatch ------------------------------------------------------------
+;;; -------------------------------------------- Metabot Command Dispatch --------------------------------------------
 
 (def ^:private apply-metabot-fn
   (dispatch-fn "understand" :metabot))
@@ -189,7 +297,7 @@
         (apply apply-metabot-fn tokens)))))
 
 
-;;; # ------------------------------------------------------------ Metabot Input Handling ------------------------------------------------------------
+;;; --------------------------------------------- Metabot Input Handling ---------------------------------------------
 
 (defn- message->command-str
   "Get the command portion of a message *event* directed at Metabot.
@@ -241,7 +349,7 @@
             nil)))))
 
 
-;;; # ------------------------------------------------------------ Websocket Connection Stuff ------------------------------------------------------------
+;;; ------------------------------------------- Websocket Connection Stuff -------------------------------------------
 
 (defn- connect-websocket! []
   (when-let [websocket-url (slack/websocket-url)]
@@ -264,9 +372,10 @@
 ;; and if it is no longer equal to theirs they should die
 (defonce ^:private websocket-monitor-thread-id (atom nil))
 
-;; we'll use a THROTTLER to implement exponential backoff for recconenction attempts, since THROTTLERS are designed with for this sort of thing
-;; e.g. after the first failed connection we'll wait 2 seconds, then each that amount increases by the `:delay-exponent` of 1.3
-;; so our reconnection schedule will look something like:
+;; we'll use a THROTTLER to implement exponential backoff for recconenction attempts, since THROTTLERS are designed
+;; with for this sort of thing e.g. after the first failed connection we'll wait 2 seconds, then each that amount
+;; increases by the `:delay-exponent` of 1.3. So our reconnection schedule will look something like:
+;;
 ;; number of consecutive failed attempts | seconds before next try (rounded up to nearest multiple of 2 seconds)
 ;; --------------------------------------+----------------------------------------------------------------------
 ;;                                    0  |   2
@@ -276,47 +385,91 @@
 ;;                                    4  |   8
 ;;                                    5  |  14
 ;;                                    6  |  30
-;; we'll throttle this based on values of the `slack-token` setting; that way if someone changes its value they won't have to wait
-;; whatever the exponential delay is before the connection is retried
+;;
+;; we'll throttle this based on values of the `slack-token` setting; that way if someone changes its value they won't
+;; have to wait whatever the exponential delay is before the connection is retried
 (def ^:private reconnection-attempt-throttler
   (throttle/make-throttler nil :attempts-threshold 1, :initial-delay-ms 2000, :delay-exponent 1.3))
 
 (defn- should-attempt-to-reconnect? ^Boolean []
-  (boolean (u/ignore-exceptions
-             (throttle/check reconnection-attempt-throttler (slack/slack-token))
-             true)))
+  (boolean
+   (u/ignore-exceptions
+     (throttle/check reconnection-attempt-throttler (slack/slack-token))
+     true)))
+
+(defn- reopen-websocket-connection-if-needed!
+  "Check to see if websocket connection is [still] open, [re-]open it if not."
+  []
+  ;; Only open the Websocket connection if this instance is the MetaBot
+  (when (am-i-the-metabot?)
+    (when (= (.getId (Thread/currentThread)) @websocket-monitor-thread-id)
+      (try
+        (when (or (not  @websocket)
+                  (s/closed? @websocket))
+          (log/debug (trs "MetaBot WebSocket is closed. Reconnecting now."))
+          (connect-websocket!))
+        (catch Throwable e
+          (log/error (trs "Error connecting websocket:") (.getMessage e)))))))
 
 (defn- start-websocket-monitor! []
   (future
     (reset! websocket-monitor-thread-id (.getId (Thread/currentThread)))
-    ;; Every 2 seconds check to see if websocket connection is [still] open, [re-]open it if not
     (loop []
+      ;; Every 2 seconds...
       (while (not (should-attempt-to-reconnect?))
         (Thread/sleep 2000))
-      (when (= (.getId (Thread/currentThread)) @websocket-monitor-thread-id)
-        (try
-          (when (or (not  @websocket)
-                    (s/closed? @websocket))
-            (log/debug (trs "MetaBot WebSocket is closed. Reconnecting now."))
-            (connect-websocket!))
-          (catch Throwable e
-            (log/error (trs "Error connecting websocket:") (.getMessage e))))
-        (recur)))))
+      (reopen-websocket-connection-if-needed!)
+      (recur))))
+
+
+(defn- check-and-update-instance-status!
+  "Check whether the current instance is serving as the MetaBot; if so, update the last checkin timestamp; if not, check
+  whether we should become the MetaBot (and do so if we should)."
+  []
+  (cond
+    ;; if we're already the MetaBot instance, update the last checkin timestamp
+    (am-i-the-metabot?)
+    (do
+      (log/debug (trs "This instance is performing MetaBot duties."))
+      (update-last-checkin!))
+    ;; otherwise if the last checkin was too long ago, it's time for us to assume the mantle of MetaBot
+    (last-checkin-was-not-recent?)
+    (become-metabot!)
+    ;; otherwise someone else is the MetaBot and we're done here! woo
+    :else
+    (log/debug (u/format-color 'blue (trs "Another instance is already handling MetaBot duties.")))))
+
+(defn- start-instance-monitor! []
+  (future
+    (loop []
+      (check-and-update-instance-status!)
+      (Thread/sleep (* 60 1000))
+      (recur))))
+
+(defn- seconds-to-wait-before-starting
+  "Return a random number of seconds to wait before starting MetaBot processess, between 0 and 59. This is done to
+  introduce a bit of jitter that should prevent a rush of multiple instances all racing to become the MetaBot at the
+  same time."
+  []
+  (mod (.nextInt (java.security.SecureRandom.)) 60))
 
 (defn start-metabot!
   "Start the MetaBot! :robot_face:
 
-   This will spin up a background thread that opens and maintains a Slack WebSocket connection."
+  This will spin up a background thread that opens and maintains a Slack WebSocket connection."
   []
-  (when (and (slack/slack-token)
-             (metabot-enabled))
-    (log/info "Starting MetaBot WebSocket monitor thread...")
-    (start-websocket-monitor!)))
+  (future
+    (Thread/sleep (* 1000 (seconds-to-wait-before-starting)))
+    (when (and (slack/slack-token)
+               (metabot-enabled))
+      (log/info (trs "Starting MetaBot threads..."))
+      (start-websocket-monitor!)
+      (start-instance-monitor!))))
 
 (defn stop-metabot!
   "Stop the MetaBot! :robot_face:
 
-   This will stop the background thread that responsible for the Slack WebSocket connection."
+  This will stop the background thread that responsible for the Slack WebSocket connection."
   []
   (log/info (trs "Stopping MetaBot...  🤖"))
   (reset! websocket-monitor-thread-id nil)
diff --git a/src/metabase/middleware.clj b/src/metabase/middleware.clj
index 55769edd28d906b7decc6d38a3abb68494f59eee..04626955aafcf08d0c01ac49343b628201a1d168 100644
--- a/src/metabase/middleware.clj
+++ b/src/metabase/middleware.clj
@@ -15,8 +15,9 @@
              [session :refer [Session]]
              [setting :refer [defsetting]]
              [user :as user :refer [User]]]
-            [metabase.util.date :as du]
-            [puppetlabs.i18n.core :refer [tru]]
+            [metabase.util
+             [date :as du]
+             [i18n :as ui18n :refer [tru]]]
             [toucan.db :as db])
   (:import com.fasterxml.jackson.core.JsonGenerator
            java.sql.SQLException))
@@ -229,7 +230,8 @@
          strict-transport-security-header
          #_(public-key-pins-header)))
 
-(defn- html-page-security-headers [& {:keys [allow-iframes?] }]
+(defn- html-page-security-headers [& {:keys [allow-iframes?]
+                                      :or   {allow-iframes? false}}]
   (merge
    (cache-prevention-headers)
    strict-transport-security-header
@@ -385,30 +387,36 @@
   "Convert an exception from an API endpoint into an appropriate HTTP response."
   [^Throwable e]
   (let [{:keys [status-code], :as info} (ex-data e)
-        other-info                      (dissoc info :status-code)
-        message                         (.getMessage e)]
-    {:status (or status-code 500)
-     :body   (cond
-               ;; Exceptions that include a status code *and* other info are things like Field validation exceptions.
-               ;; Return those as is
-               (and status-code
-                    (seq other-info))
-               other-info
-               ;; If status code was specified but other data wasn't, it's something like a 404. Return message as the
-               ;; body.
-               status-code
-               message
-               ;; Otherwise it's a 500. Return a body that includes exception & filtered stacktrace for debugging
-               ;; purposes
-               :else
-               (let [stacktrace (u/filtered-stacktrace e)]
-                 (merge (assoc other-info
-                          :message    message
-                          :type       (class e)
-                          :stacktrace stacktrace)
-                        (when (instance? SQLException e)
-                          {:sql-exception-chain (str/split (with-out-str (jdbc/print-sql-exception-chain e))
-                                                           #"\s*\n\s*")}))))}))
+        other-info                      (dissoc info :status-code :schema)
+        message                         (.getMessage e)
+        body                            (cond
+                                          ;; Exceptions that include a status code *and* other info are things like
+                                          ;; Field validation exceptions. Return those as is
+                                          (and status-code
+                                               (seq other-info))
+                                          (ui18n/localized-strings->strings other-info)
+                                          ;; If status code was specified but other data wasn't, it's something like a
+                                          ;; 404. Return message as the (plain-text) body.
+                                          status-code
+                                          (str message)
+                                          ;; Otherwise it's a 500. Return a body that includes exception & filtered
+                                          ;; stacktrace for debugging purposes
+                                          :else
+                                          (let [stacktrace (u/filtered-stacktrace e)]
+                                            (merge (assoc other-info
+                                                     :message    message
+                                                     :type       (class e)
+                                                     :stacktrace stacktrace)
+                                                   (when (instance? SQLException e)
+                                                     {:sql-exception-chain
+                                                      (str/split (with-out-str (jdbc/print-sql-exception-chain e))
+                                                                 #"\s*\n\s*")}))))]
+    {:status  (or status-code 500)
+     :headers (cond-> (html-page-security-headers)
+                (or (string? body)
+                    (ui18n/localized-string? body))
+                (assoc "Content-Type" "text/plain"))
+     :body    body}))
 
 (defn catch-api-exceptions
   "Middleware that catches API Exceptions and returns them in our normal-style format rather than the Jetty 500
diff --git a/src/metabase/models/alert.clj b/src/metabase/models/alert.clj
new file mode 100644
index 0000000000000000000000000000000000000000..fa2781518198da56ceb3688cbcce6a409aa15be1
--- /dev/null
+++ b/src/metabase/models/alert.clj
@@ -0,0 +1 @@
+(ns metabase.models.alert)
diff --git a/src/metabase/models/card.clj b/src/metabase/models/card.clj
index dc2bbe1f138cf893c84887f77d40d366dfc75ce7..90b418dc9ccb0781cefa577f63b745f154ec50c5 100644
--- a/src/metabase/models/card.clj
+++ b/src/metabase/models/card.clj
@@ -7,6 +7,7 @@
              [public-settings :as public-settings]
              [util :as u]]
             [metabase.api.common :as api :refer [*current-user-id* *current-user-permissions-set*]]
+            [metabase.mbql.util :as mbql.u]
             [metabase.models
              [dependency :as dependency]
              [field-values :as field-values]
@@ -17,14 +18,15 @@
              [revision :as revision]]
             [metabase.models.query.permissions :as query-perms]
             [metabase.query-processor.util :as qputil]
-            [metabase.util.query :as q]
-            [puppetlabs.i18n.core :refer [tru]]
+            [metabase.util.i18n :as ui18n :refer [tru]]
             [toucan
              [db :as db]
-             [models :as models]]))
+             [models :as models]]
+            [metabase.mbql.normalize :as normalize]))
 
 (models/defmodel Card :report_card)
 
+
 ;;; -------------------------------------------------- Hydration --------------------------------------------------
 
 (defn dashboard-count
@@ -33,17 +35,23 @@
   [{:keys [id]}]
   (db/count 'DashboardCard, :card_id id))
 
+
 ;;; -------------------------------------------------- Dependencies --------------------------------------------------
 
+(defn- extract-ids
+  "Get all the Segment or Metric IDs referenced by a query."
+  [segment-or-metric query]
+  (set (for [[_ id] (mbql.u/clause-instances segment-or-metric query)]
+         id)))
+
 (defn card-dependencies
   "Calculate any dependent objects for a given `card`."
   ([_ _ card]
    (card-dependencies card))
-  ([{:keys [dataset_query]}]
-   (when (and dataset_query
-              (= :query (keyword (:type dataset_query))))
-     {:Metric  (q/extract-metric-ids (:query dataset_query))
-      :Segment (q/extract-segment-ids (:query dataset_query))})))
+  ([{{query-type :type, inner-query :query} :dataset_query}]
+   (when (= :query query-type)
+     {:Metric  (extract-ids :metric inner-query)
+      :Segment (extract-ids :segment inner-query)})))
 
 
 ;;; -------------------------------------------------- Revisions --------------------------------------------------
@@ -81,15 +89,19 @@
 
         (ids-already-seen source-card-id)
         (throw
-         (ex-info (tru "Cannot save Question: source query has circular references.")
+         (ui18n/ex-info (tru "Cannot save Question: source query has circular references.")
            {:status-code 400}))
 
         :else
         (recur (or (db/select-one-field :dataset_query Card :id source-card-id)
-                   (throw (ex-info (tru "Card {0} does not exist." source-card-id)
+                   (throw (ui18n/ex-info (tru "Card {0} does not exist." source-card-id)
                             {:status-code 404})))
                (conj ids-already-seen source-card-id))))))
 
+(defn- maybe-normalize-query [card]
+  (cond-> card
+    (:dataset_query card) (update :dataset_query normalize/normalize)))
+
 (defn- pre-insert [{query :dataset_query, :as card}]
   ;; TODO - we usually check permissions to save/update stuff in the API layer rather than here in the Toucan
   ;; model-layer functions... Not saying one pattern is better than the other (although this one does make it harder
@@ -113,7 +125,7 @@
       (log/info "Card references Fields in params:" field-ids)
       (field-values/update-field-values-for-on-demand-dbs! field-ids))))
 
-(defn- pre-update [{archived? :archived, query :dataset_query, :as card}]
+(defn- pre-update [{archived? :archived, :as card}]
   ;; TODO - don't we need to be doing the same permissions check we do in `pre-insert` if the query gets changed? Or
   ;; does that happen in the `PUT` endpoint?
   (u/prog1 card
@@ -151,7 +163,7 @@
   models/IModel
   (merge models/IModelDefaults
          {:hydration-keys (constantly [:card])
-          :types          (constantly {:dataset_query          :json
+          :types          (constantly {:dataset_query          :metabase-query
                                        :description            :clob
                                        :display                :keyword
                                        :embedding_params       :json
@@ -159,8 +171,10 @@
                                        :result_metadata        :json
                                        :visualization_settings :json})
           :properties     (constantly {:timestamped? true})
-          :pre-update     (comp populate-query-fields pre-update)
-          :pre-insert     (comp populate-query-fields pre-insert)
+          ;; Make sure we normalize the query before calling `pre-update` or `pre-insert` because some of the
+          ;; functions those fns call assume normalized queries
+          :pre-update     (comp populate-query-fields pre-update maybe-normalize-query)
+          :pre-insert     (comp populate-query-fields pre-insert maybe-normalize-query)
           :post-insert    post-insert
           :pre-delete     pre-delete
           :post-select    public-settings/remove-public-uuid-if-public-sharing-is-disabled})
diff --git a/src/metabase/models/collection.clj b/src/metabase/models/collection.clj
index d4f883758bcd3b53fabfd18e412fa184a00a5d43..ea9e3ce215dbe91809a79e077050634e7bb5f8ab 100644
--- a/src/metabase/models/collection.clj
+++ b/src/metabase/models/collection.clj
@@ -17,8 +17,9 @@
              [interface :as i]
              [permissions :as perms :refer [Permissions]]]
             [metabase.util :as u]
-            [metabase.util.schema :as su]
-            [puppetlabs.i18n.core :refer [trs tru]]
+            [metabase.util
+             [i18n :as ui18n :refer [trs tru]]
+             [schema :as su]]
             [schema.core :as s]
             [toucan
              [db :as db]
@@ -42,13 +43,13 @@
 (defn- assert-valid-hex-color [^String hex-color]
   (when (or (not (string? hex-color))
             (not (re-matches hex-color-regex hex-color)))
-    (throw (ex-info (tru "Invalid color")
+    (throw (ui18n/ex-info (tru "Invalid color")
              {:status-code 400, :errors {:color (tru "must be a valid 6-character hex color code")}}))))
 
 (defn- slugify [collection-name]
   ;; double-check that someone isn't trying to use a blank string as the collection name
   (when (str/blank? collection-name)
-    (throw (ex-info (tru "Collection name cannot be blank!")
+    (throw (ui18n/ex-info (tru "Collection name cannot be blank!")
              {:status-code 400, :errors {:name (tru "cannot be blank")}})))
   (u/slugify collection-name collection-slug-max-length))
 
@@ -141,20 +142,20 @@
   (when (contains? collection :location)
     (when-not (valid-location-path? location)
       (throw
-       (ex-info (tru "Invalid Collection location: path is invalid.")
+       (ui18n/ex-info (tru "Invalid Collection location: path is invalid.")
          {:status-code 400
           :errors      {:location (tru "Invalid Collection location: path is invalid.")}})))
     ;; if this is a Personal Collection it's only allowed to go in the Root Collection: you can't put it anywhere else!
     (when (contains? collection :personal_owner_id)
       (when-not (= location "/")
         (throw
-         (ex-info (tru "You cannot move a Personal Collection.")
+         (ui18n/ex-info (tru "You cannot move a Personal Collection.")
            {:status-code 400
             :errors      {:location (tru "You cannot move a Personal Collection.")}}))))
     ;; Also make sure that all the IDs referenced in the Location path actually correspond to real Collections
     (when-not (all-ids-in-location-path-are-valid? location)
       (throw
-       (ex-info (tru "Invalid Collection location: some or all ancestors do not exist.")
+       (ui18n/ex-info (tru "Invalid Collection location: some or all ancestors do not exist.")
          {:status-code 404
           :errors      {:location (tru "Invalid Collection location: some or all ancestors do not exist.")}})))))
 
@@ -184,6 +185,13 @@
   "Special placeholder object representing the Root Collection, which isn't really a real Collection."
   (map->RootCollection {::is-root? true}))
 
+(defn root-collection-with-ui-details
+  "The special Root Collection placeholder object with some extra details to facilitate displaying it on the FE."
+  []
+  (assoc root-collection
+    :name (str (tru "Our analytics"))
+    :id   "root"))
+
 (defn- is-root-collection? [x]
   (instance? RootCollection x))
 
@@ -273,7 +281,7 @@
   (when-let [ancestor-ids (seq (location-path->ids location))]
     (db/select [Collection :name :id] :id [:in ancestor-ids] {:order-by [:%lower.name]})))
 
-(s/defn effective-ancestors :- [CollectionInstance]
+(s/defn effective-ancestors :- [(s/cond-pre RootCollection CollectionInstance)]
   "Fetch the ancestors of a `collection`, filtering out any ones the current User isn't allowed to see. This is used
   in the UI to power the 'breadcrumb' path to the location of a given Collection. For example, suppose we have four
   Collections, nested like:
@@ -282,11 +290,11 @@
 
   The ancestors of D are:
 
-    A > B > C
+    [Root] > A > B > C
 
   If the current User is allowed to see A and C, but not B, `effective-ancestors` of D will be:
 
-    A > C
+    [Root] > A > C
 
   Thus the existence of C will be kept hidden from the current User, and for all intents and purposes the current User
   can effectively treat A as the parent of C."
@@ -294,7 +302,13 @@
   [collection :- CollectionWithLocationAndIDOrRoot]
   (if (is-root-collection? collection)
     []
-    (filter i/can-read? (ancestors collection))))
+    (filter i/can-read? (cons (root-collection-with-ui-details) (ancestors collection)))))
+
+(s/defn parent-id :- (s/maybe su/IntGreaterThanZero)
+  "Get the immediate parent `collection` id, if set."
+  {:hydrate :parent_id}
+  [{:keys [location]} :- CollectionWithLocationOrRoot]
+  (if location (location-path->parent-id location)))
 
 (s/defn children-location :- LocationPath
   "Given a `collection` return a location path that should match the `:location` value of all the children of the
@@ -497,11 +511,10 @@
       (db/update-where! Collection {:id       [:in affected-collection-ids]
                                     :archived false}
         :archived true)
-      (doseq [model '[Card Dashboard]]
+      (doseq [model '[Card Dashboard Pulse]]
         (db/update-where! model {:collection_id [:in affected-collection-ids]
                                  :archived      false}
-          :archived true))
-      (db/delete! 'Pulse :collection_id [:in affected-collection-ids]))))
+          :archived true)))))
 
 (s/defn ^:private unarchive-collection!
   "Unarchive a Collection and its descendant Collections and their Cards, Dashboards, and Pulses."
@@ -512,7 +525,7 @@
       (db/update-where! Collection {:id       [:in affected-collection-ids]
                                     :archived true}
         :archived false)
-      (doseq [model '[Card Dashboard]]
+      (doseq [model '[Card Dashboard Pulse]]
         (db/update-where! model {:collection_id [:in affected-collection-ids]
                                  :archived      true}
           :archived false)))))
@@ -604,7 +617,7 @@
   ;; double-check and make sure it's not just the existing value getting passed back in for whatever reason
   (when (api/column-will-change? :personal_owner_id collection-before-updates collection-updates)
     (throw
-     (ex-info (tru "You're not allowed to change the owner of a Personal Collection.")
+     (ui18n/ex-info (tru "You're not allowed to change the owner of a Personal Collection.")
        {:status-code 400
         :errors      {:personal_owner_id (tru "You're not allowed to change the owner of a Personal Collection.")}})))
   ;;
@@ -615,15 +628,15 @@
   ;; You also definitely cannot *move* a Personal Collection
   (when (api/column-will-change? :location collection-before-updates collection-updates)
     (throw
-     (ex-info (tru "You're not allowed to move a Personal Collection.")
+     (ui18n/ex-info (tru "You're not allowed to move a Personal Collection.")
        {:status-code 400
         :errors      {:location (tru "You're not allowed to move a Personal Collection.")}})))
   ;; You also can't archive a Personal Collection
   (when (api/column-will-change? :archived collection-before-updates collection-updates)
     (throw
-     (ex-info (tru "You cannot archive a Personal Collection!")
+     (ui18n/ex-info (tru "You cannot archive a Personal Collection.")
        {:status-code 400
-        :errors      {:archived (tru "You cannot archive a Personal Collection!")}}))))
+        :errors      {:archived (tru "You cannot archive a Personal Collection.")}}))))
 
 (s/defn ^:private maybe-archive-or-unarchive!
   "If `:archived` specified in the updates map, archive/unarchive as needed."
@@ -632,7 +645,7 @@
   (when (api/column-will-change? :archived collection-before-updates collection-updates)
     ;; check to make sure we're not trying to change location at the same time
     (when (api/column-will-change? :location collection-before-updates collection-updates)
-      (throw (ex-info (tru "You cannot move a Collection and archive it at the same time.")
+      (throw (ui18n/ex-info (tru "You cannot move a Collection and archive it at the same time.")
                {:status-code 400
                 :errors      {:archived (tru "You cannot move a Collection and archive it at the same time.")}})))
     ;; ok, go ahead and do the archive/unarchive operation
@@ -954,7 +967,7 @@
   ;; the same first & last name! This will *ruin* their lives :(
   (let [{first-name :first_name, last-name :last_name} (db/select-one ['User :first_name :last_name]
                                                          :id (u/get-id user-or-id))]
-    (tru "{0} {1}''s Personal Collection" first-name last-name)))
+    (str (tru "{0} {1}''s Personal Collection" first-name last-name))))
 
 (s/defn user->personal-collection :- CollectionInstance
   "Return the Personal Collection for `user-or-id`, if it already exists; if not, create it and return it."
@@ -1002,7 +1015,12 @@
   {:batched-hydrate :personal_collection_id}
   [users]
   (when (seq users)
+    ;; efficiently create a map of user ID -> personal collection ID
     (let [user-id->collection-id (db/select-field->id :personal_owner_id Collection
                                    :personal_owner_id [:in (set (map u/get-id users))])]
+      ;; now for each User, try to find the corresponding ID out of that map. If it's not present (the personal
+      ;; Collection hasn't been created yet), then instead call `user->personal-collection-id`, which will create it
+      ;; as a side-effect. This will ensure this property never comes back as `nil`
       (for [user users]
-        (assoc user :personal_collection_id (user-id->collection-id (u/get-id user)))))))
+        (assoc user :personal_collection_id (or (user-id->collection-id (u/get-id user))
+                                                (user->personal-collection-id (u/get-id user))))))))
diff --git a/src/metabase/models/collection_revision.clj b/src/metabase/models/collection_revision.clj
index c367c5b14b139462e18b11b2c5f5150906a49c95..f953724f2fac897776df691912a516b4de1702a2 100644
--- a/src/metabase/models/collection_revision.clj
+++ b/src/metabase/models/collection_revision.clj
@@ -1,7 +1,8 @@
 (ns metabase.models.collection-revision
   (:require [metabase.util :as u]
-            [metabase.util.date :as du]
-            [puppetlabs.i18n.core :refer [tru]]
+            [metabase.util
+             [date :as du]
+             [i18n :refer [tru]]]
             [toucan
              [db :as db]
              [models :as models]]))
diff --git a/src/metabase/models/dashboard.clj b/src/metabase/models/dashboard.clj
index 28b76eaa7a0f9a37262efb9617c05ab3f4baee69..6de3818456dbdc4c7a8e3948471965bd73e09ebc 100644
--- a/src/metabase/models/dashboard.clj
+++ b/src/metabase/models/dashboard.clj
@@ -12,6 +12,7 @@
             [metabase.automagic-dashboards.populate :as magic.populate]
             [metabase.models
              [card :as card :refer [Card]]
+             [collection :as collection]
              [dashboard-card :as dashboard-card :refer [DashboardCard]]
              [field-values :as field-values]
              [interface :as i]
@@ -243,28 +244,33 @@
            (str "Filtered by: ")))
 
 (defn- ensure-unique-collection-name
-  [collection]
-  (let [c (db/count 'Collection :name [:like (format "%s%%" collection)])]
+  [collection-name parent-collection-id]
+  (let [c (db/count 'Collection
+            :name     [:like (format "%s%%" collection-name)]
+            :location  (collection/children-location  (db/select-one ['Collection :location :id]
+                                                        :id parent-collection-id)))]
     (if (zero? c)
-      collection
-      (format "%s %s" collection (inc c)))))
+      collection-name
+      (format "%s %s" collection-name (inc c)))))
 
 (defn save-transient-dashboard!
   "Save a denormalized description of `dashboard`."
-  [dashboard]
+  [dashboard parent-collection-id]
   (let [dashcards  (:ordered_cards dashboard)
+        collection (magic.populate/create-collection!
+                    (ensure-unique-collection-name (:name dashboard) parent-collection-id)
+                    (rand-nth magic.populate/colors)
+                    "Automatically generated cards."
+                    parent-collection-id)
         dashboard  (db/insert! Dashboard
                      (-> dashboard
                          (dissoc :ordered_cards :rule :related :transient_name
                                  :transient_filters :param_fields :more)
-                         (assoc :description (->> dashboard
-                                                  :transient_filters
-                                                  applied-filters-blurb))))
-        collection (magic.populate/create-collection!
-                    (ensure-unique-collection-name
-                     (format "Questions for the dashboard \"%s\"" (:name dashboard)))
-                    (rand-nth magic.populate/colors)
-                    "Automatically generated cards.")]
+                         (assoc :description         (->> dashboard
+                                                          :transient_filters
+                                                          applied-filters-blurb)
+                                :collection_id       (:id collection)
+                                :collection_position 1)))]
     (doseq [dashcard dashcards]
       (let [card     (some-> dashcard :card (assoc :collection_id (:id collection)) save-card!)
             series   (some->> dashcard :series (map (fn [card]
diff --git a/src/metabase/models/dashboard_card.clj b/src/metabase/models/dashboard_card.clj
index daeefc7a5b4f3e821db5f0f77ce20dcd1b490c7d..629a2fc0ed6d92671a4fac02980deace8bc68ddb 100644
--- a/src/metabase/models/dashboard_card.clj
+++ b/src/metabase/models/dashboard_card.clj
@@ -44,7 +44,7 @@
   models/IModel
   (merge models/IModelDefaults
          {:properties  (constantly {:timestamped? true})
-          :types       (constantly {:parameter_mappings :json, :visualization_settings :json})
+          :types       (constantly {:parameter_mappings :parameter-mappings, :visualization_settings :json})
           :pre-insert  pre-insert
           :pre-delete  pre-delete
           :post-select (u/rpartial set/rename-keys {:sizex :sizeX, :sizey :sizeY})})
@@ -55,8 +55,7 @@
           :can-write?         (partial i/current-user-has-full-permissions? :write)}))
 
 
-;;; ## ---------------------------------------- HYDRATION ----------------------------------------
-
+;;; --------------------------------------------------- HYDRATION ----------------------------------------------------
 
 (defn dashboard
   "Return the `Dashboard` associated with the `DashboardCard`."
@@ -74,8 +73,7 @@
     {:order-by [[(db/qualify DashboardCardSeries :position) :asc]]}))
 
 
-;;; ## ---------------------------------------- CRUD FNS ----------------------------------------
-
+;;; ---------------------------------------------------- CRUD FNS ----------------------------------------------------
 
 (defn retrieve-dashboard-card
   "Fetch a single `DashboardCard` by its ID value."
diff --git a/src/metabase/models/field_values.clj b/src/metabase/models/field_values.clj
index 11119715cfdc81348acc941942f7b31947a09c29..fa2daf6528551137015cd2a7b34bc705a9b21a61 100644
--- a/src/metabase/models/field_values.clj
+++ b/src/metabase/models/field_values.clj
@@ -1,8 +1,9 @@
 (ns metabase.models.field-values
   (:require [clojure.tools.logging :as log]
             [metabase.util :as u]
-            [metabase.util.schema :as su]
-            [puppetlabs.i18n.core :refer [trs]]
+            [metabase.util
+             [i18n :refer [trs]]
+             [schema :as su]]
             [schema.core :as s]
             [toucan
              [db :as db]
@@ -63,10 +64,10 @@
   (let [total-length (reduce + (map (comp count str)
                                     distinct-values))]
     (u/prog1 (<= total-length total-max-length)
-      (log/debug (format "Field values total length is %d (max %d)." total-length total-max-length)
+      (log/debug (trs "Field values total length is {0} (max {1})." total-length total-max-length)
                  (if <>
-                   "FieldValues are allowed for this Field."
-                   "FieldValues are NOT allowed for this Field.")))))
+                   (trs "FieldValues are allowed for this Field.")
+                   (trs "FieldValues are NOT allowed for this Field."))))))
 
 
 (defn- distinct-values
@@ -197,6 +198,6 @@
     (doseq [{table-id :table_id, :as field} fields]
       (when (table-id->is-on-demand? table-id)
         (log/debug
-         (format "Field %d '%s' should have FieldValues and belongs to a Database with On-Demand FieldValues updating."
+         (trs "Field {0} ''{1}'' should have FieldValues and belongs to a Database with On-Demand FieldValues updating."
                  (u/get-id field) (:name field)))
         (create-or-update-field-values! field)))))
diff --git a/src/metabase/models/humanization.clj b/src/metabase/models/humanization.clj
index 3498c171d157d88b0ae976b0eb48958de5d872fd..51cbbd7c9cbebc2cdc47615bde20a594b3798f94 100644
--- a/src/metabase/models/humanization.clj
+++ b/src/metabase/models/humanization.clj
@@ -12,8 +12,9 @@
   (:require [clojure.string :as str]
             [clojure.tools.logging :as log]
             [metabase.models.setting :as setting :refer [defsetting]]
-            [metabase.util.infer-spaces :refer [infer-spaces]]
-            [puppetlabs.i18n.core :refer [tru]]
+            [metabase.util
+             [i18n :refer [tru]]
+             [infer-spaces :refer [infer-spaces]]]
             [toucan.db :as db]))
 
 (def ^:private ^:const acronyms
@@ -114,7 +115,9 @@
 
 (defsetting ^{:added "0.28.0"} humanization-strategy
   (str (tru "Metabase can attempt to transform your table and field names into more sensible, human-readable versions, e.g. \"somehorriblename\" becomes \"Some Horrible Name\".")
+       " "
        (tru "This doesn’t work all that well if the names are in a language other than English, however.")
+       " "
        (tru "Do you want us to take a guess?"))
   :default "advanced"
   :setter  set-humanization-strategy!)
diff --git a/src/metabase/models/interface.clj b/src/metabase/models/interface.clj
index d8ebb1d23836f17fff6c8b34768c3b90a4be264c..f8629dd02c1b3bc7885f9fac6efc49fb5a579897 100644
--- a/src/metabase/models/interface.clj
+++ b/src/metabase/models/interface.clj
@@ -1,6 +1,7 @@
 (ns metabase.models.interface
   (:require [cheshire.core :as json]
             [clojure.core.memoize :as memoize]
+            [metabase.mbql.normalize :as normalize]
             [metabase.util :as u]
             [metabase.util
              [cron :as cron-util]
@@ -22,7 +23,9 @@
 
 ;;; types
 
-(defn- json-in [obj]
+(defn- json-in
+  "Default in function for Fields given a Toucan type `:json`. Serializes object as JSON."
+  [obj]
   (if (string? obj)
     obj
     (json/generate-string obj)))
@@ -33,7 +36,9 @@
       (json/parse-string s keywordize-keys?)
       obj)))
 
-(defn- json-out-with-keywordization [obj]
+(defn- json-out-with-keywordization
+  "Default out function for Fields given a Toucan type `:json`. Parses serialized JSON string and keywordizes keys."
+  [obj]
   (json-out obj true))
 
 (defn- json-out-without-keywordization [obj]
@@ -47,6 +52,35 @@
   :in  json-in
   :out json-out-without-keywordization)
 
+;; `metabase-query` type is for *outer* queries like Card.dataset_query. Normalizes them on the way in & out
+(defn- maybe-normalize [query]
+  (when query (normalize/normalize query)))
+
+(models/add-type! :metabase-query
+  :in  (comp json-in maybe-normalize)
+  :out (comp maybe-normalize json-out-with-keywordization))
+
+;; `metric-segment-definition` is, predicatbly, for Metric/Segment `:definition`s, which are just the inner MBQL query
+(defn- normalize-metric-segment-definition [definition]
+  (when definition
+    (normalize/normalize-fragment [:query] definition)))
+
+;; For inner queries like those in Metric definitions
+(models/add-type! :metric-segment-definition
+  :in  (comp json-in normalize-metric-segment-definition)
+  :out (comp normalize-metric-segment-definition json-out-with-keywordization))
+
+;; For DashCard parameter lists
+(defn- normalize-parameter-mapping-targets [parameter-mappings]
+  (for [{:keys [target], :as mapping} parameter-mappings]
+    (cond-> mapping
+      target (update :target normalize/normalize-tokens :ignore-path))))
+
+(models/add-type! :parameter-mappings
+  :in  (comp json-in normalize-parameter-mapping-targets)
+  :out (comp normalize-parameter-mapping-targets json-out-with-keywordization))
+
+
 ;; json-set is just like json but calls `set` on it when coming out of the DB. Intended for storing things like a
 ;; permissions set
 (models/add-type! :json-set
@@ -68,6 +102,10 @@
   :in  encrypted-json-in
   :out (comp cached-encrypted-json-out u/jdbc-clob->str))
 
+(models/add-type! :encrypted-text
+  :in  encryption/maybe-encrypt
+  :out (comp encryption/maybe-decrypt u/jdbc-clob->str))
+
 (defn compress
   "Compress OBJ, returning a byte array."
   [obj]
diff --git a/src/metabase/models/metric.clj b/src/metabase/models/metric.clj
index 16ac6f8e44fb6bf06c51a4845079e4019922e65f..7e3e5f1cc327c55a67ca875f8f762a3f5ebb9e98 100644
--- a/src/metabase/models/metric.clj
+++ b/src/metabase/models/metric.clj
@@ -1,13 +1,16 @@
 (ns metabase.models.metric
+  "A Metric is a saved MBQL 'macro' expanding to a combination of `:aggregation` and/or `:filter` clauses.
+  It is passed in as an `:aggregation` clause but is replaced by the `expand-macros` middleware with the appropriate
+  clauses."
   (:require [medley.core :as m]
             [metabase
              [events :as events]
              [util :as u]]
+            [metabase.mbql.util :as mbql.u]
             [metabase.models
              [dependency :as dependency]
              [interface :as i]
              [revision :as revision]]
-            [metabase.util.query :as q]
             [toucan
              [db :as db]
              [hydrate :refer [hydrate]]
@@ -26,7 +29,7 @@
 (u/strict-extend (class Metric)
   models/IModel
   (merge models/IModelDefaults
-         {:types      (constantly {:definition :json, :description :clob})
+         {:types      (constantly {:definition :metric-segment-definition, :description :clob})
           :properties (constantly {:timestamped? true})
           :pre-delete pre-delete})
   i/IObjectPermissions
@@ -36,8 +39,7 @@
           :can-write?        (partial i/current-user-has-full-permissions? :write)}))
 
 
-;;; ## ---------------------------------------- REVISIONS ----------------------------------------
-
+;;; --------------------------------------------------- REVISIONS ----------------------------------------------------
 
 (defn- serialize-metric [_ _ instance]
   (dissoc instance :created_at :updated_at))
@@ -64,14 +66,14 @@
           :diff-map           diff-metrics}))
 
 
-;;; ## ---------------------------------------- DEPENDENCIES ----------------------------------------
-
+;;; -------------------------------------------------- DEPENDENCIES --------------------------------------------------
 
 (defn metric-dependencies
-  "Calculate any dependent objects for a given `Metric`."
+  "Calculate any dependent objects for a given Metric."
   [_ _ {:keys [definition]}]
   (when definition
-    {:Segment (q/extract-segment-ids definition)}))
+    {:Segment (set (for [[_ id] (mbql.u/clause-instances :segment definition)]
+                     id))}))
 
 (u/strict-extend (class Metric)
   dependency/IDependent
@@ -81,9 +83,9 @@
 ;; ## Persistence Functions
 
 (defn create-metric!
-  "Create a new `Metric`.
+  "Create a new Metric.
 
-   Returns the newly created `Metric` or throws an Exception."
+   Returns the newly created Metric or throws an Exception."
   [table-id metric-name description creator-id definition]
   {:pre [(integer? table-id)
          (string? metric-name)
@@ -99,21 +101,21 @@
         (hydrate :creator))))
 
 (defn exists?
-  "Does an *active* `Metric` with ID exist?"
+  "Does an *active* Metric with ID exist?"
   ^Boolean [id]
   {:pre [(integer? id)]}
   (db/exists? Metric :id id, :archived false))
 
 (defn retrieve-metric
-  "Fetch a single `Metric` by its ID value. Hydrates its `:creator`."
+  "Fetch a single Metric by its ID value. Hydrates its `:creator`."
   [id]
   {:pre [(integer? id)]}
   (-> (Metric id)
       (hydrate :creator)))
 
 (defn retrieve-metrics
-  "Fetch all `Metrics` for a given `Table`.  Optional second argument allows filtering by active state by
-   providing one of 3 keyword values: `:active`, `:deleted`, `:all`.  Default filtering is for `:active`."
+  "Fetch all `Metrics` for a given `Table`. Optional second argument allows filtering by active state by providing one
+  of 3 keyword values: `:active`, `:deleted`, `:all`. Default filtering is for `:active`."
   ([table-id]
    (retrieve-metrics table-id :active))
   ([table-id state]
@@ -128,9 +130,9 @@
        (hydrate :creator))))
 
 (defn update-metric!
-  "Update an existing `Metric`.
+  "Update an existing Metric.
 
-   Returns the updated `Metric` or throws an Exception."
+   Returns the updated Metric or throws an Exception."
   [{:keys [id name definition revision_message], :as body} user-id]
   {:pre [(integer? id)
          (string? name)
@@ -139,18 +141,19 @@
          (string? revision_message)]}
   ;; update the metric itself
   (db/update! Metric id
-    (select-keys body #{:caveats :definition :description :how_is_this_calculated :name :points_of_interest :show_in_getting_started}))
+    (select-keys body #{:caveats :definition :description :how_is_this_calculated :name :points_of_interest
+                        :show_in_getting_started}))
   (u/prog1 (retrieve-metric id)
     (events/publish-event! :metric-update (assoc <> :actor_id user-id, :revision_message revision_message))))
 
 ;; TODO - rename to `delete!`
 (defn delete-metric!
-  "Delete a `Metric`.
+  "Delete a Metric.
 
-   This does a soft delete and simply marks the `Metric` as deleted but does not actually remove the
-   record from the database at any time.
+  This does a soft delete and simply marks the Metric as deleted but does not actually remove the record from the
+  database at any time.
 
-   Returns the final state of the `Metric` is successful, or throws an Exception."
+  Returns the final state of the Metric is successful, or throws an Exception."
   [id user-id revision-message]
   {:pre [(integer? id)
          (integer? user-id)
diff --git a/src/metabase/models/params.clj b/src/metabase/models/params.clj
index 209631b79b341520787579985fe1675bc81afcb3..c31acbc26fe10b82b5ef19c1985ebac0b7c960d3 100644
--- a/src/metabase/models/params.clj
+++ b/src/metabase/models/params.clj
@@ -7,7 +7,8 @@
             [metabase.query-processor.middleware.expand :as ql]
             [toucan
              [db :as db]
-             [hydrate :refer [hydrate]]])
+             [hydrate :refer [hydrate]]]
+            [metabase.mbql.util :as mbql.u])
   (:import metabase.query_processor.interface.FieldPlaceholder))
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
@@ -15,13 +16,35 @@
 ;;; +----------------------------------------------------------------------------------------------------------------+
 
 (defn field-form->id
-  "Expand a `field-id` or `fk->` FORM and return the ID of the Field it references.
+  "Expand a `field-id` or `fk->` FORM and return the ID of the Field it references. Also handles unwrapped integers.
 
      (field-form->id [:field-id 100])  ; -> 100"
   [field-form]
-  (when-let [field-placeholder (u/ignore-exceptions (ql/expand-ql-sexpr field-form))]
-    (when (instance? FieldPlaceholder field-placeholder)
-      (:field-id field-placeholder))))
+  (cond
+    (mbql.u/is-clause? :field-id field-form)
+    (second field-form)
+
+    (mbql.u/is-clause? :fk-> field-form)
+    (last field-form)
+
+    (integer? field-form)
+    field-form
+
+    :else
+    (throw (IllegalArgumentException. (str "Don't know what to do with: " field-form)))))
+
+(defn wrap-field-id-if-needed
+  "Wrap a raw Field ID in a `:field-id` clause if needed."
+  [field-id-or-form]
+  (cond
+    (mbql.u/mbql-clause? field-id-or-form)
+    field-id-or-form
+
+    (integer? field-id-or-form)
+    [:field-id field-id-or-form]
+
+    :else
+    (throw (IllegalArgumentException. (str "Don't know how to wrap:" field-id-or-form)))))
 
 (defn- field-ids->param-field-values
   "Given a collection of PARAM-FIELD-IDS return a map of FieldValues for the Fields they reference.
@@ -36,7 +59,7 @@
 
      (template-tag->field-form [:template-tag :company] some-dashcard) ; -> [:field-id 100]"
   [[_ tag] dashcard]
-  (get-in dashcard [:card :dataset_query :native :template_tags (keyword tag) :dimension]))
+  (get-in dashcard [:card :dataset_query :native :template-tags (u/keyword->qualified-name tag) :dimension]))
 
 (defn- param-target->field-id
   "Parse a Card parameter TARGET form, which looks something like `[:dimension [:field-id 100]]`, and return the Field
@@ -196,7 +219,7 @@
 (defn card->template-tag-field-ids
   "Return a set of Field IDs referenced in template tag parameters in CARD."
   [card]
-  (set (for [[_ {dimension :dimension}] (get-in card [:dataset_query :native :template_tags])
+  (set (for [[_ {dimension :dimension}] (get-in card [:dataset_query :native :template-tags])
              :when                      dimension
              :let                       [field-id (field-form->id dimension)]
              :when                      field-id]
diff --git a/src/metabase/models/permissions.clj b/src/metabase/models/permissions.clj
index f1a99b140c20c973bacb7944da66d4533154e40d..ad9331b2ce24867a80e1749539eec0d4ec285c80 100644
--- a/src/metabase/models/permissions.clj
+++ b/src/metabase/models/permissions.clj
@@ -5,16 +5,18 @@
             [clojure.core.match :refer [match]]
             [clojure.tools.logging :as log]
             [medley.core :as m]
+            [metabase
+             [config :as config]
+             [util :as u]]
             [metabase.api.common :refer [*current-user-id*]]
             [metabase.models
              [interface :as i]
              [permissions-group :as group]
              [permissions-revision :as perms-revision :refer [PermissionsRevision]]]
-            [metabase.util :as u]
             [metabase.util
              [honeysql-extensions :as hx]
+             [i18n :as ui18n :refer [tru]]
              [schema :as su]]
-            [puppetlabs.i18n.core :refer [tru]]
             [schema.core :as s]
             [toucan
              [db :as db]
@@ -36,22 +38,25 @@
    prevent accidental tragedy, but you can enable it here when creating the default entry for `Admin`."
   false)
 
+(def segmented-perm-regex
+  "Regex that matches a segmented permission"
+  #"^/db/(\d+)/schema/([^\\/]*)/table/(\d+)/query/segmented/$")
 
 ;;; --------------------------------------------------- Validation ---------------------------------------------------
 
 (def ^:private ^:const valid-object-path-patterns
-  [#"^/db/(\d+)/$"                                              ; permissions for the entire DB -- native and all schemas
-   #"^/db/(\d+)/native/$"                                       ; permissions to create new native queries for the DB
-   #"^/db/(\d+)/schema/$"                                       ; permissions for all schemas in the DB
-   #"^/db/(\d+)/schema/([^\\/]*)/$"                             ; permissions for a specific schema
-   #"^/db/(\d+)/schema/([^\\/]*)/table/(\d+)/$"                 ; FULL permissions for a specific table
-   #"^/db/(\d+)/schema/([^\\/]*)/table/(\d+)/read/$"            ; Permissions to fetch the Metadata for a specific Table
-   #"^/db/(\d+)/schema/([^\\/]*)/table/(\d+)/query/$"           ; Permissions to run any sort of query against a Table
-   #"^/db/(\d+)/schema/([^\\/]*)/table/(\d+)/query/segmented/$" ; Permissions to run a query against a Table using GTAP
-   #"^/collection/(\d+)/$"                                      ; readwrite permissions for a collection
-   #"^/collection/(\d+)/read/$"                                 ; read permissions for a collection
-   #"^/collection/root/$"                                       ; readwrite permissions for the 'Root' Collection (things with `nil` collection_id)
-   #"^/collection/root/read/$"])                                ; read permissions for the 'Root' Collection
+  [#"^/db/(\d+)/$"                                    ; permissions for the entire DB -- native and all schemas
+   #"^/db/(\d+)/native/$"                             ; permissions to create new native queries for the DB
+   #"^/db/(\d+)/schema/$"                             ; permissions for all schemas in the DB
+   #"^/db/(\d+)/schema/([^\\/]*)/$"                   ; permissions for a specific schema
+   #"^/db/(\d+)/schema/([^\\/]*)/table/(\d+)/$"       ; FULL permissions for a specific table
+   #"^/db/(\d+)/schema/([^\\/]*)/table/(\d+)/read/$"  ; Permissions to fetch the Metadata for a specific Table
+   #"^/db/(\d+)/schema/([^\\/]*)/table/(\d+)/query/$" ; Permissions to run any sort of query against a Table
+   segmented-perm-regex                               ; Permissions to run a query against a Table using GTAP
+   #"^/collection/(\d+)/$"                            ; readwrite permissions for a collection
+   #"^/collection/(\d+)/read/$"                       ; read permissions for a collection
+   #"^/collection/root/$"          ; readwrite permissions for the 'Root' Collection (things with `nil` collection_id)
+   #"^/collection/root/read/$"])   ; read permissions for the 'Root' Collection
 
 (defn valid-object-path?
   "Does OBJECT-PATH follow a known, allowed format to an *object*?
@@ -77,7 +82,7 @@
   [{:keys [group_id]}]
   (when (and (= group_id (:id (group/admin)))
              (not *allow-admin-permissions-changes*))
-    (throw (ex-info (tru "You cannot create or revoke permissions for the 'Admin' group.")
+    (throw (ui18n/ex-info (tru "You cannot create or revoke permissions for the ''Admin'' group.")
              {:status-code 400}))))
 
 (defn- assert-valid-object
@@ -87,7 +92,7 @@
              (not (valid-object-path? object))
              (or (not= object "/")
                  (not *allow-root-entries*)))
-    (throw (ex-info (tru "Invalid permissions object path: ''{0}''." object)
+    (throw (ui18n/ex-info (tru "Invalid permissions object path: ''{0}''." object)
              {:status-code 400}))))
 
 (defn- assert-valid
@@ -103,7 +108,9 @@
   (s/cond-pre su/Map su/IntGreaterThanZero))
 
 (s/defn object-path :- ObjectPath
-  "Return the permissions path for a Database, schema, or Table."
+  "Return the [readwrite] permissions path for a Database, schema, or Table. (At the time of this writing, DBs and
+  schemas don't have separate `read/` and write permissions; you either have 'data access' permissions for them, or
+  you don't. Tables, however, have separate read and write perms.)"
   ([database-or-id :- MapOrID]
    (str "/db/" (u/get-id database-or-id) "/"))
   ([database-or-id :- MapOrID, schema-name :- (s/maybe s/Str)]
@@ -123,7 +130,7 @@
   (str (object-path database-or-id) "schema/"))
 
 (s/defn collection-readwrite-path :- ObjectPath
-  "Return the permissions path for *readwrite* access for a COLLECTION-OR-ID."
+  "Return the permissions path for *readwrite* access for a `collection-or-id`."
   [collection-or-id :- MapOrID]
   (str "/collection/"
        (if (get collection-or-id :metabase.models.collection/is-root?)
@@ -132,7 +139,7 @@
        "/"))
 
 (s/defn collection-read-path :- ObjectPath
-  "Return the permissions path for *read* access for a COLLECTION-OR-ID."
+  "Return the permissions path for *read* access for a `collection-or-id`."
   [collection-or-id :- MapOrID]
   (str (collection-readwrite-path collection-or-id) "read/"))
 
@@ -362,11 +369,26 @@
 
 ;;; --------------------------------------------------- Helper Fns ---------------------------------------------------
 
-;; TODO - why does this take a PATH when everything else takes PATH-COMPONENTS or IDs?
-(s/defn delete-related-permissions!
-  "Delete all permissions for `group-or-id` for ancestors or descendant objects of object with `path`.
-   You can optionally include `other-conditions`, which are anded into the filter clause, to further restrict what is
-   deleted."
+(s/defn ^:private delete-related-permissions!
+  "This is somewhat hard to explain, but I will do my best:
+
+  Delete all 'related' permissions for `group-or-id` (i.e., perms that grant you full or partial access to `path`).
+  This includes *both* ancestor and descendant paths. For example:
+
+  Suppose we asked this functions to delete related permssions for `/db/1/schema/PUBLIC/`. Dependning on the
+  permissions the group has, it could end up doing something like:
+
+    *  deleting `/db/1/` permissions (because the ancestor perms implicity grant you full perms for `schema/PUBLIC`)
+    *  deleting perms for `/db/1/schema/PUBLIC/table/2/` (because Table 2 is a descendant of `schema/PUBLIC`)
+
+  In short, it will delete any permissions that contain `/db/1/schema/` as a prefix, or that themeselves are prefixes
+  for `/db/1/schema/`.
+
+  You can optionally include `other-conditions`, which are anded into the filter clause, to further restrict what is
+  deleted.
+
+  NOTE: This function is meant for internal usage in this namespace only; use one of the other functions like
+  `revoke-permissions!` elsewhere instead of calling this directly."
   {:style/indent 2}
   [group-or-id :- (s/cond-pre su/Map su/IntGreaterThanZero), path :- ObjectPath, & other-conditions]
   (let [where {:where (apply list
@@ -381,12 +403,19 @@
       (db/delete! Permissions where))))
 
 (defn revoke-permissions!
-  "Revoke all permissions for GROUP-OR-ID to object with PATH-COMPONENTS, *including* related permissions."
+  "Revoke all permissions for `group-or-id` to object with `path-components`, *including* related permissions (i.e,
+  permissions that grant full or partial access to the object in question).
+
+
+    (revoke-permissions! my-group my-db)"
+  {:arglists '([group-id database-or-id]
+               [group-id database-or-id schema-name]
+               [group-id database-or-id schema-name table-or-id])}
   [group-or-id & path-components]
   (delete-related-permissions! group-or-id (apply object-path path-components)))
 
 (defn grant-permissions!
-  "Grant permissions to GROUP-OR-ID to an object."
+  "Grant permissions to `group-or-id` to an object."
   ([group-or-id db-id schema & more]
    (grant-permissions! group-or-id (apply object-path db-id schema more)))
   ([group-or-id path]
@@ -396,15 +425,20 @@
        :object   path)
      ;; on some occasions through weirdness we might accidentally try to insert a key that's already been inserted
      (catch Throwable e
-       (log/error (u/format-color 'red "Failed to grant permissions: %s" (.getMessage e)))))))
+       (log/error (u/format-color 'red (tru "Failed to grant permissions: {0}" (.getMessage e))))
+       ;; if we're running tests, we're doing something wrong here if duplicate permissions are getting assigned,
+       ;; mostly likely because tests aren't properly cleaning up after themselves, and possibly causing other tests
+       ;; to pass when they shouldn't. Don't allow this during tests
+       (when config/is-test?
+         (throw e))))))
 
 (defn revoke-native-permissions!
-  "Revoke all native query permissions for GROUP-OR-ID to database with DATABASE-ID."
+  "Revoke all native query permissions for `group-or-id` to database with `database-id`."
   [group-or-id database-id]
   (delete-related-permissions! group-or-id (adhoc-native-query-path database-id)))
 
 (defn grant-native-readwrite-permissions!
-  "Grant full readwrite permissions for GROUP-OR-ID to database with DATABASE-ID."
+  "Grant full readwrite permissions for `group-or-id` to database with `database-id`."
   [group-or-id database-id]
   (grant-permissions! group-or-id (adhoc-native-query-path database-id)))
 
@@ -524,8 +558,8 @@
    Return a 409 (Conflict) if the numbers don't match up."
   [old-graph new-graph]
   (when (not= (:revision old-graph) (:revision new-graph))
-    (throw (ex-info (str (tru "Looks like someone else edited the permissions and your data is out of date.")
-                         (tru "Please fetch new data and try again."))
+    (throw (ui18n/ex-info (str (tru "Looks like someone else edited the permissions and your data is out of date.")
+                               (tru "Please fetch new data and try again."))
              {:status-code 409}))))
 
 (defn- save-perms-revision!
diff --git a/src/metabase/models/permissions_group.clj b/src/metabase/models/permissions_group.clj
index 34621c2193e818af21896541cdd826710c5a13f9..bc3072e0897932bd61520057df2eda5cd9a052b0 100644
--- a/src/metabase/models/permissions_group.clj
+++ b/src/metabase/models/permissions_group.clj
@@ -10,7 +10,7 @@
             [clojure.tools.logging :as log]
             [metabase.models.setting :as setting]
             [metabase.util :as u]
-            [puppetlabs.i18n.core :refer [trs tru]]
+            [metabase.util.i18n :as ui18n :refer [trs tru]]
             [toucan
              [db :as db]
              [models :as models]]))
@@ -26,8 +26,8 @@
                    :name group-name)
                  (u/prog1 (db/insert! PermissionsGroup
                             :name group-name)
-                          (log/info (u/format-color 'green (trs "Created magic permissions group ''{0}'' (ID = {1})"
-                                                                group-name (:id <>)))))))))
+                   (log/info (u/format-color 'green (trs "Created magic permissions group ''{0}'' (ID = {1})"
+                                                         group-name (:id <>)))))))))
 
 (def ^{:arglists '([])} ^metabase.models.permissions_group.PermissionsGroupInstance
   all-users
@@ -57,7 +57,7 @@
 (defn- check-name-not-already-taken
   [group-name]
   (when (exists-with-name? group-name)
-    (throw (ex-info (tru "A group with that name already exists.") {:status-code 400}))))
+    (throw (ui18n/ex-info (tru "A group with that name already exists.") {:status-code 400}))))
 
 (defn- check-not-magic-group
   "Make sure we're not trying to edit/delete one of the magic groups, or throw an exception."
@@ -67,7 +67,7 @@
                        (admin)
                        (metabot)]]
     (when (= id (:id magic-group))
-      (throw (ex-info (tru "You cannot edit or delete the ''{0}'' permissions group!" (:name magic-group))
+      (throw (ui18n/ex-info (tru "You cannot edit or delete the ''{0}'' permissions group!" (:name magic-group))
                {:status-code 400})))))
 
 
diff --git a/src/metabase/models/permissions_group_membership.clj b/src/metabase/models/permissions_group_membership.clj
index 87e90ddd1279fe43655b6c9e67ac7b626cb3a48f..83bf95456c4c91de6e0837dbcc5bc11dc71d0a59 100644
--- a/src/metabase/models/permissions_group_membership.clj
+++ b/src/metabase/models/permissions_group_membership.clj
@@ -1,7 +1,7 @@
 (ns metabase.models.permissions-group-membership
   (:require [metabase.models.permissions-group :as group]
             [metabase.util :as u]
-            [puppetlabs.i18n.core :refer [tru]]
+            [metabase.util.i18n :as ui18n :refer [tru]]
             [toucan
              [db :as db]
              [models :as models]]))
@@ -12,8 +12,8 @@
   "Throw an Exception if we're trying to add or remove a user to the MetaBot group."
   [group-id]
   (when (= group-id (:id (group/metabot)))
-    (throw (ex-info (tru "You cannot add or remove users to/from the 'MetaBot' group.")
-                    {:status-code 400}))))
+    (throw (ui18n/ex-info (tru "You cannot add or remove users to/from the ''MetaBot'' group.")
+             {:status-code 400}))))
 
 (def ^:dynamic ^Boolean *allow-changing-all-users-group-members*
   "Should we allow people to be added to or removed from the All Users permissions group?
@@ -25,14 +25,14 @@
   [group-id]
   (when (= group-id (:id (group/all-users)))
     (when-not *allow-changing-all-users-group-members*
-      (throw (ex-info (tru "You cannot add or remove users to/from the 'All Users' group.")
+      (throw (ui18n/ex-info (tru "You cannot add or remove users to/from the ''All Users'' group.")
                {:status-code 400})))))
 
 (defn- check-not-last-admin []
   (when (<= (db/count PermissionsGroupMembership
               :group_id (:id (group/admin)))
             1)
-    (throw (ex-info (tru "You cannot remove the last member of the 'Admin' group!")
+    (throw (ui18n/ex-info (tru "You cannot remove the last member of the ''Admin'' group!")
              {:status-code 400}))))
 
 (defn- pre-delete [{:keys [group_id user_id]}]
diff --git a/src/metabase/models/permissions_revision.clj b/src/metabase/models/permissions_revision.clj
index 07ea81ff83b71bde58be7cfd7555144581b356a5..d28f3c207c904681185e050ca7164c48f569824f 100644
--- a/src/metabase/models/permissions_revision.clj
+++ b/src/metabase/models/permissions_revision.clj
@@ -1,7 +1,8 @@
 (ns metabase.models.permissions-revision
   (:require [metabase.util :as u]
-            [metabase.util.date :as du]
-            [puppetlabs.i18n.core :refer [tru]]
+            [metabase.util
+             [date :as du]
+             [i18n :refer [tru]]]
             [toucan
              [db :as db]
              [models :as models]]))
diff --git a/src/metabase/models/pulse.clj b/src/metabase/models/pulse.clj
index b3cefa148d77d14c7560b22e18e2716ff9888a34..d3ca3f0e4d293028e963ed6cbdc44c2221949ed0 100644
--- a/src/metabase/models/pulse.clj
+++ b/src/metabase/models/pulse.clj
@@ -1,5 +1,5 @@
 (ns metabase.models.pulse
-  "Notifications are ways to deliver the results of Questions to users without going through the normal Metabase UI. At
+  "Notifcations are ways to deliver the results of Questions to users without going through the normal Metabase UI. At
   the time of this writing, there are two delivery mechanisms for Notifications -- email and Slack notifications;
   these destinations are known as 'Channels'. Notifications themselves are futher divied into two categories --
   'Pulses', which are sent at specified intervals, and 'Alerts', which are sent when certain conditions are met (such
@@ -27,8 +27,9 @@
              [pulse-card :refer [PulseCard]]
              [pulse-channel :as pulse-channel :refer [PulseChannel]]
              [pulse-channel-recipient :refer [PulseChannelRecipient]]]
-            [metabase.util.schema :as su]
-            [puppetlabs.i18n.core :refer [tru]]
+            [metabase.util
+             [i18n :refer [tru]]
+             [schema :as su]]
             [schema.core :as s]
             [toucan
              [db :as db]
@@ -51,7 +52,9 @@
    (:card alert)
    ;; otherwise fetch the associated `:cards` (if not already fetched) and then pull the first one out, since Alerts
    ;; can only have one Card
-   (-> (hydrate alert :cards) :cards first)))
+   (-> (hydrate alert :cards) :cards first)
+   ;; if there's still not a Card, throw an Exception!
+   (throw (Exception. (str (tru "Invalid Alert: Alert does not have a Card assoicated with it"))))))
 
 (defn- perms-objects-set
   "Permissions to read or write a *Pulse* are the same as those of its parent Collection.
@@ -159,8 +162,8 @@
 
 (s/defn retrieve-notification :- (s/maybe PulseInstance)
   "Fetch an Alert or Pulse, and do the 'standard' hydrations."
-  [notification-or-id]
-  (-> (Pulse (u/get-id notification-or-id))
+  [notification-or-id & additional-condtions]
+  (-> (apply Pulse :id (u/get-id notification-or-id), additional-condtions)
       hydrate-notification))
 
 (s/defn ^:private notification->alert :- PulseInstance
@@ -180,25 +183,31 @@
 
 (s/defn retrieve-alerts :- [PulseInstance]
   "Fetch all Alerts."
-  []
-  (for [alert (db/select Pulse, :alert_condition [:not= nil], {:order-by [[:%lower.name :asc]]})]
-    (-> alert
-        hydrate-notification
-        notification->alert)))
+  ([]
+   (retrieve-alerts nil))
+  ([{:keys [archived?]
+     :or   {archived? false}}]
+   (for [alert (db/select Pulse, :alert_condition [:not= nil], :archived archived?, {:order-by [[:%lower.name :asc]]})]
+     (-> alert
+         hydrate-notification
+         notification->alert))))
 
 (s/defn retrieve-pulses :- [PulseInstance]
   "Fetch all `Pulses`."
-  []
-  (for [pulse (db/select Pulse, :alert_condition nil, {:order-by [[:%lower.name :asc]]})]
-    (-> pulse
-        hydrate-notification
-        notification->pulse)))
+  ([]
+   (retrieve-pulses nil))
+  ([{:keys [archived?]
+     :or   {archived? false}}]
+   (for [pulse (db/select Pulse, :alert_condition nil, :archived archived?, {:order-by [[:%lower.name :asc]]})]
+     (-> pulse
+         hydrate-notification
+         notification->pulse))))
 
 (defn- query-as [model query]
   (db/do-post-select model (db/query query)))
 
 (defn retrieve-user-alerts-for-card
-  "Find all alerts for `CARD-ID` that `USER-ID` is set to receive"
+  "Find all alerts for `card-id` that `user-id` is set to receive"
   [card-id user-id]
   (map (comp notification->alert hydrate-notification)
        (query-as Pulse
@@ -364,15 +373,18 @@
                     (s/optional-key :collection_id)       (s/maybe su/IntGreaterThanZero)
                     (s/optional-key :collection_position) (s/maybe su/IntGreaterThanZero)
                     (s/optional-key :cards)               [CoercibleToCardRef]
-                    (s/optional-key :channels)            [su/Map]}]
+                    (s/optional-key :channels)            [su/Map]
+                    (s/optional-key :archived)            s/Bool}]
   (db/update! Pulse (u/get-id notification)
     (u/select-keys-when notification
-      :present [:collection_id :collection_position]
+      :present [:collection_id :collection_position :archived]
       :non-nil [:name :alert_condition :alert_above_goal :alert_first_only :skip_if_empty]))
   ;; update Cards if the 'refs' have changed
-  (update-notification-cards-if-changed! notification (map card->ref (:cards notification)))
+  (when (contains? notification :cards)
+    (update-notification-cards-if-changed! notification (map card->ref (:cards notification))))
   ;; update channels as needed
-  (update-notification-channels! notification (:channels notification)))
+  (when (contains? notification :channels)
+    (update-notification-channels! notification (:channels notification))))
 
 (s/defn update-pulse!
   "Update an existing Pulse, including all associated data such as: PulseCards, PulseChannels, and
diff --git a/src/metabase/models/query.clj b/src/metabase/models/query.clj
index d45694dbdd871b23a86bd58f9394f099e1837aca..7344ccd613fe0438e92863af31db3ba93c9eec4e 100644
--- a/src/metabase/models/query.clj
+++ b/src/metabase/models/query.clj
@@ -1,7 +1,6 @@
 (ns metabase.models.query
   "Functions related to the 'Query' model, which records stuff such as average query execution time."
   (:require [metabase.db :as mdb]
-            [metabase.query-processor.util :as qputil]
             [metabase.util.honeysql-extensions :as hx]
             [toucan
              [db :as db]
@@ -59,24 +58,21 @@
               ;; rethrow e if updating an existing average execution time failed
               (throw e))))))
 
-
-(def ^:private ^{:arglists '([query-type])} native-query?
-  (comp #{:native} qputil/normalize-token))
+;; TODO - somewhat confusing that the Ad-hoc queries here use the keys `:table-id` and `:database-id` instead of the
+;; `:database_id` and `:table_id` that come out of the Database. In the automagic dashboards code, for example, we
+;; have special utility functions to account for both possiblities. Should we fix this?
 
 (defn query->database-and-table-ids
   "Return a map with `:database-id` and source `:table-id` that should be saved for a Card. Handles queries that use
    other queries as their source (ones that come in with a `:source-table` like `card__100`) recursively, as well as
    normal queries."
-  [outer-query]
-  (let [database-id  (qputil/get-normalized outer-query :database)
-        query-type   (qputil/get-normalized outer-query :type)
-        source-table (qputil/get-in-normalized outer-query [:query :source-table])]
-    (cond
-      (native-query? query-type) {:database-id database-id, :table-id nil}
-      (integer? source-table)    {:database-id database-id, :table-id source-table}
-      (string? source-table)     (let [[_ card-id] (re-find #"^card__(\d+)$" source-table)]
-                                   (db/select-one ['Card [:table_id :table-id] [:database_id :database-id]]
-                                     :id (Integer/parseInt card-id))))))
+  [{database-id :database, query-type :type, {:keys [source-table]} :query}]
+  (cond
+    (= :native query-type)  {:database-id database-id, :table-id nil}
+    (integer? source-table) {:database-id database-id, :table-id source-table}
+    (string? source-table)  (let [[_ card-id] (re-find #"^card__(\d+)$" source-table)]
+                              (db/select-one ['Card [:table_id :table-id] [:database_id :database-id]]
+                                :id (Integer/parseInt card-id)))))
 
 (defn adhoc-query
   "Wrap query map into a Query object (mostly to fascilitate type dispatch)."
diff --git a/src/metabase/models/query/permissions.clj b/src/metabase/models/query/permissions.clj
index 5bb219184acd0f3d1be96eff9b1b1efc8be2590c..e8f7f5896d8eb995c45e72ba09a077070066c296 100644
--- a/src/metabase/models/query/permissions.clj
+++ b/src/metabase/models/query/permissions.clj
@@ -8,8 +8,9 @@
              [permissions :as perms]]
             [metabase.query-processor.util :as qputil]
             [metabase.util :as u]
-            [metabase.util.schema :as su]
-            [puppetlabs.i18n.core :refer [tru]]
+            [metabase.util
+             [i18n :refer [tru]]
+             [schema :as su]]
             [schema.core :as s]
             [toucan.db :as db]))
 
diff --git a/src/metabase/models/query_execution.clj b/src/metabase/models/query_execution.clj
index 4fda12dd6fe27f78de5412dbf33d54592963fa05..90f9ac872bde249c159161e55ca445cec79906fb 100644
--- a/src/metabase/models/query_execution.clj
+++ b/src/metabase/models/query_execution.clj
@@ -1,37 +1,20 @@
 (ns metabase.models.query-execution
+  "QueryExecution is a log of very time a query is executed, and other information such as the User who executed it, run
+  time, context it was executed in, etc."
   (:require [metabase.util :as u]
-            [puppetlabs.i18n.core :refer [tru]]
+            [metabase.util.i18n :refer [tru]]
+            [metabase.mbql.schema :as mbql.s]
             [schema.core :as s]
             [toucan.models :as models]))
 
 (models/defmodel QueryExecution :query_execution)
 
-(def ^:dynamic ^Boolean *validate-context*
-  "Whether we should validate the values of `context` for QueryExecutions when INSERTing them.
-   (In normal usage, this should always be `true`, but this switch is provided so we can migrating
-   legacy QueryExecution entries, which have no `context` information)."
-  true)
-
-(def Context
-  "Schema for valid values of QueryExecution `:context`."
-  (s/enum :ad-hoc
-          :csv-download
-          :dashboard
-          :embedded-dashboard
-          :embedded-question
-          :json-download
-          :map-tiles
-          :metabot
-          :public-dashboard
-          :public-question
-          :pulse
-          :question
-          :xlsx-download))
+(def ^:private ^{:arglists '([context])} validate-context
+  (s/validator mbql.s/Context))
 
 (defn- pre-insert [{context :context, :as query-execution}]
   (u/prog1 query-execution
-    (when *validate-context*
-      (s/validate Context context))))
+    (validate-context context)))
 
 (defn- post-select [{:keys [result_rows] :as query-execution}]
   ;; sadly we have 2 ways to reference the row count :(
diff --git a/src/metabase/models/revision.clj b/src/metabase/models/revision.clj
index c79fd82e03c8938093b60915c001399394fb0430..35663b0fa02757ff84f13668fcf05732c46ea255 100644
--- a/src/metabase/models/revision.clj
+++ b/src/metabase/models/revision.clj
@@ -3,8 +3,9 @@
             [metabase.models.revision.diff :refer [diff-string]]
             [metabase.models.user :refer [User]]
             [metabase.util :as u]
-            [metabase.util.date :as du]
-            [puppetlabs.i18n.core :refer [tru]]
+            [metabase.util
+             [date :as du]
+             [i18n :refer [tru]]]
             [toucan
              [db :as db]
              [hydrate :refer [hydrate]]
@@ -66,12 +67,24 @@
 (defn- pre-insert [revision]
   (assoc revision :timestamp (du/new-sql-timestamp)))
 
+(defn- do-post-select-for-object
+  "Call the appropriate `post-select` methods (including the type functions) on the `:object` this Revision recorded.
+  This is important for things like Card revisions, where the `:dataset_query` property needs to be normalized when
+  coming out of the DB."
+  [{:keys [model], :as revision}]
+  ;; in some cases (such as tests) we have 'fake' models that cannot be resolved normally; don't fail entirely in
+  ;; those cases
+  (let [model (u/ignore-exceptions (db/resolve-model (symbol model)))]
+    (cond-> revision
+      model (update :object (partial models/do-post-select model)))))
+
 (u/strict-extend (class Revision)
   models/IModel
   (merge models/IModelDefaults
-         {:types      (constantly {:object :json, :message :clob})
-          :pre-insert pre-insert
-          :pre-update (fn [& _] (throw (Exception. (str (tru "You cannot update a Revision!")))))}))
+         {:types       (constantly {:object :json, :message :clob})
+          :pre-insert  pre-insert
+          :pre-update  (fn [& _] (throw (Exception. (str (tru "You cannot update a Revision!")))))
+          :post-select do-post-select-for-object}))
 
 
 ;;; # Functions
@@ -91,8 +104,7 @@
 (defn revisions
   "Get the revisions for ENTITY with ID in reverse chronological order."
   [entity id]
-  {:pre [(models/model? entity)
-         (integer? id)]}
+  {:pre [(models/model? entity) (integer? id)]}
   (db/select Revision, :model (:name entity), :model_id id, {:order-by [[:id :desc]]}))
 
 (defn revisions+details
@@ -109,7 +121,10 @@
   "Delete old revisions of ENTITY with ID when there are more than `max-revisions` in the DB."
   [entity id]
   {:pre [(models/model? entity) (integer? id)]}
-  (when-let [old-revisions (seq (drop max-revisions (map :id (db/select [Revision :id], :model (:name entity), :model_id id, {:order-by [[:timestamp :desc]]}))))]
+  (when-let [old-revisions (seq (drop max-revisions (map :id (db/select [Revision :id]
+                                                               :model    (:name entity)
+                                                               :model_id id
+                                                               {:order-by [[:timestamp :desc]]}))))]
     (db/delete! Revision :id [:in old-revisions])))
 
 (defn push-revision!
diff --git a/src/metabase/models/segment.clj b/src/metabase/models/segment.clj
index e279bca237b0d935c0b007179b964f97e968bf7b..e792a21a989744bb58424e3aeb75902f2e3fc6b5 100644
--- a/src/metabase/models/segment.clj
+++ b/src/metabase/models/segment.clj
@@ -1,4 +1,6 @@
 (ns metabase.models.segment
+  "A Segment is a saved MBQL 'macro', expanding to a `:filter` subclause. It is passed in as a `:filter` subclause but is
+  replaced by the `expand-macros` middleware with the appropriate clauses."
   (:require [medley.core :as m]
             [metabase
              [events :as events]
@@ -21,7 +23,7 @@
 (u/strict-extend (class Segment)
   models/IModel
   (merge models/IModelDefaults
-         {:types          (constantly {:definition :json, :description :clob})
+         {:types          (constantly {:definition :metric-segment-definition, :description :clob})
           :properties     (constantly {:timestamped? true})
           :hydration-keys (constantly [:segment])})
   i/IObjectPermissions
@@ -108,7 +110,9 @@
 (defn update-segment!
   "Update an existing `Segment`.
    Returns the updated `Segment` or throws an Exception."
-  [{:keys [id name description caveats points_of_interest show_in_getting_started definition revision_message], :as body} user-id]
+  [{:keys [id name description caveats points_of_interest show_in_getting_started definition revision_message]
+    :as   body}
+   user-id]
   {:pre [(integer? id)
          (string? name)
          (map? definition)
diff --git a/src/metabase/models/setting.clj b/src/metabase/models/setting.clj
index fd80041abc20e55e337e9604edb7993e25d9db8b..2b7213bd0bace7e7e240f3378e832b8bc6392924 100644
--- a/src/metabase/models/setting.clj
+++ b/src/metabase/models/setting.clj
@@ -30,13 +30,22 @@
       (setting/all)"
   (:refer-clojure :exclude [get])
   (:require [cheshire.core :as json]
-            [clojure.string :as str]
+            [clojure
+             [core :as core]
+             [string :as str]]
+            [clojure.core.memoize :as memoize]
+            [clojure.java.jdbc :as jdbc]
             [clojure.tools.logging :as log]
             [environ.core :as env]
+            [honeysql.core :as hsql]
             [metabase
+             [db :as mdb]
              [events :as events]
              [util :as u]]
-            [puppetlabs.i18n.core :refer [tru]]
+            [metabase.util
+             [date :as du]
+             [honeysql-extensions :as hx]
+             [i18n :as ui18n :refer [trs tru]]]
             [schema.core :as s]
             [toucan
              [db :as db]
@@ -49,20 +58,31 @@
 (u/strict-extend (class Setting)
   models/IModel
   (merge models/IModelDefaults
-         {:types (constantly {:value :clob})}))
+         {:types (constantly {:value :encrypted-text})}))
 
 
 (def ^:private Type
-  (s/enum :string :boolean :json :integer :double))
+  (s/enum :string :boolean :json :integer :double :timestamp))
+
+(def ^:private default-tag-for-type
+  "Type tag that will be included in the Setting's metadata, so that the getter function will not cause reflection
+  warnings."
+  {:string    String
+   :boolean   Boolean
+   :integer   Long
+   :double    Double
+   :timestamp java.sql.Timestamp})
 
 (def ^:private SettingDefinition
   {:name        s/Keyword
-   :description s/Str            ; used for docstring and is user-facing in the admin panel
+   :description s/Any            ; description is validated via the macro, not schema
    :default     s/Any
    :type        Type             ; all values are stored in DB as Strings,
    :getter      clojure.lang.IFn ; different getters/setters take care of parsing/unparsing
    :setter      clojure.lang.IFn
-   :internal?   s/Bool})         ; should the API never return this setting? (default: false)
+   :tag         (s/maybe Class)  ; type annotation, e.g. ^String, to be applied. Defaults to tag based on :type
+   :internal?   s/Bool           ; should the API never return this setting? (default: false)
+   :cache?      s/Bool})         ; should the getter always fetch this value "fresh" from the DB? (default: false)
 
 
 (defonce ^:private registered-settings
@@ -74,7 +94,8 @@
     setting-or-name
     (let [k (keyword setting-or-name)]
       (or (@registered-settings k)
-          (throw (Exception. (str (tru "Setting {0} does not exist.\nFound: {1}" k (sort (keys @registered-settings))))))))))
+          (throw (Exception.
+                  (str (tru "Setting {0} does not exist.\nFound: {1}" k (sort (keys @registered-settings))))))))))
 
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
@@ -84,12 +105,119 @@
 ;; Cache is a 1:1 mapping of what's in the DB
 ;; Cached lookup time is ~60µs, compared to ~1800µs for DB lookup
 
-(defonce ^:private cache
+(def ^:private cache
+  "Settings cache. Map of Setting key (string) -> Setting value (string)."
   (atom nil))
 
-(defn- restore-cache-if-needed! []
-  (when-not @cache
-    (reset! cache (db/select-field->field :key :value Setting))))
+;; CACHE SYNCHRONIZATION
+;;
+;; When running multiple Metabase instances (horizontal scaling), it is of course possible for one instance to update
+;; a Setting, and, since Settings are cached (to avoid tons of DB calls), for the other instances to then have an
+;; out-of-date cache. Thus we need a way for instances to know when their caches are out of date, so they can update
+;; them accordingly. Here is our solution:
+;;
+;; We will record the last time *any* Setting was updated in a special Setting called `settings-last-updated`.
+;;
+;; Since `settings-last-updated` itself is a Setting, it will get fetched as part of each instance's local cache; we
+;; can then periodically compare the locally cached value of `settings-last-updated` with the value in the DB. If our
+;; locally cached value is older than the one in the DB, we will flush our cache. When the cache is fetched again, it
+;; will have the up-to-date value.
+;;
+;; Because different machines can have out-of-sync clocks, we'll rely entirely on the application DB for caclulating
+;; and comparing values of `settings-last-updated`. Because the Setting table itself only stores text values, we'll
+;; need to cast it between TEXT and TIMESTAMP SQL types as needed.
+
+(def ^:private ^String settings-last-updated-key "settings-last-updated")
+
+(defn- update-settings-last-updated!
+  "Update the value of `settings-last-updated` in the DB; if the row does not exist, insert one."
+  []
+  (log/debug (trs "Updating value of settings-last-updated in DB..."))
+  ;; for MySQL, cast(current_timestamp AS char); for H2 & Postgres, cast(current_timestamp AS text)
+  (let [current-timestamp-as-string-honeysql (hx/cast (if (= (mdb/db-type) :mysql) :char :text)
+                                                      (hsql/raw "current_timestamp"))]
+    ;; attempt to UPDATE the existing row. If no row exists, `update-where!` will return false...
+    (or (db/update-where! Setting {:key settings-last-updated-key} :value current-timestamp-as-string-honeysql)
+        ;; ...at which point we will try to INSERT a new row. Note that it is entirely possible two instances can both
+        ;; try to INSERT it at the same time; one instance would fail because it would violate the PK constraint on
+        ;; `key`, and throw a SQLException. As long as one instance updates the value, we are fine, so we can go ahead
+        ;; and ignore that Exception if one is thrown.
+        (try
+          ;; Use `simple-insert!` because we do *not* want to trigger pre-insert behavior, such as encrypting `:value`
+          (db/simple-insert! Setting :key settings-last-updated-key, :value current-timestamp-as-string-honeysql)
+          (catch java.sql.SQLException e
+            ;; go ahead and log the Exception anyway on the off chance that it *wasn't* just a race condition issue
+            (log/error (trs "Error inserting a new Setting: {0}"
+                            (with-out-str (jdbc/print-sql-exception-chain e))))))))
+  ;; Now that we updated the value in the DB, go ahead and update our cached value as well, because we know about the
+  ;; changes
+  (swap! cache assoc settings-last-updated-key (db/select-one-field :value Setting :key settings-last-updated-key)))
+
+(defn- cache-out-of-date?
+  "Check whether our Settings cache is out of date. We know the cache is out of date if either of the following
+  conditions is true:
+
+   *  The cache is empty (the `cache` atom is `nil`), which of course means it needs to be updated
+   *  There is a value of `settings-last-updated` in the cache, and it is older than the value of in the DB. (There
+      will be no value until the first time a normal Setting is updated; thus if it is not yet set, we do not yet need
+      to invalidate our cache.)"
+  []
+  (log/debug (trs "Checking whether settings cache is out of date (requires DB call)..."))
+  (boolean
+   (or
+    ;; is the cache empty?
+    (not @cache)
+    ;; if not, get the cached value of `settings-last-updated`, and if it exists...
+    (when-let [last-known-update (core/get @cache settings-last-updated-key)]
+      ;; compare it to the value in the DB. This is done be seeing whether a row exists
+      ;; WHERE value > <local-value>
+      (u/prog1 (db/select-one Setting
+                 {:where [:and
+                          [:= :key settings-last-updated-key]
+                          [:> :value last-known-update]]})
+        (when <>
+          (log/info (u/format-color 'red
+                        (str (trs "Settings have been changed on another instance, and will be reloaded here."))))))))))
+
+(def ^:private cache-update-check-interval-ms
+  "How often we should check whether the Settings cache is out of date (which requires a DB call)?"
+  ;; once a minute
+  (* 60 1000))
+
+(def ^:private ^{:arglists '([])} should-restore-cache?
+  "TTL-memoized version of `cache-out-of-date?`. Call this function to see whether we need to repopulate the cache with
+  values from the DB."
+  (memoize/ttl cache-out-of-date? :ttl/threshold cache-update-check-interval-ms))
+
+(def ^:private restore-cache-if-needed-lock (Object.))
+
+(defn- restore-cache-if-needed!
+  "Check whether we need to repopulate the cache with fresh values from the DB (because the cache is either empty or
+  known to be out-of-date), and do so if needed. This is intended to be called every time a Setting value is
+  retrieved, so it should be efficient; thus the calculation (`should-restore-cache?`) is itself TTL-memoized."
+  []
+  ;; There's a potential race condition here where two threads both call this at the exact same moment, and both get
+  ;; `true` when they call `should-restore-cache`, and then both simultaneously try to update the cache (or, one
+  ;; updates the cache, but the other calls `should-restore-cache?` and gets `true` before the other calls
+  ;; `memo-swap!` (see below))
+  ;;
+  ;; This is not desirable, since either situation would result in duplicate work. Better to just add a quick lock
+  ;; here so only one of them does it, since at any rate waiting for the other thread to finish the task in progress is
+  ;; certainly quicker than starting the task ourselves from scratch
+  (locking restore-cache-if-needed-lock
+    (when (should-restore-cache?)
+      (log/debug (trs "Refreshing Settings cache..."))
+      (reset! cache (db/select-field->field :key :value Setting))
+      ;; Now the cache is up-to-date. That is all good, but if we call `should-restore-cache?` again in a second it
+      ;; will still return `true`, because its result is memoized, and we would be on the hook to (again) update the
+      ;; cache. So go ahead and clear the memozied results for `should-restore-cache?`. The next time around when
+      ;; someone calls this it will cache the latest value (which should be `false`)
+      ;;
+      ;; NOTE: I tried using `memo-swap!` instead to set the cached response to `false` here, avoiding the extra DB
+      ;; call the next fn call would make, but it didn't seem to work correctly (I think it was still discarding the
+      ;; new value because of the TTL). So we will just stick with `memo-clear!` for now. (One extra DB call whenever
+      ;; the cache gets invalidated shouldn't be a huge deal)
+      (memoize/memo-clear! should-restore-cache?))))
 
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
@@ -100,14 +228,14 @@
   (name (:name (resolve-setting setting-or-name))))
 
 (defn- env-var-name
-  "Get the env var corresponding to SETTING-OR-NAME.
+  "Get the env var corresponding to `setting-or-name`.
    (This is used primarily for documentation purposes)."
   ^String [setting-or-name]
   (let [setting (resolve-setting setting-or-name)]
     (str "MB_" (str/upper-case (str/replace (setting-name setting) "-" "_")))))
 
 (defn env-var-value
-  "Get the value of SETTING-OR-NAME from the corresponding env var, if any.
+  "Get the value of `setting-or-name` from the corresponding env var, if any.
    The name of the Setting is converted to uppercase and dashes to underscores;
    for example, a setting named `default-domain` can be set with the env var `MB_DEFAULT_DOMAIN`."
   ^String [setting-or-name]
@@ -116,15 +244,20 @@
     (when (seq v)
       v)))
 
+(def ^:private ^:dynamic *disable-cache* false)
+
 (defn- db-value
-  "Get the value, if any, of SETTING-OR-NAME from the DB (using / restoring the cache as needed)."
+  "Get the value, if any, of `setting-or-name` from the DB (using / restoring the cache as needed)."
   ^String [setting-or-name]
-  (restore-cache-if-needed!)
-  (clojure.core/get @cache (setting-name setting-or-name)))
+  (if *disable-cache*
+    (db/select-one-field :value Setting :key (setting-name setting-or-name))
+    (do
+      (restore-cache-if-needed!)
+      (clojure.core/get @cache (setting-name setting-or-name)))))
 
 
 (defn get-string
-  "Get string value of SETTING-OR-NAME. This is the default getter for `String` settings; valuBis fetched as follows:
+  "Get string value of `setting-or-name`. This is the default getter for `String` settings; value is fetched as follows:
 
    1.  From the database (i.e., set via the admin panel), if a value is present;
    2.  From corresponding env var, if any;
@@ -144,60 +277,77 @@
     (case (str/lower-case string-value)
       "true"  true
       "false" false
-      (throw (Exception. (str (tru "Invalid value for string: must be either \"true\" or \"false\" (case-insensitive).")))))))
+      (throw (Exception.
+              (str (tru "Invalid value for string: must be either \"true\" or \"false\" (case-insensitive).")))))))
 
 (defn get-boolean
-  "Get boolean value of (presumably `:boolean`) SETTING-OR-NAME. This is the default getter for `:boolean` settings.
+  "Get boolean value of (presumably `:boolean`) `setting-or-name`. This is the default getter for `:boolean` settings.
    Returns one of the following values:
 
-   *  `nil`   if string value of SETTING-OR-NAME is unset (or empty)
-   *  `true`  if *lowercased* string value of SETTING-OR-NAME is `true`
-   *  `false` if *lowercased* string value of SETTING-OR-NAME is `false`."
+   *  `nil`   if string value of `setting-or-name` is unset (or empty)
+   *  `true`  if *lowercased* string value of `setting-or-name` is `true`
+   *  `false` if *lowercased* string value of `setting-or-name` is `false`."
   ^Boolean [setting-or-name]
   (string->boolean (get-string setting-or-name)))
 
 (defn get-integer
-  "Get integer value of (presumably `:integer`) SETTING-OR-NAME. This is the default getter for `:integer` settings."
+  "Get integer value of (presumably `:integer`) `setting-or-name`. This is the default getter for `:integer` settings."
   ^Integer [setting-or-name]
   (when-let [s (get-string setting-or-name)]
     (Integer/parseInt s)))
 
 (defn get-double
-  "Get double value of (presumably `:double`) SETTING-OR-NAME. This is the default getter for `:double` settings."
+  "Get double value of (presumably `:double`) `setting-or-name`. This is the default getter for `:double` settings."
   ^Double [setting-or-name]
   (when-let [s (get-string setting-or-name)]
     (Double/parseDouble s)))
 
 (defn get-json
-  "Get the string value of SETTING-OR-NAME and parse it as JSON."
+  "Get the string value of `setting-or-name` and parse it as JSON."
   [setting-or-name]
   (json/parse-string (get-string setting-or-name) keyword))
 
+(defn get-timestamp
+  "Get the string value of `setting-or-name` and parse it as an ISO-8601-formatted string, returning a Timestamp."
+  [setting-or-name]
+  (du/->Timestamp (get-string setting-or-name) :no-timezone))
+
 (def ^:private default-getter-for-type
-  {:string  get-string
-   :boolean get-boolean
-   :integer get-integer
-   :json    get-json
-   :double  get-double})
+  {:string    get-string
+   :boolean   get-boolean
+   :integer   get-integer
+   :json      get-json
+   :timestamp get-timestamp
+   :double    get-double})
 
 (defn get
-  "Fetch the value of SETTING-OR-NAME. What this means depends on the Setting's `:getter`; by default, this looks for
+  "Fetch the value of `setting-or-name`. What this means depends on the Setting's `:getter`; by default, this looks for
    first for a corresponding env var, then checks the cache, then returns the default value of the Setting, if any."
   [setting-or-name]
-  ((:getter (resolve-setting setting-or-name))))
+  (let [{:keys [cache? getter]} (resolve-setting setting-or-name)]
+    (binding [*disable-cache* (not cache?)]
+      (getter))))
 
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                                      set!                                                      |
 ;;; +----------------------------------------------------------------------------------------------------------------+
 
-(defn- update-setting! [setting-name new-value]
-  (db/update-where! Setting {:key setting-name}
-    :value new-value))
+(defn- update-setting!
+  "Update an existing Setting. Used internally by `set-string!` below; do not use directly."
+  [setting-name new-value]
+  (assert (not= setting-name settings-last-updated-key)
+    (tru "You cannot update `settings-last-updated` yourself! This is done automatically."))
+  ;; This is indeed a very annoying way of having to do things, but `update-where!` doesn't call `pre-update` (in case
+  ;; it updates thousands of objects). So we need to manually trigger `pre-update` behavior by calling `do-pre-update`
+  ;; so that `value` can get encrypted if `MB_ENCRYPTION_SECRET_KEY` is in use. Then take that possibly-encrypted
+  ;; value and pass that into `update-where!`.
+  (let [{maybe-encrypted-new-value :value} (models/do-pre-update Setting {:value new-value})]
+    (db/update-where! Setting {:key setting-name}
+      :value maybe-encrypted-new-value)))
 
 (defn- set-new-setting!
-  "Insert a new row for a Setting with SETTING-NAME and SETTING-VALUE.
-   Takes care of resetting the cache if the insert fails for some reason."
+  "Insert a new row for a Setting. Used internally by `set-string!` below; do not use directly."
   [setting-name new-value]
   (try (db/insert! Setting
          :key   setting-name
@@ -206,13 +356,15 @@
        ;; and there's actually a row in the DB that's not in the cache for some reason. Go ahead and update the
        ;; existing value and log a warning
        (catch Throwable e
-         (log/warn "Error INSERTing a new Setting:" (.getMessage e)
-                   "\nAssuming Setting already exists in DB and updating existing value.")
+         (log/warn (tru "Error inserting a new Setting:") "\n"
+                   (.getMessage e) "\n"
+                   (tru "Assuming Setting already exists in DB and updating existing value."))
          (update-setting! setting-name new-value))))
 
 (s/defn set-string!
-  "Set string value of SETTING-OR-NAME. A `nil` or empty NEW-VALUE can be passed to unset (i.e., delete)
-   SETTING-OR-NAME."
+  "Set string value of `setting-or-name`. A `nil` or empty `new-value` can be passed to unset (i.e., delete)
+  `setting-or-name`. String-type settings use this function directly; all other types ultimately call this (e.g.
+  `set-boolean!` eventually calls `set-string!`). Returns the `new-value`."
   [setting-or-name, new-value :- (s/maybe s/Str)]
   (let [new-value    (when (seq new-value)
                        new-value)
@@ -230,11 +382,17 @@
     (if new-value
       (swap! cache assoc  setting-name new-value)
       (swap! cache dissoc setting-name))
+    ;; Record the fact that a Setting has been updated so eventaully other instances (if applicable) find out about it
+    ;; (For Settings that don't use the Cache, don't update the `last-updated` value, because it will cause other
+    ;; instances to do needless reloading of the cache from the DB)
+    (when-not *disable-cache*
+      (update-settings-last-updated!))
+    ;; Now return the `new-value`.
     new-value))
 
 (defn set-boolean!
-  "Set the value of boolean SETTING-OR-NAME. NEW-VALUE can be nil, a boolean, or a string representation of one, such
-   as `\"true\"` or `\"false\"` (these strings are case-insensitive)."
+  "Set the value of boolean `setting-or-name`. `new-value` can be nil, a boolean, or a string representation of one,
+  such as `\"true\"` or `\"false\"` (these strings are case-insensitive)."
   [setting-or-name new-value]
   (set-string! setting-or-name (if (string? new-value)
                                  (set-boolean! setting-or-name (string->boolean new-value))
@@ -244,7 +402,7 @@
                                    nil   nil))))
 
 (defn set-integer!
-  "Set the value of integer SETTING-OR-NAME."
+  "Set the value of integer `setting-or-name`."
   [setting-or-name new-value]
   (set-string! setting-or-name (when new-value
                                  (assert (or (integer? new-value)
@@ -253,7 +411,7 @@
                                  (str new-value))))
 
 (defn set-double!
-  "Set the value of double SETTING-OR-NAME."
+  "Set the value of double `setting-or-name`."
   [setting-or-name new-value]
   (set-string! setting-or-name (when new-value
                                  (assert (or (float? new-value)
@@ -262,20 +420,25 @@
                                  (str new-value))))
 
 (defn set-json!
-  "Serialize NEW-VALUE for SETTING-OR-NAME as a JSON string and save it."
+  "Serialize `new-value` for `setting-or-name` as a JSON string and save it."
   [setting-or-name new-value]
-  (set-string! setting-or-name (when new-value
-                                 (json/generate-string new-value))))
+  (set-string! setting-or-name (some-> new-value json/generate-string)))
+
+(defn set-timestamp!
+  "Serialize `new-value` for `setting-or-name` as a ISO 8601-encoded timestamp strign and save it."
+  [setting-or-name new-value]
+  (set-string! setting-or-name (some-> new-value du/date->iso-8601)))
 
 (def ^:private default-setter-for-type
-  {:string  set-string!
-   :boolean set-boolean!
-   :integer set-integer!
-   :json    set-json!
-   :double  set-double!})
+  {:string    set-string!
+   :boolean   set-boolean!
+   :integer   set-integer!
+   :json      set-json!
+   :timestamp set-timestamp!
+   :double    set-double!})
 
 (defn set!
-  "Set the value of SETTING-OR-NAME. What this means depends on the Setting's `:setter`; by default, this just updates
+  "Set the value of `setting-or-name`. What this means depends on the Setting's `:setter`; by default, this just updates
    the Settings cache and writes its value to the DB.
 
     (set :mandrill-api-key \"xyz123\")
@@ -284,7 +447,9 @@
 
      (mandrill-api-key \"xyz123\")"
   [setting-or-name new-value]
-  ((:setter (resolve-setting setting-or-name)) new-value))
+  (let [{:keys [setter cache?]} (resolve-setting setting-or-name)]
+    (binding [*disable-cache* (not cache?)]
+      (setter new-value))))
 
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
@@ -292,17 +457,19 @@
 ;;; +----------------------------------------------------------------------------------------------------------------+
 
 (defn register-setting!
-  "Register a new `Setting` with a map of `SettingDefinition` attributes.
+  "Register a new Setting with a map of `SettingDefinition` attributes.
    This is used internally be `defsetting`; you shouldn't need to use it yourself."
   [{setting-name :name, setting-type :type, default :default, :as setting}]
-  (u/prog1 (let [setting-type (s/validate Type (or setting-type :string))]
+  (u/prog1 (let [setting-type         (s/validate Type (or setting-type :string))]
              (merge {:name        setting-name
                      :description nil
                      :type        setting-type
                      :default     default
                      :getter      (partial (default-getter-for-type setting-type) setting-name)
                      :setter      (partial (default-setter-for-type setting-type) setting-name)
-                     :internal?   false}
+                     :tag         (default-tag-for-type setting-type)
+                     :internal?   false
+                     :cache?      true}
                     (dissoc setting :name :type :default)))
     (s/validate SettingDefinition <>)
     (swap! registered-settings assoc setting-name <>)))
@@ -315,27 +482,28 @@
 
 (defn metadata-for-setting-fn
   "Create metadata for the function automatically generated by `defsetting`."
-  [{:keys [default description], setting-type :type, :as setting}]
+  [{:keys [default description tag], setting-type :type, :as setting}]
   {:arglists '([] [new-value])
    ;; indentation below is intentional to make it clearer what shape the generated documentation is going to take.
    ;; Turn on auto-complete-mode in Emacs and see for yourself!
+   :tag tag
    :doc (str/join "\n" [        description
                                 ""
-                        (format "`%s` is a %s `Setting`. You can get its value by calling:" (setting-name setting) (name setting-type))
+                        (format "`%s` is a %s Setting. You can get its value by calling:" (setting-name setting) (name setting-type))
                                 ""
-                        (format "    (%s)"                                                  (setting-name setting))
+                        (format "    (%s)"                                                (setting-name setting))
                                 ""
                                 "and set its value by calling:"
                                 ""
-                        (format "    (%s <new-value>)"                                      (setting-name setting))
+                        (format "    (%s <new-value>)"                                    (setting-name setting))
                                 ""
-                        (format "You can also set its value with the env var `%s`."         (env-var-name setting))
+                        (format "You can also set its value with the env var `%s`."       (env-var-name setting))
                                 ""
                                 "Clear its value by calling:"
                                 ""
-                        (format "    (%s nil)"                                              (setting-name setting))
+                        (format "    (%s nil)"                                            (setting-name setting))
                                 ""
-                        (format "Its default value is `%s`."                                (if (nil? default) "nil" default))])})
+                        (format "Its default value is `%s`."                              (if (nil? default) "nil" default))])})
 
 
 
@@ -351,8 +519,35 @@
      ;; :refer-clojure :exclude doesn't seem to work in this case
      (metabase.models.setting/set! setting new-value))))
 
+(defn- expr-of-sym? [symbols expr]
+  (when-let [first-sym (and (coll? expr)
+                            (first expr))]
+    (some #(= first-sym %) symbols)))
+
+(defn- valid-trs-or-tru? [desc]
+  (expr-of-sym? ['trs 'tru `trs `tru] desc))
+
+(defn- valid-str-of-trs-or-tru? [maybe-str-expr]
+  (when (expr-of-sym? ['str `str] maybe-str-expr)
+    ;; When there are several i18n'd sentences, there will probably be a surrounding `str` invocation and a space in
+    ;; between the sentences, remove those to validate the i18n clauses
+    (let [exprs-without-strs (remove (every-pred string? str/blank?) (rest maybe-str-expr))]
+      ;; We should have at lease 1 i18n clause, so ensure `exprs-without-strs` is not empty
+      (and (seq exprs-without-strs)
+           (every? valid-trs-or-tru? exprs-without-strs)))))
+
+(defn- validate-description
+  "Validates the description expression `desc-expr`, ensuring it contains an i18n form, or a string consisting of 1 or more i18n forms"
+  [desc]
+  (when-not (or (valid-trs-or-tru? desc)
+                (valid-str-of-trs-or-tru? desc))
+    (throw (IllegalArgumentException.
+            (str (trs "defsetting descriptions strings must be `:internal?` or internationalized, found: `{0}`"
+                      (pr-str desc))))))
+  desc)
+
 (defmacro defsetting
-  "Defines a new `Setting` that will be added to the DB at some point in the future.
+  "Defines a new Setting that will be added to the DB at some point in the future.
    Conveniently can be used as a getter/setter as well:
 
      (defsetting mandrill-api-key \"API key for Mandrill.\")
@@ -368,7 +563,7 @@
    *  `:default`   - The default value of the setting. (default: `nil`)
    *  `:type`      - `:string` (default), `:boolean`, `:integer`, or `:json`. Non-`:string` settings have special
                      default getters and setters that automatically coerce values to the correct types.
-   *  `:internal?` - This `Setting` is for internal use and shouldn't be exposed in the UI (i.e., not returned by the
+   *  `:internal?` - This Setting is for internal use and shouldn't be exposed in the UI (i.e., not returned by the
                       corresponding endpoints). Default: `false`
    *  `:getter`    - A custom getter fn, which takes no arguments. Overrides the default implementation. (This can in
                      turn call functions in this namespace like `get-string` or `get-boolean` to invoke the default
@@ -376,13 +571,18 @@
    *  `:setter`    - A custom setter fn, which takes a single argument. Overrides the default implementation. (This
                      can in turn call functions in this namespace like `set-string!` or `set-boolean!` to invoke the
                      default setter behavior. Keep in mind that the custom setter may be passed `nil`, which should
-                     clear the values of the Setting.)"
+                     clear the values of the Setting.)
+   *  `:cache?`    - Should this Setting be cached? (default `true`)? Be careful when disabling this, because it could
+                     have a very negative performance impact."
   {:style/indent 1}
   [setting-symb description & {:as options}]
   {:pre [(symbol? setting-symb)]}
-  `(let [setting# (register-setting! (assoc ~options
+  `(let [desc# ~(if (:internal? options)
+                  description
+                  (validate-description description))
+         setting# (register-setting! (assoc ~options
                                        :name ~(keyword setting-symb)
-                                       :description ~description))]
+                                       :description desc#))]
      (-> (def ~setting-symb (setting-fn setting#))
          (alter-meta! merge (metadata-for-setting-fn setting#)))))
 
@@ -392,7 +592,7 @@
 ;;; +----------------------------------------------------------------------------------------------------------------+
 
 (defn set-many!
-  "Set the value of several `Settings` at once.
+  "Set the value of several Settings at once.
 
     (set-all {:mandrill-api-key \"xyz123\", :another-setting \"ABC\"})"
   [settings]
@@ -417,7 +617,7 @@
                        v)
      :is_env_setting (boolean env-value)
      :env_name       (env-var-name setting)
-     :description    (:description setting)
+     :description    (str (:description setting))
      :default        (or (when env-value
                            (format "Using $%s" (env-var-name setting)))
                          (:default setting))}))
diff --git a/src/metabase/models/task_history.clj b/src/metabase/models/task_history.clj
new file mode 100644
index 0000000000000000000000000000000000000000..a2257b13445f303895dd9b09ae80a596b6449a12
--- /dev/null
+++ b/src/metabase/models/task_history.clj
@@ -0,0 +1,31 @@
+(ns metabase.models.task-history
+  (:require [metabase.models.interface :as i]
+            [metabase.util :as u]
+            [toucan
+             [db :as db]
+             [models :as models]]))
+
+(models/defmodel TaskHistory :task_history)
+
+(defn cleanup-task-history!
+  "Deletes older TaskHistory rows. Will order TaskHistory by `ended_at` and delete everything after
+  `num-rows-to-keep`. This is intended for a quick cleanup of old rows."
+  [num-rows-to-keep]
+  ;; Ideally this would be one query, but MySQL does not allow nested queries with a limit. The query below orders the
+  ;; tasks by the time they finished, newest first. Then finds the first row after skipping `num-rows-to-keep`. Using
+  ;; the date that task finished, it deletes everything after that. As we continue to add TaskHistory entries, this
+  ;; ensures we'll have a good amount of history for debugging/troubleshooting, but not grow too large and fill the
+  ;; disk.
+  (when-let  [clean-before-date (db/select-one-field :ended_at TaskHistory {:limit    1
+                                                                            :offset   num-rows-to-keep
+                                                                            :order-by [[:ended_at :desc]]})]
+    (db/simple-delete! TaskHistory :ended_at [:<= clean-before-date])))
+
+(u/strict-extend (class TaskHistory)
+  models/IModel
+  (merge models/IModelDefaults
+         {:types (constantly {:task_details :json})})
+  i/IObjectPermissions
+  (merge i/IObjectPermissionsDefaults
+         {:can-read?  i/superuser?
+          :can-write? i/superuser?}))
diff --git a/src/metabase/models/user.clj b/src/metabase/models/user.clj
index 858558ab24621cb0c2054f02718af6fe5428580f..6f2dd9a005130d342700741ba7bbf3ce56e622eb 100644
--- a/src/metabase/models/user.clj
+++ b/src/metabase/models/user.clj
@@ -13,8 +13,8 @@
              [permissions-group-membership :as perm-membership :refer [PermissionsGroupMembership]]]
             [metabase.util
              [date :as du]
+             [i18n :refer [tru]]
              [schema :as su]]
-            [puppetlabs.i18n.core :refer [tru]]
             [schema.core :as s]
             [toucan
              [db :as db]
diff --git a/src/metabase/plugins.clj b/src/metabase/plugins.clj
index 0ee78b8d8fdf7e4d0a30948ff1dda0292274b0e5..a7933a560ee0c73e9c57a7fe60d57c49224a8946 100644
--- a/src/metabase/plugins.clj
+++ b/src/metabase/plugins.clj
@@ -8,7 +8,7 @@
             [metabase
              [config :as config]
              [util :as u]]
-            [puppetlabs.i18n.core :refer [trs]]))
+            [metabase.util.i18n :refer [trs]]))
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                                     Java 8                                                     |
@@ -43,7 +43,7 @@
             :when (and (.isFile file)
                        (.canRead file)
                        (re-find #"\.jar$" (.getPath file)))]
-      (log/info (u/format-color 'magenta (str (trs "Loading plugin {0}... " file) (u/emoji "🔌"))))
+      (log/info (u/format-color 'magenta (trs "Loading plugin {0}... " file) (u/emoji "🔌")))
       (add-jar-to-classpath! file))))
 
 
diff --git a/src/metabase/public_settings.clj b/src/metabase/public_settings.clj
index 643c05315696671011415f1d9ba5eb663ecabbcd..34c0c73394cd96da4e83ba4d2637c07c285fe51e 100644
--- a/src/metabase/public_settings.clj
+++ b/src/metabase/public_settings.clj
@@ -8,9 +8,8 @@
              [setting :as setting :refer [defsetting]]]
             [metabase.public-settings.metastore :as metastore]
             [metabase.util
-             [i18n :refer [available-locales-with-names set-locale]]
+             [i18n :refer [available-locales-with-names set-locale tru]]
              [password :as password]]
-            [puppetlabs.i18n.core :refer [tru]]
             [toucan.db :as db])
   (:import [java.util TimeZone UUID]))
 
diff --git a/src/metabase/public_settings/metastore.clj b/src/metabase/public_settings/metastore.clj
index ed9a6685141d5b38cbed0f96ca837ffe1944db3e..6d61738fc5df38087d987b1729eca0c7442670d5 100644
--- a/src/metabase/public_settings/metastore.clj
+++ b/src/metabase/public_settings/metastore.clj
@@ -8,8 +8,9 @@
              [config :as config]
              [util :as u]]
             [metabase.models.setting :as setting :refer [defsetting]]
-            [metabase.util.schema :as su]
-            [puppetlabs.i18n.core :refer [trs tru]]
+            [metabase.util
+             [i18n :refer [trs tru]]
+             [schema :as su]]
             [schema.core :as s]))
 
 (def ^:private ValidToken
@@ -48,15 +49,15 @@
                   ;; 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.")})
+                    {:valid false, :status (str (tru "Unable to validate token."))})
                   ;; if there was any other 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
                     (log/error e (trs "Error fetching token status:"))
-                    {:valid false, :status (tru "There was an error checking whether this token was valid.")})))
+                    {:valid false, :status (str (tru "There was an error checking whether this token was valid."))})))
            fetch-token-status-timeout-ms
-           {:valid false, :status (tru "Token validation timed out.")})))
+           {:valid false, :status (str (tru "Token validation timed out."))})))
 
 (defn- check-embedding-token-is-valid* [token]
   (when (s/check ValidToken token)
diff --git a/src/metabase/pulse.clj b/src/metabase/pulse.clj
index 96a16402d4699f6cfff19a34916796869b340a6f..33a81d5ad66a75f5a17462074b9c36597533c182 100644
--- a/src/metabase/pulse.clj
+++ b/src/metabase/pulse.clj
@@ -13,10 +13,9 @@
              [pulse :refer [Pulse]]]
             [metabase.pulse.render :as render]
             [metabase.util
+             [i18n :refer [trs tru]]
              [ui-logic :as ui]
              [urls :as urls]]
-            [metabase.util.urls :as urls]
-            [puppetlabs.i18n.core :refer [tru]]
             [schema.core :as s]
             [toucan.db :as db])
   (:import java.util.TimeZone
@@ -132,7 +131,7 @@
     (goal-met? alert results)
 
     :else
-    (let [^String error-text (tru "Unrecognized alert with condition ''{0}''" alert_condition)]
+    (let [^String error-text (str (tru "Unrecognized alert with condition ''{0}''" alert_condition))]
       (throw (IllegalArgumentException. error-text)))))
 
 (defmethod should-send-notification? :pulse
@@ -189,7 +188,7 @@
 
 (defmethod create-notification :default
   [_ _ {:keys [channel_type] :as channel}]
-  (let [^String ex-msg (tru "Unrecognized channel type {0}" (pr-str channel_type))]
+  (let [^String ex-msg (str (tru "Unrecognized channel type {0}" (pr-str channel_type)))]
     (throw (UnsupportedOperationException. ex-msg))))
 
 (defmulti ^:private send-notification!
@@ -212,7 +211,12 @@
 
 (defn- send-notifications! [notifications]
   (doseq [notification notifications]
-    (send-notification! notification)))
+    ;; do a try-catch around each notification so if one fails, we'll still send the other ones for example, an Alert
+    ;; set up to send over both Slack & email: if Slack fails, we still want to send the email (#7409)
+    (try
+      (send-notification! notification)
+      (catch Throwable e
+        (log/error e (trs "Error sending notification!"))))))
 
 (defn- pulse->notifications [{:keys [cards channel-ids], :as pulse}]
   (let [results     (for [card  cards
diff --git a/src/metabase/pulse/color.clj b/src/metabase/pulse/color.clj
new file mode 100644
index 0000000000000000000000000000000000000000..bd35e17e236190c23a897f8f951410305d562f96
--- /dev/null
+++ b/src/metabase/pulse/color.clj
@@ -0,0 +1,60 @@
+(ns metabase.pulse.color
+  "Namespaces that uses the Nashorn javascript engine to invoke some shared javascript code that we use to determine
+  the background color of pulse table cells"
+  (:require [cheshire.core :as json]
+            [metabase.util.i18n :refer [trs]]
+            [schema.core :as s])
+  (:import java.io.InputStream
+           [javax.script Invocable ScriptEngine ScriptEngineManager]
+           [jdk.nashorn.api.scripting JSObject ScriptObjectMirror]))
+
+(defn- make-js-engine-with-script [^String script]
+  (let [engine-mgr (ScriptEngineManager.)
+        js-engine  (.getEngineByName engine-mgr "nashorn")]
+    (.eval js-engine script)
+    js-engine))
+
+(defn- ^InputStream get-classpath-resource [path]
+  (.getResourceAsStream (class []) path))
+
+(def ^:private js-engine
+  (let [js-file-path "/frontend_shared/color_selector.js"]
+    ;; The code that loads the JS engine is behind a delay so that we don't incur that cost on startup. The below
+    ;; assertion till look for the javascript file at startup and fail if it doesn't find it. This is to avoid a big
+    ;; delay in finding out that the system is broken
+    (assert (get-classpath-resource js-file-path)
+      (trs "Can't find JS color selector at ''{0}''" js-file-path))
+    (delay
+     (with-open [stream (get-classpath-resource js-file-path)]
+       (make-js-engine-with-script (slurp stream))))))
+
+(def ^:private QueryResults
+  "This is a pretty loose schema, more as a safety net as we have a long feedback loop for this being broken as it's
+  being handed to the JS color picking code. Currently it just needs column names from `:cols`, and the query results
+  from `:rows`"
+  {:cols [{:name s/Str
+           s/Any s/Any}]
+   :rows [[s/Any]]
+   s/Any s/Any})
+
+(s/defn make-color-selector
+  "Returns a curried javascript function (object) that can be used with `get-background-color` for delegating to JS
+  code to pick out the correct color for a given cell in a pulse table. The logic for picking a color is somewhat
+  complex, but defined in a set of rules in `viz-settings`. There are some colors that are picked based on a
+  particular cell value, others affect the row, so it's necessary to call this once for the resultset and then
+  `get-background-color` on each cell."
+  [{:keys [cols rows]} :- QueryResults, viz-settings]
+  ;; NOTE: for development it is useful to replace the following line with this one so it reload each time:
+  ; (let [^Invocable engine (make-js-engine-with-script (slurp "resources/frontend_shared/color_selector.js"))
+  (let [^Invocable engine @js-engine
+        ;; Ideally we'd convert everything to JS data before invoking the function below, but converting rows would be
+        ;; expensive. The JS code is written to deal with `rows` in it's native Nashorn format but since `cols` and
+        ;; `viz-settings` are small, pass those as JSON so that they can be deserialized to pure JS objects once in JS code
+        js-fn-args (object-array [rows (json/generate-string cols) (json/generate-string viz-settings)])]
+    (.invokeFunction engine "makeCellBackgroundGetter" js-fn-args)))
+
+(defn get-background-color
+  "Get the correct color for a cell in a pulse table. This is intended to be invoked on each cell of every row in the
+  table. See `make-color-selector` for more info."
+  [^JSObject color-selector cell-value column-name row-index]
+  (.call color-selector color-selector (object-array [cell-value row-index column-name])))
diff --git a/src/metabase/pulse/render.clj b/src/metabase/pulse/render.clj
index a969bad8f07f5cf0d85fc48778d3da8320fa1eaa..c720b8166fa77d98fb4aa461ae3d21f12397d642 100644
--- a/src/metabase/pulse/render.clj
+++ b/src/metabase/pulse/render.clj
@@ -10,12 +10,13 @@
             [hiccup
              [core :refer [h html]]
              [util :as hutil]]
+            [metabase.pulse.color :as color]
             [metabase.util :as u]
             [metabase.util
              [date :as du]
+             [i18n :refer [trs tru]]
              [ui-logic :as ui-logic]
              [urls :as urls]]
-            [puppetlabs.i18n.core :refer [trs tru]]
             [schema.core :as s])
   (:import cz.vutbr.web.css.MediaSpec
            [java.awt BasicStroke Color Dimension RenderingHints]
@@ -87,33 +88,19 @@
                        :padding-bottom  :5px}))
 
 (defn- bar-td-style []
-  (merge (font-style) {:font-size     :16px
-                       :font-weight   400
-                       :text-align    :left
-                       :padding-right :1em
-                       :padding-top   :8px}))
-
-;; TO-DO for @senior: apply this style to headings of numeric columns
-(defn- bar-th-numeric-style []
-  (merge (font-style) {:text-align      :right
-                       :font-size       :14.22px
-                       :font-weight     700
-                       :color           color-gray-4
-                       :border-bottom   (str "1px solid " color-row-border)
-                       :padding-top     :20px
-                       :padding-bottom  :5px}))
-
-;; TO-DO for @senior: apply this style to numeric cells
-(defn- bar-td-style-numeric []
   (merge (font-style) {:font-size      :14.22px
                        :font-weight    400
-                       :color          color-dark-gray
-                       :text-align     :right
-                       :padding-right  :1em
-                       :padding-top    :2px
-                       :padding-bottom :1px
-                       :font-family    "Courier, Monospace"
-                       :border-bottom  (str "1px solid " color-row-border)}))
+                       :text-align     :left
+                       :padding-right  :0.5em
+                       :padding-left   :0.5em
+                       :padding-top    :4px
+                       :padding-bottom :4px}))
+
+(defn- bar-th-style-numeric []
+  (merge (font-style) (bar-th-style) {:text-align :right}))
+
+(defn- bar-td-style-numeric []
+  (merge (font-style) (bar-td-style) {:text-align :right}))
 
 (def ^:private RenderedPulseCard
   "Schema used for functions that operate on pulse card contents and their attachments"
@@ -356,7 +343,7 @@
 (defn- heading-style-for-type
   [cell]
   (if (instance? NumericWrapper cell)
-    (bar-th-numeric-style)
+    (bar-th-style-numeric)
     (bar-th-style)))
 
 (defn- row-style-for-type
@@ -366,32 +353,42 @@
     (bar-td-style)))
 
 (defn- render-table
-  [header+rows]
-  [:table {:style (style {:max-width (str "100%"), :white-space :nowrap, :padding-bottom :8px, :border-collapse :collapse})}
-   (let [{header-row :row bar-width :bar-width} (first header+rows)]
+  "This function returns the HTML data structure for the pulse table. `color-selector` is a function that returns the
+  background color for a given cell. `column-names` is different from the header in `header+rows` as the header is the
+  display_name (i.e. human friendly. `header+rows` includes the text contents of the table we're about ready to
+  create."
+  [color-selector column-names header+rows]
+  (let [{bar-width :bar-width :as header} (first header+rows)]
+    [:table {:style (style {:max-width (str "100%"), :white-space :nowrap, :padding-bottom :8px, :border-collapse :collapse})
+             :cellpadding "0"
+             :cellspacing "0"}
      [:thead
       [:tr
-       (for [header-cell header-row]
+       (for [header-cell (:row header)]
          [:th {:style (style (row-style-for-type header-cell) (heading-style-for-type header-cell) {:min-width :60px})}
           (h header-cell)])
        (when bar-width
-         [:th {:style (style (bar-td-style) (bar-th-style) {:width (str bar-width "%")})}])]])
-   [:tbody
-    (map-indexed (fn [row-idx {:keys [row bar-width]}]
-                   [:tr {:style (style {:color color-gray-3})}
-                    (map-indexed (fn [col-idx cell]
-                                   [:td {:style (style (row-style-for-type cell) (when (and bar-width (= col-idx 1)) {:font-weight 700}))}
-                                    (h cell)])
-                                 row)
-                    (when bar-width
-                      [:td {:style (style (bar-td-style) {:width :99%})}
-                       [:div {:style (style {:background-color color-purple
-                                             :max-height       :10px
-                                             :height           :10px
-                                             :border-radius    :2px
-                                             :width            (str bar-width "%")})}
-                        "&#160;"]])])
-                 (rest header+rows))]])
+         [:th {:style (style (bar-td-style) (bar-th-style) {:width (str bar-width "%")})}])]]
+     [:tbody
+      (map-indexed (fn [row-idx {:keys [row bar-width]}]
+                     [:tr {:style (style {:color color-gray-3})}
+                      (map-indexed (fn [col-idx cell]
+                                     (let [bg-color (color/get-background-color color-selector cell (get column-names col-idx) row-idx)]
+                                       [:td {:style (style (row-style-for-type cell)
+                                                           (merge {:background-color bg-color}
+                                                                  (when (and bar-width (= col-idx 1))
+                                                                    {:font-weight 700})))}
+                                        (h cell)]))
+                                   row)
+                      (when bar-width
+                        [:td {:style (style (bar-td-style) {:width :99%})}
+                         [:div {:style (style {:background-color color-purple
+                                               :max-height       :10px
+                                               :height           :10px
+                                               :border-radius    :2px
+                                               :width            (str bar-width "%")})}
+                          "&#160;"]])])
+                   (rest header+rows))]]))
 
 (defn- create-remapping-lookup
   "Creates a map with from column names to a column index. This is used to figure out what a given column name or value
@@ -424,9 +421,9 @@
   "Returns a seq of stringified formatted rows that can be rendered into HTML"
   [timezone remapping-lookup cols rows bar-column max-value]
   (for [row rows]
-    {:bar-width (when bar-column
+    {:bar-width (when-let [bar-value (and bar-column (bar-column row))]
                   ;; cast to double to avoid "Non-terminating decimal expansion" errors
-                  (float (* 100 (/ (double (bar-column row)) max-value))))
+                  (float (* 100 (/ (double bar-value) max-value))))
      :row (for [[maybe-remapped-col maybe-remapped-row-cell] (map vector cols row)
                 :when (and (not (:remapped_from maybe-remapped-col))
                            (show-in-table? maybe-remapped-col))
@@ -493,20 +490,30 @@
 (s/defn ^:private render:table :- RenderedPulseCard
   [render-type timezone card {:keys [cols rows] :as data}]
   (let [table-body [:div
-                    (render-table (prep-for-html-rendering timezone cols rows nil nil cols-limit))
+                    (render-table (color/make-color-selector data (:visualization_settings card))
+                                  (mapv :name (:cols data))
+                                  (prep-for-html-rendering timezone cols rows nil nil cols-limit))
                     (render-truncation-warning cols-limit (count-displayed-columns cols) rows-limit (count rows))]]
     {:attachments nil
      :content     (if-let [results-attached (attached-results-text render-type cols cols-limit rows rows-limit)]
                     (list results-attached table-body)
                     (list table-body))}))
 
+(defn- non-nil-rows
+  "Remove any rows that have a nil value for the `x-axis-fn` OR `y-axis-fn`"
+  [x-axis-fn y-axis-fn rows]
+  (filter (every-pred x-axis-fn y-axis-fn) rows))
+
 (s/defn ^:private render:bar :- RenderedPulseCard
-  [timezone card {:keys [cols rows] :as data}]
+  [timezone card {:keys [cols] :as data}]
   (let [[x-axis-rowfn y-axis-rowfn] (graphing-columns card data)
+        rows (non-nil-rows x-axis-rowfn y-axis-rowfn (:rows data))
         max-value (apply max (map y-axis-rowfn rows))]
     {:attachments nil
      :content     [:div
-                   (render-table (prep-for-html-rendering timezone cols rows y-axis-rowfn max-value 2))
+                   (render-table (color/make-color-selector data (:visualization_settings card))
+                                 (mapv :name cols)
+                                 (prep-for-html-rendering timezone cols rows y-axis-rowfn max-value 2))
                    (render-truncation-warning 2 (count-displayed-columns cols) rows-limit (count rows))]}))
 
 (s/defn ^:private render:scalar :- RenderedPulseCard
@@ -541,7 +548,7 @@
                  (* 2 sparkline-dot-radius)
                  (* 2 sparkline-dot-radius)))
     (when-not (ImageIO/write image "png" os)                    ; returns `true` if successful -- see JavaDoc
-      (let [^String msg (tru "No appropriate image writer found!")]
+      (let [^String msg (str (tru "No appropriate image writer found!"))]
         (throw (Exception. msg))))
     (.toByteArray os)))
 
@@ -649,10 +656,11 @@
         ft-row (if (datetime-field? (x-axis-rowfn cols))
                  #(.getTime ^Date (du/->Timestamp % timezone))
                  identity)
-        rows   (if (> (ft-row (x-axis-rowfn (first rows)))
-                      (ft-row (x-axis-rowfn (last rows))))
-                 (reverse rows)
-                 rows)
+        rows   (non-nil-rows x-axis-rowfn y-axis-rowfn
+                (if (> (ft-row (x-axis-rowfn (first rows)))
+                       (ft-row (x-axis-rowfn (last rows))))
+                  (reverse rows)
+                  rows))
         xs     (map (comp ft-row x-axis-rowfn) rows)
         xmin   (apply min xs)
         xmax   (apply max xs)
@@ -769,7 +777,7 @@
   [render-type timezone card {:keys [data error]}]
   (try
     (when error
-      (let [^String msg (tru "Card has errors: {0}" error)]
+      (let [^String msg (str (tru "Card has errors: {0}" error))]
         (throw (Exception. msg))))
     (case (detect-pulse-card-type card data)
       :empty     (render:empty     render-type card data)
diff --git a/src/metabase/query_processor.clj b/src/metabase/query_processor.clj
index a49bb992b7897de7b33dab381f0ee5ac764e4cf0..67733ddef24d7ce23f5b549b8faf08c18bff7416 100644
--- a/src/metabase/query_processor.clj
+++ b/src/metabase/query_processor.clj
@@ -5,6 +5,7 @@
             [metabase
              [driver :as driver]
              [util :as u]]
+            [metabase.mbql.schema :as mbql.s]
             [metabase.models
              [query :as query]
              [query-execution :as query-execution :refer [QueryExecution]]]
@@ -28,16 +29,18 @@
              [limit :as limit]
              [log :as log-query]
              [mbql-to-native :as mbql-to-native]
+             [normalize-query :as normalize]
              [parameters :as parameters]
              [permissions :as perms]
              [resolve :as resolve]
              [resolve-driver :as resolve-driver]
              [results-metadata :as results-metadata]
-             [source-table :as source-table]]
+             [source-table :as source-table]
+             [validate :as validate]]
             [metabase.query-processor.util :as qputil]
             [metabase.util
              [date :as du]
-             [schema :as su]]
+             [i18n :refer [tru]]]
             [schema.core :as s]
             [toucan.db :as db]))
 
@@ -95,9 +98,9 @@
       dev/check-results-format
       limit/limit
       cumulative-ags/handle-cumulative-aggregations
+      results-metadata/record-and-return-metadata!
       format-rows/format-rows
       binning/update-binning-strategy
-      results-metadata/record-and-return-metadata!
       resolve/resolve-middleware
       add-dim/add-remapping
       implicit-clauses/add-implicit-clauses
@@ -114,6 +117,8 @@
       log-query/log-initial-query
       cache/maybe-return-cached-results
       log-query/log-results-metadata
+      validate/validate-query
+      normalize/normalize
       catch-exceptions/catch-exceptions))
 ;; ▲▲▲ PRE-PROCESSING ▲▲▲ happens from BOTTOM-TO-TOP, e.g. the results of `expand-macros` are passed to
 ;; `substitute-parameters`
@@ -146,7 +151,9 @@
        driver-specific/process-query-in-context
        resolve-driver/resolve-driver
        fetch-source-query/fetch-source-query
-       bind-timezone/bind-effective-timezone))
+       bind-timezone/bind-effective-timezone
+       validate/validate-query
+       normalize/normalize))
 ;; ▲▲▲ This only does PRE-PROCESSING, so it happens from bottom to top, eventually returning the preprocessed query
 ;; instead of running it
 
@@ -232,7 +239,7 @@
    :dashboard_id      dashboard-id
    :pulse_id          pulse-id
    :context           context
-   :hash              (or query-hash (throw (Exception. "Missing query hash!")))
+   :hash              (or query-hash (throw (Exception. (str (tru "Missing query hash!")))))
    :native            (= query-type "native")
    :json_query        (dissoc query :info)
    :started_at        (du/new-sql-timestamp)
@@ -255,23 +262,7 @@
                     (u/pprint-to-str (u/filtered-stacktrace e))))
         (save-and-return-failed-query! query-execution (.getMessage e))))))
 
-(def ^:private DatasetQueryOptions
-  "Schema for the options map for the `dataset-query` function.
-   This becomes available to QP middleware as the `:info` dictionary in the top level of a query.
-   When the query is finished running, most of these values are saved in the new `QueryExecution` row.
-   In some cases, these values are used by the middleware; for example, the permissions-checking middleware
-   will check Collection permissions if applicable if `card-id` is non-nil."
-  (s/constrained {:context                       query-execution/Context
-                  (s/optional-key :executed-by)  (s/maybe su/IntGreaterThanZero)
-                  (s/optional-key :card-id)      (s/maybe su/IntGreaterThanZero)
-                  (s/optional-key :dashboard-id) (s/maybe su/IntGreaterThanZero)
-                  (s/optional-key :pulse-id)     (s/maybe su/IntGreaterThanZero)
-                  (s/optional-key :nested?)      (s/maybe s/Bool)}
-                 (fn [{:keys [executed-by]}]
-                   (or (integer? executed-by)
-                       *allow-queries-with-no-executor-id*))
-                 "executed-by cannot be nil unless *allow-queries-with-no-executor-id* is true"))
-
+;; TODO - couldn't saving the query execution be done by MIDDLEWARE?
 (s/defn process-query-and-save-execution!
   "Process and run a json based dataset query and return results.
 
@@ -283,9 +274,9 @@
   Depending on the database specified in the query this function will delegate to a driver specific implementation.
   For the purposes of tracking we record each call to this function as a QueryExecution in the database.
 
-  OPTIONS must conform to the `DatasetQueryOptions` schema; refer to that for more details."
+  OPTIONS must conform to the `mbql.s/Info` schema; refer to that for more details."
   {:style/indent 1}
-  [query, options :- DatasetQueryOptions]
+  [query, options :- mbql.s/Info]
   (run-and-save-query! (assoc query :info (assoc options
                                             :query-hash (qputil/query-hash query)
                                             :query-type (if (qputil/mbql-query? query) "MBQL" "native")))))
@@ -306,5 +297,5 @@
 (s/defn process-query-and-save-with-max!
   "Same as `process-query-and-save-execution!` but will include the default max rows returned as a constraint"
   {:style/indent 1}
-  [query, options :- DatasetQueryOptions]
+  [query, options :- mbql.s/Info]
   (process-query-and-save-execution! (assoc query :constraints default-query-constraints) options))
diff --git a/src/metabase/query_processor/annotate.clj b/src/metabase/query_processor/annotate.clj
index 22a8edd44d7467ff8da76909bcc6fc3dda2eb2e7..b1c569e76c29c015000532b0bf57b79c78e2ea6a 100644
--- a/src/metabase/query_processor/annotate.clj
+++ b/src/metabase/query_processor/annotate.clj
@@ -197,9 +197,8 @@
   "Return a set of bare-bones metadata for a Field named K when all else fails.
    Scan the INITIAL-VALUES of K in an attempt to determine the `base-type`."
   [k & [initial-values]]
-  {:base-type          (if (seq initial-values)
-                         (driver/values->base-type initial-values)
-                         :type/*)
+  {:base-type          (or (driver/values->base-type initial-values)
+                           :type/*)
    :preview-display    true
    :special-type       nil
    :field-name         k
@@ -364,6 +363,13 @@
            ;; add FK info
            add-extra-info-to-fk-fields))))
 
+(defn- pre-sort-index->post-sort-index
+  "Return a  mapping of how columns should be sorted:
+   [2 1 0] means the 1st column should be 3rd, 2nd remain 2nd, and 3rd should come 1st."
+  [unsorted-columns sorted-columns]
+  (let [column-index (zipmap unsorted-columns (range))]
+    (map column-index sorted-columns)))
+
 (defn annotate-and-sort
   "Post-process a structured query to add metadata to the results. This stage:
 
@@ -371,13 +377,17 @@
   2.  Resolves the Fields returned in the results and adds information like `:columns` and `:cols` expected by the
       frontend."
   [query {:keys [columns rows], :as results}]
-  (let [row-maps (for [row rows]
-                   (zipmap columns row))
-        cols    (resolve-sort-and-format-columns (:query query) (distinct columns) (take 10 row-maps))
-        columns (mapv :name cols)]
+  (let [cols           (resolve-sort-and-format-columns (:query query)
+                                                        (distinct columns)
+                                                        (for [row (take 10 rows)]
+                                                          (zipmap columns row)))
+        sorted-columns (mapv :name cols)]
     (assoc results
       :cols    (vec (for [col cols]
                       (update col :name name)))
-      :columns (mapv name columns)
-      :rows    (for [row row-maps]
-                 (mapv row columns)))))
+      :columns (mapv name sorted-columns)
+      :rows    (if (not= columns sorted-columns)
+                 (let [sorted-column-ordering (pre-sort-index->post-sort-index columns sorted-columns)]
+                   (for [row rows]
+                     (mapv (partial nth (vec row)) sorted-column-ordering)))
+                 rows))))
diff --git a/src/metabase/query_processor/interface.clj b/src/metabase/query_processor/interface.clj
index 42359622cc39cda6f39904e94392f225dece7faa..4c5b1518c58bf822b2e53d5222a4a3931ff2a3f5 100644
--- a/src/metabase/query_processor/interface.clj
+++ b/src/metabase/query_processor/interface.clj
@@ -1,7 +1,7 @@
 (ns metabase.query-processor.interface
   "Definitions of `Field`, `Value`, and other record types present in an expanded query.
-   This namespace should just contain definitions of various protocols and record types; associated logic should go in
-  `metabase.query-processor.middleware.expand`."
+   This namespace should just contain definitions ^:deprecated of various protocols and record types; associated logic
+  should go in `metabase.query-processor.middleware.expand`."
   (:require [metabase.models
              [dimension :as dim]
              [field :as field]]
@@ -38,7 +38,7 @@
    Not neccesarily bound when using various functions like `fk->` in the REPL."
   nil)
 
-(defn driver-supports?
+(defn ^:deprecated driver-supports?
   "Does the currently bound `*driver*` support FEATURE?
    (This returns `nil` if `*driver*` is unbound. `*driver*` is always bound when running queries the normal way,
    but may not be when calling this function directly from the REPL.)"
@@ -48,7 +48,7 @@
 
 ;; `assert-driver-supports` doesn't run check when `*driver*` is unbound (e.g., when used in the REPL)
 ;; Allows flexibility when composing queries for tests or interactive development
-(defn assert-driver-supports
+(defn ^:deprecated assert-driver-supports
   "When `*driver*` is bound, assert that is supports keyword FEATURE."
   [feature]
   (when *driver*
@@ -65,23 +65,36 @@
 
 ;; These are just used by the QueryExpander to record information about how joins should occur.
 
-(s/defrecord JoinTableField [field-id   :- su/IntGreaterThanZero
-                             field-name :- su/NonBlankString]
+(s/defrecord ^:deprecated JoinTableField [field-id   :- su/IntGreaterThanZero
+                                          field-name :- su/NonBlankString]
   nil
   :load-ns true)
 
-(s/defrecord JoinTable [source-field :- JoinTableField
-                        pk-field     :- JoinTableField
-                        table-id     :- su/IntGreaterThanZero
-                        table-name   :- su/NonBlankString
-                        schema       :- (s/maybe su/NonBlankString)
-                        join-alias   :- su/NonBlankString]
+(s/defrecord ^:deprecated JoinTable [source-field :- JoinTableField
+                                     pk-field     :- JoinTableField
+                                     table-id     :- su/IntGreaterThanZero
+                                     table-name   :- su/NonBlankString
+                                     schema       :- (s/maybe su/NonBlankString)
+                                     join-alias   :- su/NonBlankString]
+  nil
+  :load-ns true)
+
+(declare Query)
+
+;; Similar to a `JoinTable` but instead of referencing a table, it references a query expression
+(s/defrecord ^:deprecated JoinQuery [source-field :- JoinTableField
+                                     pk-field     :- JoinTableField
+                                     table-id     :- su/IntGreaterThanZero
+                                     schema       :- (s/maybe su/NonBlankString)
+                                     join-alias   :- su/NonBlankString
+                                     query        :- {s/Any  s/Any
+                                                      :query Query}]
   nil
   :load-ns true)
 
 ;;; --------------------------------------------------- PROTOCOLS ----------------------------------------------------
 
-(defprotocol IField
+(defprotocol ^:deprecated IField
   "Methods specific to the Query Expander `Field` record type."
   (qualified-name-components [this]
     "Return a vector of name components of the form `[table-name parent-names... field-name]`
@@ -95,51 +108,51 @@
 ;;; +----------------------------------------------------------------------------------------------------------------+
 
 
-(s/defrecord FieldValues [field-value-id          :- su/IntGreaterThanZero
-                          field-id                :- su/IntGreaterThanZero
-                          values                  :- (s/maybe (s/cond-pre [s/Any] {} []))
-                          human-readable-values   :- (s/maybe (s/cond-pre [s/Any] {} []))
-                          created-at              :- java.util.Date
-                          updated-at              :- java.util.Date]
+(s/defrecord ^:deprecated FieldValues [field-value-id          :- su/IntGreaterThanZero
+                                       field-id                :- su/IntGreaterThanZero
+                                       values                  :- (s/maybe (s/cond-pre [s/Any] {} []))
+                                       human-readable-values   :- (s/maybe (s/cond-pre [s/Any] {} []))
+                                       created-at              :- java.util.Date
+                                       updated-at              :- java.util.Date]
   nil
   :load-ns true)
 
-(s/defrecord Dimensions [dimension-id            :- su/IntGreaterThanZero
-                         field-id                :- su/IntGreaterThanZero
-                         dimension-name          :- su/NonBlankString
-                         human-readable-field-id :- (s/maybe su/IntGreaterThanZero)
-                         dimension-type          :- (apply s/enum dim/dimension-types)
-                         created-at              :- java.util.Date
-                         updated-at              :- java.util.Date]
+(s/defrecord ^:deprecated Dimensions [dimension-id            :- su/IntGreaterThanZero
+                                      field-id                :- su/IntGreaterThanZero
+                                      dimension-name          :- su/NonBlankString
+                                      human-readable-field-id :- (s/maybe su/IntGreaterThanZero)
+                                      dimension-type          :- (apply s/enum dim/dimension-types)
+                                      created-at              :- java.util.Date
+                                      updated-at              :- java.util.Date]
   nil
   :load-ns true)
 
 ;; Field is the "expanded" form of a Field ID (field reference) in MBQL
-(s/defrecord Field [field-id           :- su/IntGreaterThanZero
-                    field-name         :- su/NonBlankString
-                    field-display-name :- su/NonBlankString
-                    database-type      :- su/NonBlankString
-                    base-type          :- su/FieldType
-                    special-type       :- (s/maybe su/FieldType)
-                    visibility-type    :- (apply s/enum field/visibility-types)
-                    table-id           :- su/IntGreaterThanZero
-                    schema-name        :- (s/maybe su/NonBlankString)
-                    table-name         :- (s/maybe su/NonBlankString) ; TODO - Why is this `maybe` ?
-                    position           :- (s/maybe su/IntGreaterThanZero)
-                    fk-field-id        :- (s/maybe s/Int)
-                    description        :- (s/maybe su/NonBlankString)
-                    parent-id          :- (s/maybe su/IntGreaterThanZero)
-                    ;; Field once its resolved; FieldPlaceholder before that
-                    parent             :- s/Any
-                    remapped-from      :- (s/maybe s/Str)
-                    remapped-to        :- (s/maybe s/Str)
-                    dimensions         :- (s/maybe (s/cond-pre Dimensions {} []))
-                    values             :- (s/maybe (s/cond-pre FieldValues {} []))
-                    fingerprint        :- (s/maybe i/Fingerprint)]
+(s/defrecord ^:deprecated Field [field-id           :- su/IntGreaterThanZero
+                                 field-name         :- su/NonBlankString
+                                 field-display-name :- su/NonBlankString
+                                 database-type      :- su/NonBlankString
+                                 base-type          :- su/FieldType
+                                 special-type       :- (s/maybe su/FieldType)
+                                 visibility-type    :- (apply s/enum field/visibility-types)
+                                 table-id           :- su/IntGreaterThanZero
+                                 schema-name        :- (s/maybe su/NonBlankString)
+                                 table-name         :- (s/maybe su/NonBlankString) ; TODO - Why is this `maybe` ?
+                                 position           :- (s/maybe su/IntGreaterThanZero)
+                                 fk-field-id        :- (s/maybe s/Int)
+                                 description        :- (s/maybe su/NonBlankString)
+                                 parent-id          :- (s/maybe su/IntGreaterThanZero)
+                                 ;; Field once its resolved; FieldPlaceholder before that
+                                 parent             :- s/Any
+                                 remapped-from      :- (s/maybe s/Str)
+                                 remapped-to        :- (s/maybe s/Str)
+                                 dimensions         :- (s/maybe (s/cond-pre Dimensions {} []))
+                                 values             :- (s/maybe (s/cond-pre FieldValues {} []))
+                                 fingerprint        :- (s/maybe i/Fingerprint)]
   nil
   :load-ns true
   clojure.lang.Named
-  (getName [_] field-name) ; (name <field>) returns the *unqualified* name of the field, #obvi
+  (getName [_] field-name)              ; (name <field>) returns the *unqualified* name of the field, #obvi
 
   IField
   (qualified-name-components [_]
@@ -150,38 +163,45 @@
 
 ;;; DateTimeField
 
-(def datetime-field-units
+(def ^:deprecated datetime-field-units
   "Valid units for a `DateTimeField`."
   #{:default :minute :minute-of-hour :hour :hour-of-day :day :day-of-week :day-of-month :day-of-year
     :week :week-of-year :month :month-of-year :quarter :quarter-of-year :year})
 
-(def relative-datetime-value-units
+(def ^:deprecated relative-datetime-value-units
   "Valid units for a `RelativeDateTimeValue`."
   #{:minute :hour :day :week :month :quarter :year})
 
-(def DatetimeFieldUnit
+(def ^:deprecated DatetimeFieldUnit
   "Schema for datetime units that are valid for `DateTimeField` forms."
   (s/named (apply s/enum datetime-field-units) "Valid datetime unit for a field"))
 
-(def DatetimeValueUnit
+(def ^:deprecated DatetimeValueUnit
   "Schema for datetime units that valid for relative datetime values."
   (s/named (apply s/enum relative-datetime-value-units) "Valid datetime unit for a relative datetime"))
 
-(defn datetime-field-unit?
+(defn ^:deprecated datetime-field-unit?
   "Is UNIT a valid datetime unit for a `DateTimeField` form?"
   [unit]
   (contains? datetime-field-units (keyword unit)))
 
-(defn relative-datetime-value-unit?
+(defn ^:deprecated relative-datetime-value-unit?
   "Is UNIT a valid datetime unit for a `RelativeDateTimeValue` form?"
   [unit]
   (contains? relative-datetime-value-units (keyword unit)))
 
+(def ^:deprecated binning-strategies
+  "Valid binning strategies for a `BinnedField`"
+  #{:num-bins :bin-width :default})
+
 ;; TODO - maybe we should figure out some way to have the schema validate that the driver supports field literals,
 ;; like we do for some of the other clauses. Ideally we'd do that in a more generic way (perhaps in expand, we could
 ;; make the clauses specify required feature metadata and have that get checked automatically?)
-(s/defrecord FieldLiteral [field-name    :- su/NonBlankString
-                           base-type     :- su/FieldType]
+(s/defrecord ^:deprecated FieldLiteral [field-name       :- su/NonBlankString
+                                        base-type        :- su/FieldType
+                                        binning-strategy :- (s/maybe (apply s/enum binning-strategies))
+                                        binning-param    :- (s/maybe s/Num)
+                                        fingerprint      :- (s/maybe i/Fingerprint)]
   nil
   :load-ns true
   clojure.lang.Named
@@ -190,42 +210,38 @@
   (qualified-name-components [_] [nil field-name]))
 
 ;; DateTimeField is just a simple wrapper around Field
-(s/defrecord DateTimeField [field :- (s/cond-pre Field FieldLiteral)
-                            unit  :- DatetimeFieldUnit]
+(s/defrecord ^:deprecated DateTimeField [field :- (s/cond-pre Field FieldLiteral)
+                                         unit  :- DatetimeFieldUnit]
   nil
   :load-ns true
   clojure.lang.Named
   (getName [_] (name field)))
 
 ;; TimeField is just a field wrapper that indicates string should be interpretted as a time
-(s/defrecord TimeField [field :- (s/cond-pre Field FieldLiteral)]
+(s/defrecord ^:deprecated TimeField [field :- (s/cond-pre Field FieldLiteral)]
   nil
   :load-ns true
   clojure.lang.Named
   (getName [_] (name field)))
 
-(s/defrecord TimeValue [value       :- Time
-                        field       :- TimeField
-                        timezone-id :- (s/maybe String)]
+(s/defrecord ^:deprecated TimeValue [value       :- Time
+                                     field       :- TimeField
+                                     timezone-id :- (s/maybe String)]
   nil
   :load-ns true)
 
-(def binning-strategies
-  "Valid binning strategies for a `BinnedField`"
-  #{:num-bins :bin-width :default})
-
-(s/defrecord BinnedField [field     :- Field
-                          strategy  :- (apply s/enum binning-strategies)
-                          num-bins  :- s/Int
-                          min-value :- s/Num
-                          max-value :- s/Num
-                          bin-width :- s/Num]
+(s/defrecord ^:deprecated BinnedField [field     :- (s/cond-pre Field FieldLiteral)
+                                       strategy  :- (apply s/enum binning-strategies)
+                                       num-bins  :- s/Int
+                                       min-value :- s/Num
+                                       max-value :- s/Num
+                                       bin-width :- s/Num]
   nil
   :load-ns true
   clojure.lang.Named
   (getName [_] (name field)))
 
-(s/defrecord ExpressionRef [expression-name :- su/NonBlankString]
+(s/defrecord ^:deprecated ExpressionRef [expression-name :- su/NonBlankString]
   nil
   :load-ns true
   clojure.lang.Named
@@ -237,7 +253,7 @@
 
 ;;; Placeholder Types. See explaination above RE what these mean
 
-(def FKFieldID
+(def ^:deprecated FKFieldID
   "Schema for an ID for a foreign key Field. If `*driver*` is bound this will throw an Exception if this is non-nil
   and the driver does not support foreign keys."
   (s/constrained
@@ -248,41 +264,41 @@
 ;; Replace Field IDs with these during first pass
 ;; fk-field-id = the ID of the Field we point to (if any). For example if we are 'bird_id` then that is the ID of
 ;; bird.id
-(s/defrecord FieldPlaceholder [field-id            :- su/IntGreaterThanZero
-                               fk-field-id         :- (s/maybe FKFieldID)
-                               datetime-unit       :- (s/maybe DatetimeFieldUnit)
-                               remapped-from       :- (s/maybe s/Str)
-                               remapped-to         :- (s/maybe s/Str)
-                               field-display-name  :- (s/maybe s/Str)
-                               binning-strategy    :- (s/maybe (apply s/enum binning-strategies))
-                               binning-param       :- (s/maybe s/Num)]
+(s/defrecord ^:deprecated FieldPlaceholder [field-id            :- su/IntGreaterThanZero
+                                            fk-field-id         :- (s/maybe FKFieldID)
+                                            datetime-unit       :- (s/maybe DatetimeFieldUnit)
+                                            remapped-from       :- (s/maybe s/Str)
+                                            remapped-to         :- (s/maybe s/Str)
+                                            field-display-name  :- (s/maybe s/Str)
+                                            binning-strategy    :- (s/maybe (apply s/enum binning-strategies))
+                                            binning-param       :- (s/maybe s/Num)]
   nil
   :load-ns true)
 
-(s/defrecord AgFieldRef [index :- s/Int]
+(s/defrecord ^:deprecated AgFieldRef [index :- s/Int]
   nil
   :load-ns true)
 ;; TODO - add a method to get matching expression from the query?
 
-(s/defrecord RelativeDatetime [amount :- s/Int
-                               unit   :- DatetimeValueUnit]
+(s/defrecord ^:deprecated RelativeDatetime [amount :- s/Int
+                                            unit   :- DatetimeValueUnit]
   nil
   :load-ns true)
 
 (declare Aggregation AnyField AnyValueLiteral)
 
-(def ^:private ExpressionOperator (s/named (s/enum :+ :- :* :/) "Valid expression operator"))
+(def ^:deprecated ^:private ExpressionOperator (s/named (s/enum :+ :- :* :/) "Valid expression operator"))
 
-(s/defrecord Expression [operator   :- ExpressionOperator
-                         args       :- [(s/cond-pre (s/recursive #'AnyValueLiteral)
-                                                    (s/recursive #'AnyField)
-                                                    (s/recursive #'Aggregation))]
-                         custom-name :- (s/maybe su/NonBlankString)]
+(s/defrecord ^:deprecated Expression [operator   :- ExpressionOperator
+                                      args       :- [(s/cond-pre (s/recursive #'AnyValueLiteral)
+                                                                 (s/recursive #'AnyField)
+                                                                 (s/recursive #'Aggregation))]
+                                      custom-name :- (s/maybe su/NonBlankString)]
   nil
   :load-ns true)
 
 
-(def AnyField
+(def ^:deprecated AnyField
   "Schema for anything that is considered a valid 'field' including placeholders, expressions, and literals."
   (s/named (s/cond-pre Field
                        FieldPlaceholder
@@ -298,25 +314,25 @@
 ;;; |                                                     VALUES                                                     |
 ;;; +----------------------------------------------------------------------------------------------------------------+
 
-(def LiteralDatetimeString
+(def ^:deprecated LiteralDatetimeString
   "Schema for an MBQL datetime string literal, in ISO-8601 format."
   (s/constrained su/NonBlankString du/date-string? "Valid ISO-8601 datetime string literal"))
 
-(def LiteralDatetime
+(def ^:deprecated LiteralDatetime
   "Schema for an MBQL literal datetime value: and ISO-8601 string or `java.sql.Date`."
   (s/named (s/cond-pre java.sql.Date LiteralDatetimeString)
            "Valid datetime literal (must be ISO-8601 string or java.sql.Date)"))
 
-(def Datetime
+(def ^:deprecated Datetime
   "Schema for an MBQL datetime value: an ISO-8601 string, `java.sql.Date`, or a relative dateitme form."
   (s/named (s/cond-pre RelativeDatetime LiteralDatetime)
            "Valid datetime (must ISO-8601 string literal or a relative-datetime form)"))
 
-(def OrderableValueLiteral
+(def ^:deprecated OrderableValueLiteral
   "Schema for something that is orderable value in MBQL (either a number or datetime)."
   (s/named (s/cond-pre s/Num Datetime) "Valid orderable value (must be number or datetime)"))
 
-(def AnyValueLiteral
+(def ^:deprecated AnyValueLiteral
   "Schema for anything that is a considered a valid value literal in MBQL - `nil`, a `Boolean`, `Number`, `String`, or
   relative datetime form."
   (s/named (s/maybe (s/cond-pre s/Bool su/NonBlankString OrderableValueLiteral))
@@ -326,25 +342,25 @@
 ;; Value is the expansion of a value within a QL clause
 ;; Information about the associated Field is included for convenience
 ;; TODO - Value doesn't need the whole field, just the relevant type info / units
-(s/defrecord Value [value   :- AnyValueLiteral
-                    field   :- (s/recursive #'AnyField)]
+(s/defrecord ^:deprecated Value [value   :- AnyValueLiteral
+                                 field   :- (s/recursive #'AnyField)]
   nil
   :load-ns true)
 
-(s/defrecord RelativeDateTimeValue [amount :- s/Int
-                                    unit   :- DatetimeValueUnit
-                                    field  :- (s/cond-pre DateTimeField
-                                                          FieldPlaceholder)]
+(s/defrecord ^:deprecated RelativeDateTimeValue [amount :- s/Int
+                                                 unit   :- DatetimeValueUnit
+                                                 field  :- (s/cond-pre DateTimeField
+                                                                       FieldPlaceholder)]
   nil
   :load-ns true)
 
 ;; e.g. an absolute point in time (literal)
-(s/defrecord DateTimeValue [value :- (s/maybe Timestamp)
-                            field :- DateTimeField]
+(s/defrecord ^:deprecated DateTimeValue [value :- (s/maybe Timestamp)
+                                         field :- DateTimeField]
   nil
   :load-ns true)
 
-(def OrderableValue
+(def ^:deprecated OrderableValue
   "Schema for an instance of `Value` whose `:value` property is itself orderable (a datetime or number, i.e. a
   `OrderableValueLiteral`)."
   (s/named (s/cond-pre
@@ -354,13 +370,13 @@
                                    (nil? (s/check OrderableValueLiteral value)))))
            "Value that is orderable (Value whose :value is something orderable, like a datetime or number)"))
 
-(def StringValue
+(def ^:deprecated StringValue
   "Schema for an instance of `Value` whose `:value` property is itself a string (a datetime or string, i.e. a
   `OrderableValueLiteral`)."
   (s/named (s/constrained Value (comp string? :value))
            "Value that is a string (Value whose :value is a string)"))
 
-(defprotocol ^:private IDateTimeValue
+(defprotocol ^:deprecated ^:private IDateTimeValue
   (unit [this]
     "Get the `unit` associated with a `DateTimeValue` or `RelativeDateTimeValue`.")
 
@@ -381,37 +397,37 @@
 
 ;; Replace values with these during first pass over Query.
 ;; Include associated Field ID so appropriate the info can be found during Field resolution
-(s/defrecord ValuePlaceholder [field-placeholder :- AnyField
-                               value             :- AnyValueLiteral]
+(s/defrecord ^:deprecated ValuePlaceholder [field-placeholder :- AnyField
+                                            value             :- AnyValueLiteral]
   nil
   :load-ns true)
 
-(def OrderableValuePlaceholder
+(def ^:deprecated OrderableValuePlaceholder
   "`ValuePlaceholder` schema with the additional constraint that the value be orderable (a number or datetime)."
   (s/constrained
    ValuePlaceholder
    (comp (complement (s/checker OrderableValueLiteral)) :value)
    ":value must be orderable (number or datetime)"))
 
-(def OrderableValueOrPlaceholder
+(def ^:deprecated OrderableValueOrPlaceholder
   "Schema for an `OrderableValue` (instance of `Value` whose `:value` is orderable) or a placeholder for one."
   (s/named (s/cond-pre OrderableValue OrderableValuePlaceholder)
            "Must be an OrderableValue or OrderableValuePlaceholder"))
 
-(def StringValuePlaceholder
+(def ^:deprecated StringValuePlaceholder
   "`ValuePlaceholder` schema with the additional constraint that the value be a string/"
   (s/constrained ValuePlaceholder (comp string? :value) ":value must be a string"))
 
-(def StringValueOrPlaceholder
+(def ^:deprecated StringValueOrPlaceholder
   "Schema for an `StringValue` (instance of `Value` whose `:value` is a string) or a placeholder for one."
   (s/named (s/cond-pre StringValue StringValuePlaceholder)
            "Must be an StringValue or StringValuePlaceholder"))
 
-(def AnyValue
+(def ^:deprecated AnyValue
   "Schema that accepts anything normally considered a value or value placeholder."
   (s/named (s/cond-pre DateTimeValue RelativeDateTimeValue Value ValuePlaceholder) "Valid value"))
 
-(def AnyFieldOrValue
+(def ^:deprecated AnyFieldOrValue
   "Schema that accepts anything normally considered a field or value."
   (s/named (s/cond-pre AnyField AnyValue) "Field or value"))
 
@@ -422,27 +438,28 @@
 
 ;;; aggregation
 
-(s/defrecord AggregationWithoutField [aggregation-type :- (s/named (s/enum :count :cumulative-count)
-                                                                   "Valid aggregation type")
-                                      custom-name      :- (s/maybe su/NonBlankString)]
+(s/defrecord ^:deprecated AggregationWithoutField [aggregation-type :- (s/named (s/enum :count :cumulative-count)
+                                                                                "Valid aggregation type")
+                                                   custom-name      :- (s/maybe su/NonBlankString)]
   nil
   :load-ns true)
 
-(s/defrecord AggregationWithField [aggregation-type :- (s/named (s/enum :avg :count :cumulative-sum :distinct :max
-                                                                        :min :stddev :sum)
-                                                                "Valid aggregation type")
-                                   field            :- (s/cond-pre AnyField
-                                                                   Expression)
-                                   custom-name      :- (s/maybe su/NonBlankString)]
+(s/defrecord ^:deprecated AggregationWithField [aggregation-type :- (s/named (s/enum :avg :count :cumulative-count
+                                                                                     :cumulative-sum :distinct :max
+                                                                                     :min :stddev :sum)
+                                                                             "Valid aggregation type")
+                                                field            :- (s/cond-pre AnyField
+                                                                                Expression)
+                                                custom-name      :- (s/maybe su/NonBlankString)]
   nil
   :load-ns true)
 
-(defn- valid-aggregation-for-driver? [{:keys [aggregation-type]}]
+(defn- ^:deprecated valid-aggregation-for-driver? [{:keys [aggregation-type]}]
   (when (= aggregation-type :stddev)
     (assert-driver-supports :standard-deviation-aggregations))
   true)
 
-(def Aggregation
+(def ^:deprecated Aggregation
   "Schema for an `aggregation` subclause in an MBQL query."
   (s/constrained
    (s/cond-pre AggregationWithField AggregationWithoutField Expression)
@@ -452,51 +469,51 @@
 
 ;;; filter
 
-(s/defrecord EqualityFilter [filter-type :- (s/enum := :!=)
-                             field       :- AnyField
-                             value       :- AnyFieldOrValue]
+(s/defrecord ^:deprecated EqualityFilter [filter-type :- (s/enum := :!=)
+                                          field       :- AnyField
+                                          value       :- AnyFieldOrValue]
   nil
   :load-ns true)
 
-(s/defrecord ComparisonFilter [filter-type :- (s/enum :< :<= :> :>=)
-                               field       :- AnyField
-                               value       :- OrderableValueOrPlaceholder]
+(s/defrecord ^:deprecated ComparisonFilter [filter-type :- (s/enum :< :<= :> :>=)
+                                            field       :- AnyField
+                                            value       :- OrderableValueOrPlaceholder]
   nil
   :load-ns true)
 
-(s/defrecord BetweenFilter [filter-type  :- (s/eq :between)
-                            min-val      :- OrderableValueOrPlaceholder
-                            field        :- AnyField
-                            max-val      :- OrderableValueOrPlaceholder]
+(s/defrecord ^:deprecated BetweenFilter [filter-type  :- (s/eq :between)
+                                         min-val      :- OrderableValueOrPlaceholder
+                                         field        :- AnyField
+                                         max-val      :- OrderableValueOrPlaceholder]
   nil
   :load-ns true)
 
-(s/defrecord StringFilter [filter-type     :- (s/enum :starts-with :contains :ends-with)
-                           field           :- AnyField
-                           ;; TODO - not 100% sure why this is also allowed to accept a plain string
-                           value           :- (s/cond-pre s/Str StringValueOrPlaceholder)
-                           case-sensitive? :- s/Bool]
+(s/defrecord ^:deprecated StringFilter [filter-type     :- (s/enum :starts-with :contains :ends-with)
+                                        field           :- AnyField
+                                        ;; TODO - not 100% sure why this is also allowed to accept a plain string
+                                        value           :- (s/cond-pre s/Str StringValueOrPlaceholder)
+                                        case-sensitive? :- s/Bool]
   nil
   :load-ns true)
 
-(def SimpleFilterClause
+(def ^:deprecated SimpleFilterClause
   "Schema for a non-compound, non-`not` MBQL `filter` clause."
   (s/named (s/cond-pre EqualityFilter ComparisonFilter BetweenFilter StringFilter)
            "Simple filter clause"))
 
-(s/defrecord NotFilter [compound-type :- (s/eq :not)
-                        subclause     :- SimpleFilterClause]
+(s/defrecord ^:deprecated NotFilter [compound-type :- (s/eq :not)
+                                     subclause     :- SimpleFilterClause]
   nil
   :load-ns true)
 
 (declare Filter)
 
-(s/defrecord CompoundFilter [compound-type :- (s/enum :and :or)
-                             subclauses    :- [(s/recursive #'Filter)]]
+(s/defrecord ^:deprecated CompoundFilter [compound-type :- (s/enum :and :or)
+                                          subclauses    :- [(s/recursive #'Filter)]]
   nil
   :load-ns true)
 
-(def Filter
+(def ^:deprecated Filter
   "Schema for top-level `filter` clause in an MBQL query."
   (s/named (s/cond-pre SimpleFilterClause NotFilter CompoundFilter)
            "Valid filter clause"))
@@ -504,11 +521,11 @@
 
 ;;; order-by
 
-(def OrderByDirection
+(def ^:deprecated OrderByDirection
   "Schema for the direction in an `OrderBy` subclause."
   (s/named (s/enum :ascending :descending) "Valid order-by direction"))
 
-(def OrderBy
+(def ^:deprecated OrderBy
   "Schema for top-level `order-by` clause in an MBQL query."
   (s/named {:field     AnyField
             :direction OrderByDirection}
@@ -517,7 +534,7 @@
 
 ;;; page
 
-(def Page
+(def ^:deprecated Page
   "Schema for the top-level `page` clause in a MBQL query."
   (s/named {:page  su/IntGreaterThanZero
             :items su/IntGreaterThanZero}
@@ -526,13 +543,11 @@
 
 ;;; source-query
 
-(declare Query)
-
-(def SourceQuery
+(def ^:deprecated SourceQuery
   "Schema for a valid value for a `:source-query` clause."
   (s/if :native
     {:native                         s/Any
-     (s/optional-key :template_tags) s/Any}
+     (s/optional-key :template-tags) s/Any}
     (s/recursive #'Query)))
 
 
@@ -540,7 +555,7 @@
 ;;; |                                                     QUERY                                                      |
 ;;; +----------------------------------------------------------------------------------------------------------------+
 
-(def Query
+(def ^:deprecated Query
   "Schema for an MBQL query."
   (s/constrained
    {(s/optional-key :aggregation)  [Aggregation]
diff --git a/src/metabase/query_processor/middleware/add_dimension_projections.clj b/src/metabase/query_processor/middleware/add_dimension_projections.clj
index e6f3629f63cc566443209698c5c1599538b7c247..e3efc9d520a1e5ed6e7ff7606c6af833d07fa659 100644
--- a/src/metabase/query_processor/middleware/add_dimension_projections.clj
+++ b/src/metabase/query_processor/middleware/add_dimension_projections.clj
@@ -1,6 +1,9 @@
 (ns metabase.query-processor.middleware.add-dimension-projections
   "Middleware for adding remapping and other dimension related projections"
-  (:require [metabase.query-processor.interface :as i]))
+  (:require [metabase.query-processor
+             [interface :as i]
+             [util :as qputil]]
+            [metabase.query-processor.middleware.resolve :as resolve]))
 
 (defn- create-remapped-col [col-name remapped-from]
   {:description     nil
@@ -97,11 +100,13 @@
   "Function that will include FK references needed for external remappings. This will then flow through to the resolver
   to get the new tables included in the join."
   [query]
-  (let [remap-col-pairs (create-remap-col-pairs (get-in query [:query :fields]))]
+  (let [resolved-fields (resolve/resolve-fields-if-needed (qputil/get-in-query query [:fields]))
+        remap-col-pairs (create-remap-col-pairs resolved-fields)]
     (if (seq remap-col-pairs)
-      (-> query
-          (update-in [:query :order-by] #(update-remapped-order-by (into {} remap-col-pairs) %))
-          (update-in [:query :fields] concat (map second remap-col-pairs)))
+      (let [order-by (qputil/get-in-query query [:order-by])]
+        (-> query
+            (qputil/assoc-in-query [:order-by] (update-remapped-order-by (into {} remap-col-pairs) order-by))
+            (qputil/assoc-in-query [:fields] (concat resolved-fields (map second remap-col-pairs)))))
       query)))
 
 (defn- remap-results
diff --git a/src/metabase/query_processor/middleware/add_implicit_clauses.clj b/src/metabase/query_processor/middleware/add_implicit_clauses.clj
index 77ff8cc38a44ef059bb1816bd42e21012319cd97..820409c2e09ae0d7f7cf88ba86f7418f1a0c51eb 100644
--- a/src/metabase/query_processor/middleware/add_implicit_clauses.clj
+++ b/src/metabase/query_processor/middleware/add_implicit_clauses.clj
@@ -26,17 +26,16 @@
 
 (defn- fields-for-source-table
   "Return the all fields for SOURCE-TABLE, for use as an implicit `:fields` clause."
-  [inner-query]
+  [{{source-table-id :id, :as source-table} :source-table, :as inner-query}]
   ;; Sort the implicit FIELDS so the SQL (or other native query) that gets generated (mostly) approximates the 'magic' sorting
   ;; we do on the results. This is done so when the outer query we generate is a `SELECT *` the order doesn't change
-  (let [{source-table-id :id, :as source-table} (qputil/get-normalized inner-query :source-table)]
-    (for [field (sort/sort-fields inner-query (fetch-fields-for-souce-table-id source-table-id))
-          :let  [field (-> field
-                           resolve/convert-db-field
-                           (resolve/resolve-table {[nil source-table-id] source-table}))]]
-      (if (qputil/datetime-field? field)
-        (i/map->DateTimeField {:field field, :unit :default})
-        field))))
+  (for [field (sort/sort-fields inner-query (fetch-fields-for-souce-table-id source-table-id))
+        :let  [field (-> field
+                         resolve/convert-db-field
+                         (resolve/resolve-table {[nil source-table-id] source-table}))]]
+    (if (qputil/datetime-field? field)
+      (i/map->DateTimeField {:field field, :unit :default})
+      field)))
 
 (defn- should-add-implicit-fields? [{:keys [fields breakout source-table], aggregations :aggregation}]
   (and source-table ; if query is using another query as its source then there will be no table to add nested fields for
diff --git a/src/metabase/query_processor/middleware/annotate_and_sort.clj b/src/metabase/query_processor/middleware/annotate_and_sort.clj
index 3b8b13e43c349d69d740e26ac6946cb09da7b22a..2c408596225625556b6028c7fdff93b1737696fe 100644
--- a/src/metabase/query_processor/middleware/annotate_and_sort.clj
+++ b/src/metabase/query_processor/middleware/annotate_and_sort.clj
@@ -26,8 +26,9 @@
                         :let [col (nth columns i)]]
                     {:name         (name col)
                      :display_name (humanization/name->human-readable-name (name col))
-                     :base_type    (driver/values->base-type (for [row rows]
-                                                               (nth row i)))}))))
+                     :base_type    (or (driver/values->base-type (for [row rows]
+                                                                   (nth row i)))
+                                       :type/*)}))))
 
 
 ;;; +------------------------------------------------------------------------------------------------------------------------+
diff --git a/src/metabase/query_processor/middleware/binning.clj b/src/metabase/query_processor/middleware/binning.clj
index 1cde0589b6d6b03754300630ad61b024340dbdec..53c333bb23e80575af7357f2860499e1f9185a0f 100644
--- a/src/metabase/query_processor/middleware/binning.clj
+++ b/src/metabase/query_processor/middleware/binning.clj
@@ -3,7 +3,8 @@
             [clojure.walk :as walk]
             [metabase
              [public-settings :as public-settings]
-             [util :as u]])
+             [util :as u]]
+            [metabase.query-processor.util :as qputil])
   (:import [metabase.query_processor.interface BetweenFilter BinnedField ComparisonFilter]))
 
 (defn- update!
@@ -162,8 +163,6 @@
   (fn [query]
     (let [filter-field-map (filter->field-map (get-in query [:query :filter]))]
       (qp
-       (walk/postwalk (fn [node]
-                        (if (instance? BinnedField node)
-                          (update-binned-field node filter-field-map)
-                          node))
-                      query)))))
+       (qputil/postwalk-pred #(instance? BinnedField %)
+                             #(update-binned-field % filter-field-map)
+                             query)))))
diff --git a/src/metabase/query_processor/middleware/cache.clj b/src/metabase/query_processor/middleware/cache.clj
index eedf5b2e2bdee3c6ba10ab10db9ec4d9261256c7..58b088a3414e9e15de0a3f666f58fb683650f9fe 100644
--- a/src/metabase/query_processor/middleware/cache.clj
+++ b/src/metabase/query_processor/middleware/cache.clj
@@ -122,6 +122,7 @@
     results))
 
 (defn- run-query-with-cache [qp {cache-ttl :cache_ttl, :as query}]
+  ;; TODO - Query should already have a `info.hash`, shouldn't it?
   (let [query-hash (qputil/query-hash query)]
     (or (cached-results query-hash cache-ttl)
         (run-query-and-save-results-if-successful! query-hash qp query))))
diff --git a/src/metabase/query_processor/middleware/expand.clj b/src/metabase/query_processor/middleware/expand.clj
index 19ca6f57fd6e67e5d554cbb53c0d5a2bf82f7c51..91222d3d45d565ec7cfc7da5b34e633c26ed32f8 100644
--- a/src/metabase/query_processor/middleware/expand.clj
+++ b/src/metabase/query_processor/middleware/expand.clj
@@ -1,4 +1,4 @@
-(ns metabase.query-processor.middleware.expand
+(ns ^:deprecated metabase.query-processor.middleware.expand
   "Converts a Query Dict as received by the API into an *expanded* one that contains extra information that will be
   needed to construct the appropriate native Query, and perform various post-processing steps such as Field ordering."
   (:refer-clojure :exclude [< <= > >= = != and or not filter count distinct sum min max + - / *])
@@ -21,16 +21,7 @@
 ;;; |                                                CLAUSE HANDLERS                                                 |
 ;;; +----------------------------------------------------------------------------------------------------------------+
 
-;; TODO - check that there's a matching :aggregation clause in the query ?
-(s/defn ^:ql aggregate-field :- AgFieldRef
-  "Aggregate field referece, e.g. for use in an `order-by` clause.
-
-     (query (aggregate (count))
-            (order-by (asc (aggregate-field 0)))) ; order by :count"
-  [index :- s/Int]
-  (i/map->AgFieldRef {:index index}))
-
-(s/defn ^:ql field-id :- i/AnyField
+(s/defn ^:deprecated ^:ql field-id :- i/AnyField
   "Create a generic reference to a `Field` with ID."
   [id]
   ;; If for some reason we were passed a field literal (e.g. [field-id [field-literal ...]])
@@ -42,7 +33,7 @@
       id)
     (i/map->FieldPlaceholder {:field-id id})))
 
-(s/defn ^:private field :- i/AnyField
+(s/defn ^:deprecated ^:private field :- i/AnyField
   "Generic reference to a `Field`. F can be an integer Field ID, or various other forms like `fk->` or `aggregation`."
   [f]
   (if (integer? f)
@@ -50,13 +41,13 @@
         (field-id f))
     f))
 
-(s/defn ^:ql field-literal :- FieldLiteral
+(s/defn ^:deprecated ^:ql field-literal :- FieldLiteral
   "Generic reference to a Field by FIELD-NAME. This is intended for use when using nested queries so as to allow one
    to refer to the fields coming back from the source query."
   [field-name :- su/KeywordOrString, field-type :- su/KeywordOrString]
   (i/map->FieldLiteral {:field-name (u/keyword->qualified-name field-name), :base-type (keyword field-type)}))
 
-(s/defn ^:ql named :- i/Aggregation
+(s/defn ^:deprecated ^:ql named :- i/Aggregation
   "Specify a CUSTOM-NAME to use for a top-level AGGREGATION-OR-EXPRESSION in the results.
    (This will probably be extended to support Fields in the future, but for now, only the `:aggregation` clause is
    supported.)"
@@ -64,7 +55,7 @@
   [aggregation-or-expression :- i/Aggregation, custom-name :- su/NonBlankString]
   (assoc aggregation-or-expression :custom-name custom-name))
 
-(s/defn ^:ql datetime-field :- i/AnyField
+(s/defn ^:deprecated ^:ql datetime-field :- i/AnyField
   "Reference to a `DateTimeField`. This is just a `Field` reference with an associated datetime UNIT."
   ([f _ unit]
    (log/warn (u/format-color 'yellow (str "The syntax for datetime-field has changed in MBQL '98. "
@@ -79,18 +70,23 @@
      ;; (:datetime-unit f)          f
      :else                       (assoc (field f) :datetime-unit (qputil/normalize-token unit)))))
 
-(s/defn ^:ql fk-> :- FieldPlaceholder
+(s/defn ^:deprecated ^:ql fk-> :- FieldPlaceholder
   "Reference to a `Field` that belongs to another `Table`. DEST-FIELD-ID is the ID of this Field, and FK-FIELD-ID is
    the ID of the foreign key field belonging to the *source table* we should use to perform the join.
 
    `fk->` is so named because you can think of it as \"going through\" the FK Field to get to the dest Field:
 
      (fk-> 100 200) ; refer to Field 200, which is part of another Table; join to the other table via our foreign key 100"
-  [fk-field-id :- s/Int, dest-field-id :- s/Int]
+  [fk-field-id, dest-field-id]
   (i/assert-driver-supports :foreign-keys)
-  (i/map->FieldPlaceholder {:fk-field-id fk-field-id, :field-id dest-field-id}))
-
-(defn- datetime-unit
+  (i/map->FieldPlaceholder {:fk-field-id (if (instance? FieldPlaceholder fk-field-id)
+                                           (:field-id fk-field-id)
+                                           fk-field-id)
+                            :field-id    (if (instance? FieldPlaceholder dest-field-id)
+                                           (:field-id dest-field-id)
+                                           dest-field-id)}))
+
+(defn- ^:deprecated datetime-unit
   "Determine the appropriate datetime unit that should be used for a field F and a value V.
 
   (Sometimes the value may already have a 'default' value that should be replaced with the value from the field it is
@@ -103,7 +99,7 @@
                                    (:unit f)
                                    (:unit v))))
 
-(s/defn ^:private value :- i/AnyValue
+(s/defn ^:deprecated ^:private value :- i/AnyValue
   "Literal value. F is the `Field` it relates to, and V is `nil`, or a boolean, string, numerical, or datetime value."
   [f v]
   (cond
@@ -119,7 +115,7 @@
                                           (i/map->Value {:value v, :field f}))
     :else                               (i/map->ValuePlaceholder {:field-placeholder (field f), :value v})))
 
-(s/defn ^:private field-or-value
+(s/defn ^:deprecated ^:private field-or-value
   "Use instead of `value` when something may be either a field or a value."
   [f v]
 
@@ -128,7 +124,7 @@
     v
     (value f v)))
 
-(s/defn ^:ql relative-datetime :- RelativeDatetime
+(s/defn ^:deprecated ^:ql relative-datetime :- RelativeDatetime
   "Value that represents a point in time relative to each moment the query is ran, e.g. \"today\" or \"1 year ago\".
 
    With `:current` as the only arg, refer to the current point in time; otherwise N is some number and UNIT is a unit
@@ -142,7 +138,7 @@
                                                                    :day                        ; give :unit a default value so we can simplify the schema a bit and require a :unit
                                                                    (qputil/normalize-token unit))})))
 
-(s/defn ^:ql expression :- ExpressionRef
+(s/defn ^:deprecated ^:ql expression :- ExpressionRef
   {:added "0.17.0"}
   [expression-name :- su/KeywordOrString]
   (i/strict-map->ExpressionRef {:expression-name (name expression-name)}))
@@ -150,7 +146,7 @@
 
 ;;; ## aggregation
 
-(defn- field-or-expression [f]
+(defn- ^:deprecated field-or-expression [f]
   (if (instance? Expression f)
     ;; recursively call field-or-expression on all the args inside the expression unless they're numbers
     ;; plain numbers are always assumed to be numeric literals here; you must use MBQL '98 `:field-id` syntax to refer
@@ -162,7 +158,7 @@
     ;; otherwise if it's not an Expression it's a Field
     (field f)))
 
-(s/defn ^:private ag-with-field :- i/Aggregation [ag-type f]
+(s/defn ^:deprecated ^:private ag-with-field :- i/Aggregation [ag-type f]
   (i/map->AggregationWithField {:aggregation-type ag-type, :field (field-or-expression f)}))
 
 (def ^:ql ^{:arglists '([f])} avg      "Aggregation clause. Return the average value of F."                (partial ag-with-field :avg))
@@ -172,37 +168,32 @@
 (def ^:ql ^{:arglists '([f])} min      "Aggregation clause. Return the minimum value of F."                (partial ag-with-field :min))
 (def ^:ql ^{:arglists '([f])} max      "Aggregation clause. Return the maximum value of F."                (partial ag-with-field :max))
 
-(defn ^:ql stddev
+(defn ^:deprecated ^:ql stddev
   "Aggregation clause. Return the standard deviation of values of F.
    Requires the feature `:standard-deviation-aggregations`."
   [f]
   (i/assert-driver-supports :standard-deviation-aggregations)
   (ag-with-field :stddev f))
 
-(s/defn ^:ql count :- i/Aggregation
+(s/defn ^:deprecated ^:ql count :- i/Aggregation
   "Aggregation clause. Return total row count (e.g., `COUNT(*)`). If F is specified, only count rows where F is non-null (e.g. `COUNT(f)`)."
   ([]  (i/map->AggregationWithoutField {:aggregation-type :count}))
   ([f] (ag-with-field :count f)))
 
-(s/defn ^:ql cum-count :- i/Aggregation
+(s/defn ^:deprecated ^:ql cum-count :- i/Aggregation
   "Aggregation clause. Return the cumulative row count (presumably broken out in some way)."
-  []
-  (i/map->AggregationWithoutField {:aggregation-type :cumulative-count}))
+  ([]
+   (i/map->AggregationWithoutField {:aggregation-type :cumulative-count}))
+  ([f] (ag-with-field :cumulative-count f)))
 
-(defn ^:ql ^:deprecated rows
-  "Bare rows aggregation. This is the default behavior, so specifying it is deprecated."
-  []
-  (log/warn (u/format-color 'yellow "Specifying :rows as the aggregation type is deprecated in MBQL '98. This is the default behavior, so you don't need to specify it.")))
-
-(s/defn ^:ql aggregation
+(s/defn ^:deprecated ^:ql aggregation
   "Specify the aggregation to be performed for this query.
 
      (aggregation {} (count 100))
      (aggregation {} :count 100))"
   ;; Handle ag field references like [:aggregation 0] (deprecated)
   ([index :- s/Int]
-   (log/warn "The syntax for aggregate fields has changed in MBQL '98. Instead of `[:aggregation 0]`, please use `[:aggregate-field 0]` instead.")
-   (aggregate-field index))
+   (i/map->AgFieldRef {:index index}))
 
   ;; Handle :aggregation top-level clauses. This is either a single map (single aggregation) or a vector of maps
   ;; (multiple aggregations)
@@ -225,7 +216,7 @@
 
 ;;; ## breakout & fields
 
-(s/defn ^:ql binning-strategy :- FieldPlaceholder
+(s/defn ^:deprecated ^:ql binning-strategy :- (s/cond-pre FieldPlaceholder FieldLiteral)
   "Reference to a `BinnedField`. This is just a `Field` reference with an associated `STRATEGY-NAME` and
   `STRATEGY-PARAM`"
   ([f strategy-name & [strategy-param]]
@@ -233,7 +224,7 @@
          field (field f)]
      (assoc field :binning-strategy strategy, :binning-param strategy-param))))
 
-(defn- fields-list-clause
+(defn- ^:deprecated fields-list-clause
   ([_ query] query)
   ([k query & fields] (assoc query k (mapv field fields))))
 
@@ -242,7 +233,7 @@
 
 ;;; ## filter
 
-(s/defn ^:private compound-filter :- i/Filter
+(s/defn ^:deprecated ^:private compound-filter :- i/Filter
   ([compound-type, subclause :- i/Filter]
    (log/warn (u/format-color 'yellow "You shouldn't specify an %s filter with only one subclause." compound-type))
    subclause)
@@ -253,7 +244,7 @@
 (def ^:ql ^{:arglists '([& subclauses])} and "Filter subclause. Return results that satisfy *all* SUBCLAUSES." (partial compound-filter :and))
 (def ^:ql ^{:arglists '([& subclauses])} or  "Filter subclause. Return results that satisfy *any* of the SUBCLAUSES." (partial compound-filter :or))
 
-(s/defn ^:private equality-filter :- i/Filter
+(s/defn ^:deprecated ^:private equality-filter :- i/Filter
   ([filter-type _ f v]
    (i/map->EqualityFilter {:filter-type filter-type, :field (field f), :value (field-or-value f v)}))
   ([filter-type compound-fn f v & more]
@@ -276,10 +267,10 @@
      (!= f v1 v2) ; same as (and (!= f v1) (!= f v2))"
   (partial equality-filter :!= and))
 
-(defn ^:ql is-null  "Filter subclause. Return results where F is `nil`."     [f] (=  f nil)) ; TODO - Should we deprecate these? They're syntactic sugar, and not particualarly useful.
-(defn ^:ql not-null "Filter subclause. Return results where F is not `nil`." [f] (!= f nil)) ; not-null is doubly unnecessary since you could just use `not` instead.
+(defn ^:deprecated ^:ql is-null  "Filter subclause. Return results where F is `nil`."     [f] (=  f nil)) ; TODO - Should we deprecate these? They're syntactic sugar, and not particualarly useful.
+(defn ^:deprecated ^:ql not-null "Filter subclause. Return results where F is not `nil`." [f] (!= f nil)) ; not-null is doubly unnecessary since you could just use `not` instead.
 
-(s/defn ^:private comparison-filter :- ComparisonFilter [filter-type f v]
+(s/defn ^:deprecated ^:private comparison-filter :- ComparisonFilter [filter-type f v]
   (i/map->ComparisonFilter {:filter-type filter-type, :field (field f), :value (value f v)}))
 
 (def ^:ql ^{:arglists '([f v])} <  "Filter subclause. Return results where F is less than V. V must be orderable, i.e. a number or datetime."                (partial comparison-filter :<))
@@ -287,20 +278,20 @@
 (def ^:ql ^{:arglists '([f v])} >  "Filter subclause. Return results where F is greater than V. V must be orderable, i.e. a number or datetime."             (partial comparison-filter :>))
 (def ^:ql ^{:arglists '([f v])} >= "Filter subclause. Return results where F is greater than or equal to V. V must be orderable, i.e. a number or datetime." (partial comparison-filter :>=))
 
-(s/defn ^:ql between :- BetweenFilter
+(s/defn ^:deprecated ^:ql between :- BetweenFilter
   "Filter subclause. Return results where F is between MIN and MAX. MIN and MAX must be orderable, i.e. numbers or datetimes.
    This behaves like SQL `BETWEEN`, i.e. MIN and MAX are inclusive."
   [f min-val max-val]
   (i/map->BetweenFilter {:filter-type :between, :field (field f), :min-val (value f min-val), :max-val (value f max-val)}))
 
-(s/defn ^:ql inside :- CompoundFilter
+(s/defn ^:deprecated ^:ql inside :- CompoundFilter
   "Filter subclause for geo bounding. Return results where LAT-FIELD and LON-FIELD are between some set of bounding values."
   [lat-field lon-field lat-max lon-min lat-min lon-max]
   (and (between lat-field lat-min lat-max)
        (between lon-field lon-min lon-max)))
 
 
-(s/defn ^:private string-filter :- StringFilter
+(s/defn ^:deprecated ^:private string-filter :- StringFilter
   "String search filter clauses: `contains`, `starts-with`, and `ends-with`. First shipped in `0.11.0` (before initial
   public release) but only supported case-sensitive searches. In `0.29.0` support for case-insensitive searches was
   added. For backwards-compatibility, and to avoid possible performance implications, case-sensitive is the default
@@ -316,7 +307,7 @@
     {:filter-type     filter-type
      :field           (field f)
      :value           (value f s)
-     :case-sensitive? (qputil/get-normalized options-map :case-sensitive true)})))
+     :case-sensitive? (get options-map :case-sensitive true)})))
 
 (def ^:ql ^{:arglists '([f s] [f s options-map])} starts-with
   "Filter subclause. Return results where F starts with the string S. By default, is case-sensitive, but you may pass an
@@ -334,7 +325,7 @@
   (partial string-filter :ends-with))
 
 
-(s/defn ^:ql not :- i/Filter
+(s/defn ^:deprecated ^:ql not :- i/Filter
   "Filter subclause. Return results that do *not* satisfy SUBCLAUSE.
 
    For the sake of simplifying driver implementation, `not` automatically translates its argument to a simpler,
@@ -366,7 +357,7 @@
   "Filter subclause. Return results where F does not start with the string S."
   (comp not contains))
 
-(s/defn ^:ql time-interval :- i/Filter
+(s/defn ^:deprecated ^:ql time-interval :- i/Filter
   "Filter subclause. Syntactic sugar for specifying a specific time interval.
 
  Optionally accepts a map of `options`. The following options are currently implemented:
@@ -387,7 +378,7 @@
       :last    (recur f -1 unit options)
       :next    (recur f  1 unit options))
     (let [f                (datetime-field f unit)
-          include-current? (qputil/get-normalized options :include-current)]
+          include-current? (:include-current options)]
       (cond
         (core/= n  0) (= f (value f (relative-datetime  0 unit)))
         (core/= n -1) (= f (value f (relative-datetime -1 unit)))
@@ -397,7 +388,7 @@
         (core/> n  1) (between f (value f (relative-datetime (if include-current? 0  1) unit))
                                  (value f (relative-datetime                          n unit)))))))
 
-(s/defn ^:ql filter
+(s/defn ^:deprecated ^:ql filter
   "Filter the results returned by the query.
 
      (filter {} := 100 true) ; return rows where Field 100 == true"
@@ -406,7 +397,7 @@
     (assoc query :filter filter-map)
     query))
 
-(s/defn ^:ql limit
+(s/defn ^:deprecated ^:ql limit
   "Limit the number of results returned by the query.
 
      (limit {} 10)"
@@ -418,7 +409,7 @@
 
 ;;; ## order-by
 
-(s/defn ^:private order-by-subclause :- i/OrderBy
+(s/defn ^:deprecated ^:private order-by-subclause :- i/OrderBy
   [direction :- i/OrderByDirection, f]
   ;; it's not particularly useful to sort datetime fields with the default `:day` bucketing,
   ;; so specifiy `:default` bucketing to prevent the default of `:day` from being set during resolution.
@@ -442,7 +433,7 @@
      (order-by {} (desc 100))"
   (partial order-by-subclause :descending))
 
-(s/defn ^:private maybe-parse-order-by-subclause :- i/OrderBy
+(s/defn ^:deprecated ^:private maybe-parse-order-by-subclause :- i/OrderBy
   [subclause]
   (cond
     (map? subclause)    subclause ; already parsed by `asc` or `desc`
@@ -450,12 +441,12 @@
                           (log/warn (u/format-color 'yellow "The syntax for order-by has changed in MBQL '98. [<field> :ascending/:descending] is deprecated. Prefer [:asc/:desc <field>] instead."))
                           (order-by-subclause (qputil/normalize-token direction) f))))
 
-(defn ^:ql order-by
+(defn ^:deprecated ^:ql order-by
   "Specify how ordering should be done for this query.
 
      (order-by {} (asc 20))        ; order by field 20
      (order-by {} [20 :ascending]) ; order by field 20 (deprecated/legacy syntax)
-     (order-by {} [(aggregate-field 0) :descending]) ; order by the aggregate field (e.g. :count)"
+     (order-by {} [(aggregation 0) :descending]) ; order by the aggregate field (e.g. :count)"
   ([query] query)
   ([query & subclauses]
    (assoc query :order-by (mapv maybe-parse-order-by-subclause subclauses))))
@@ -463,7 +454,7 @@
 
 ;;; ## page
 
-(s/defn ^:ql page
+(s/defn ^:deprecated ^:ql page
   "Specify which 'page' of results to fetch (offset and limit the results).
 
      (page {} {:page 1, :items 20}) ; fetch first 20 rows"
@@ -474,7 +465,7 @@
 
 ;;; ## source-table
 
-(s/defn ^:ql source-table
+(s/defn ^:deprecated ^:ql source-table
   "Specify the ID of the table to query.
    Queries must specify *either* `:source-table` or `:source-query`.
 
@@ -484,7 +475,7 @@
 
 (declare expand-inner)
 
-(s/defn ^:ql source-query
+(s/defn ^:deprecated ^:ql source-query
   "Specify a query to use as the source for this query (e.g., as a `SUBSELECT`).
    Queries must specify *either* `:source-table` or `:source-query`.
 
@@ -499,13 +490,13 @@
 
 ;;; ## calculated columns
 
-(s/defn ^:ql expressions
+(s/defn ^:deprecated ^:ql expressions
   "Top-level clause. Add additional calculated fields to a query."
   {:added "0.17.0"}
   [query, m :- {s/Keyword Expression}]
   (assoc query :expressions m))
 
-(s/defn ^:private expression-fn :- Expression
+(s/defn ^:deprecated ^:private expression-fn :- Expression
   [k :- s/Keyword, & args]
   (i/map->Expression {:operator k, :args (vec (for [arg args]
                                                 (if (number? arg)
@@ -524,8 +515,8 @@
 ;; consist of custom `metric` and `segment` clauses we need to at least accept them without barfing so we can expand a
 ;; query in order to check what permissions it requires.  TODO - in the future, we should just make these functions
 ;; expand Metric and Segment macros for consistency with the rest of the MBQL clauses
-(defn ^:ql metric  "Placeholder expansion function for GA metric clauses. (This does not expand normal Metric macros; that is done in `metabase.query-processor.macros`.)"   [& _])
-(defn ^:ql segment "Placeholder expansion function for GA segment clauses. (This does not expand normal Segment macros; that is done in `metabase.query-processor.macros`.)" [& _])
+(defn ^:deprecated ^:ql metric  "Placeholder expansion function for GA metric clauses. (This does not expand normal Metric macros; that is done in `metabase.query-processor.macros`.)"   [& _])
+(defn ^:deprecated ^:ql segment "Placeholder expansion function for GA segment clauses. (This does not expand normal Segment macros; that is done in `metabase.query-processor.macros`.)" [& _])
 
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
@@ -539,7 +530,7 @@
                  :when       (:ql (meta varr))]
              {(keyword symb) varr})))
 
-(defn- fn-for-token
+(defn- ^:deprecated fn-for-token
   "Return fn var that matches a token, or throw an exception.
 
      (fn-for-token :starts-with) -> #'starts-with"
@@ -548,7 +539,7 @@
     (core/or (token->ql-fn token)
              (throw (Exception. (str "Illegal clause (no matching fn found): " token))))))
 
-(s/defn expand-ql-sexpr
+(s/defn ^:deprecated expand-ql-sexpr
   "Expand a QL bracketed S-expression by dispatching to the appropriate `^:ql` function. If SEXPR is not a QL
    S-expression (the first item isn't a token), it is returned as-is.
 
@@ -559,7 +550,7 @@
     (apply (fn-for-token token) args)
     sexpr))
 
-(defn- walk-expand-ql-sexprs
+(defn ^:deprecated walk-expand-ql-sexprs
   "Walk QUERY depth-first and expand QL bracketed S-expressions."
   [x]
   (cond (map? x)        (into x (for [[k v] x]                    ; do `into x` instead of `into {}` so we can keep the original class,
@@ -568,7 +559,7 @@
         :else           x))
 
 
-(s/defn expand-inner :- i/Query
+(s/defn ^:deprecated expand-inner :- i/Query
   "Expand an inner query map."
   [inner-query :- (s/pred map?)]
   (loop [query {}, [[clause-name arg] & more] (seq inner-query)]
@@ -583,7 +574,7 @@
         (recur query more)
         query))))
 
-(defn expand
+(defn ^:deprecated expand
   "Expand a query dictionary as it comes in from the API and return an \"expanded\" form, (almost) ready for use by
    the Query Processor. This includes steps like token normalization and function dispatch.
 
@@ -600,7 +591,7 @@
   [outer-query]
   (update outer-query :query expand-inner))
 
-(defn expand-middleware
+(defn ^:deprecated expand-middleware
   "Wraps `expand` in a query-processor middleware function"
   [qp]
   (fn [query]
@@ -608,21 +599,12 @@
           (expand query)
           query))))
 
-(defmacro query
-  "Build a query by threading an (initially empty) map through each form in BODY with `->`.
-   The final result is validated against the `Query` schema."
-  {:style/indent 0}
-  [& body]
-  `(-> {}
-       ~@body
-       expand-inner))
-
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                                OTHER HELPER FNS                                                |
 ;;; +----------------------------------------------------------------------------------------------------------------+
 
-(defn is-clause?
+(defn ^:deprecated ^:deprecated is-clause?
   "Check to see whether CLAUSE is an instance of the clause named by normalized CLAUSE-KEYWORD.
 
      (is-clause? :field-id [\"FIELD-ID\" 2000]) ; -> true"
diff --git a/src/metabase/query_processor/middleware/expand_macros.clj b/src/metabase/query_processor/middleware/expand_macros.clj
index b40b54d579a4dcb34a5d4bb90f3ba5c828630547..524d0b550e59445940ccb7c83d69e471dfd41edd 100644
--- a/src/metabase/query_processor/middleware/expand_macros.clj
+++ b/src/metabase/query_processor/middleware/expand_macros.clj
@@ -8,7 +8,7 @@
    TODO - this namespace is ancient and written with MBQL '95 in mind, e.g. it is case-sensitive.
    At some point this ought to be reworked to be case-insensitive and cleaned up."
   (:require [clojure.tools.logging :as log]
-            [clojure.walk :as walk]
+            [metabase.mbql.util :as mbql.u]
             [metabase.models
              [metric :refer [Metric]]
              [segment :refer [Segment]]]
@@ -16,60 +16,33 @@
              [interface :as i]
              [util :as qputil]]
             [metabase.util :as u]
+            [puppetlabs.i18n.core :refer [tru]]
             [toucan.db :as db]))
 
-;;; +----------------------------------------------------------------------------------------------------------------+
-;;; |                                                    UTIL FNS                                                    |
-;;; +----------------------------------------------------------------------------------------------------------------+
-
-(defn- is-clause? [clause-names object]
-  (and (sequential? object)
-       ((some-fn string? keyword?) (first object))
-       (contains? clause-names (qputil/normalize-token (first object)))))
-
-(defn- non-empty-clause? [clause]
-  (and clause
-       (or (not (sequential? clause))
-           (and (seq clause)
-                (not (every? nil? clause))))))
-
-
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                                    SEGMENTS                                                    |
 ;;; +----------------------------------------------------------------------------------------------------------------+
 
-(defn- segment-parse-filter-subclause [form]
-  (when (non-empty-clause? form)
-    (if-not (is-clause? #{:segment} form)
-      form
-      (:filter (db/select-one-field :definition Segment :id (u/get-id (second form)))))))
-
-(defn- segment-parse-filter [form]
-  (when (non-empty-clause? form)
-    (if (is-clause? #{:and :or :not} form)
-      ;; for forms that start with AND/OR/NOT recursively parse the subclauses and put them nicely back into their
-      ;; compound form
-      (cons (first form) (mapv segment-parse-filter (rest form)))
-      ;; otherwise we should have a filter subclause so parse it as such
-      (segment-parse-filter-subclause form))))
-
-(defn- expand-segments [query-dict]
-  (cond
-    (non-empty-clause? (get-in query-dict [:query :filter]))
-    (update-in query-dict [:query :filter] segment-parse-filter)
+(defn- segment-clauses->id->definition [segment-clauses]
+  (db/select-id->field :definition Segment, :id [:in (set (map second segment-clauses))]))
 
-    (non-empty-clause? (get-in query-dict [:query :source-query :filter]))
-    (update-in query-dict [:query :source-query :filter] segment-parse-filter)
+(defn- replace-segment-clauses [outer-query segment-id->definition]
+  (mbql.u/replace-clauses-in outer-query [:query] :segment
+    (fn [[_ segment-id]]
+      (or (:filter (segment-id->definition segment-id))
+          (throw (IllegalArgumentException. (str (tru "Segment {0} does not exist, or is invalid." segment-id))))))))
 
-    :else
-    query-dict))
+(defn- expand-segments [{inner-query :query, :as outer-query}]
+  (if-let [segments (mbql.u/clause-instances :segment inner-query)]
+    (replace-segment-clauses outer-query (segment-clauses->id->definition segments))
+    outer-query))
 
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                                    METRICS                                                     |
 ;;; +----------------------------------------------------------------------------------------------------------------+
 
-(defn- ga-metric?
+(defn ga-metric?
   "Is this metric clause not a Metabase Metric, but rather a GA one? E.g. something like [metric ga:users]. We want to
    ignore those because they're not the same thing at all as MB Metrics and don't correspond to objects in our
    application DB."
@@ -78,80 +51,49 @@
    (when ((some-fn string? keyword?) id)
      (re-find #"^ga(id)?:" (name id)))))
 
-(defn- metric? [aggregation]
-  (and (is-clause? #{:metric} aggregation)
-       (not (ga-metric? aggregation))))
-
-(defn- metric-id [metric]
-  (when (metric? metric)
-    (u/get-id (second metric))))
-
-(defn- maybe-unnest-ag-clause
-  "Unnest AG-CLAUSE if it's wrapped in a vector (i.e. if it is using the \"multiple-aggregation\" syntax).
-   (This is provided merely as a convenience to facilitate implementation of the Query Builder, so it can use the same
-   UI for normal aggregations and Metric creation. *METRICS DO NOT SUPPORT MULTIPLE AGGREGATIONS,* so if nested syntax
-   is used, any aggregation after the first will be ignored.)"
-  [ag-clause]
-  (if (and (coll? ag-clause)
-           (every? coll? ag-clause))
-    (first ag-clause)
-    ag-clause))
-
-(defn- expand-metric [metric-clause filter-clauses-atom]
-  (let [{filter-clause :filter, ag-clause :aggregation} (db/select-one-field :definition Metric
-                                                          :id (metric-id metric-clause))]
-    (when filter-clause
-      (swap! filter-clauses-atom conj filter-clause))
-    (maybe-unnest-ag-clause ag-clause)))
-
-(defn- expand-metrics-in-ag-clause [query-dict filter-clauses-atom]
-  (walk/postwalk
-   (fn [form]
-     (if-not (metric? form)
-       form
-       (expand-metric form filter-clauses-atom)))
-   query-dict))
-
-(defn merge-filter-clauses
-  "Merge filter clauses."
-  ([] [])
-  ([clause] clause)
-  ([base-clause additional-clauses]
-   (cond
-     (and (seq base-clause)
-          (seq additional-clauses)) [:and base-clause additional-clauses]
-     (seq base-clause)              base-clause
-     (seq additional-clauses)       additional-clauses
-     :else                          [])))
-
-(defn- add-metrics-filter-clauses
-  "Add any FILTER-CLAUSES to the QUERY-DICT. If query has existing filter clauses, the new ones are
-   combined with an `:and` filter clause."
-  [query-dict filter-clauses]
-  (if-not (seq filter-clauses)
-    query-dict
-    (update-in query-dict [:query :filter] merge-filter-clauses (if (> (count filter-clauses) 1)
-                                                                  (cons :and filter-clauses)
-                                                                  (first filter-clauses)))))
-
-(defn- expand-metrics* [query-dict]
-  (let [filter-clauses-atom (atom [])
-        query-dict          (expand-metrics-in-ag-clause query-dict filter-clauses-atom)]
-    (add-metrics-filter-clauses query-dict @filter-clauses-atom)))
-
-(defn- expand-metrics [{{aggregations :aggregation} :query, :as query-dict}]
-  (if-not (seq aggregations)
-    ;; :aggregation is empty, so no METRIC to expand
-    query-dict
-    ;; otherwise walk the query dict and expand METRIC clauses
-    (expand-metrics* query-dict)))
+(defn- metrics
+  "Return a sequence of any (non-GA) `:metric` MBQL clauses in `query`."
+  [{inner-query :query}] ; metrics won't be in a native query but they could be in source-query or aggregation clause
+  (seq (filter (complement ga-metric?) (mbql.u/clause-instances :metric inner-query))))
+
+(defn- metric-clauses->id->definition [metric-clauses]
+  (db/select-id->field :definition Metric, :id [:in (set (map second metric-clauses))]))
+
+(defn- add-metrics-filters [query metric-id->definition]
+  (let [filters (filter identity (map :filter (vals metric-id->definition)))]
+    (reduce mbql.u/add-filter-clause query filters)))
+
+(defn- replace-metrics-aggregations [query metric-id->definition]
+  (mbql.u/replace-clauses-in query [:query] :metric
+    (fn [[_ metric-id, :as metric]]
+      (if (ga-metric? metric)
+        metric
+        (or (first (:aggregation (metric-id->definition metric-id)))
+            (throw (IllegalArgumentException.
+                    (str (tru "Metric {0} does not exist, or is invalid." metric-id)))))))))
+
+(defn- add-metrics-clauses
+  "Add appropriate `filter` and `aggregation` clauses for a sequence of Metrics.
+
+    (add-metrics-clauses {:query {}} [[:metric 10]])
+    ;; -> {:query {:aggregation [[:count]], :filter [:= [:field-id 10] 20]}}"
+  [query metric-id->definition]
+  (-> query
+      (add-metrics-filters metric-id->definition)
+      (replace-metrics-aggregations metric-id->definition)))
+
+(defn- expand-metrics [query]
+  (if-let [metrics (metrics query)]
+    (add-metrics-clauses query (metric-clauses->id->definition metrics))
+    query))
 
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                                   MIDDLEWARE                                                   |
 ;;; +----------------------------------------------------------------------------------------------------------------+
 
-(defn- expand-metrics-and-segments "Expand the macros (SEGMENT, METRIC) in a QUERY."
+(defn- expand-metrics-and-segments
+  "Expand the macros (`segment`, `metric`) in a `query`."
   [query]
   (-> query
       expand-metrics
@@ -167,6 +109,7 @@
         (log/debug (u/format-color 'cyan "\n\nMACRO/SUBSTITUTED: %s\n%s" (u/emoji "😻") (u/pprint-to-str <>)))))))
 
 (defn expand-macros
-  "Middleware that looks for `METRIC` and `SEGMENT` macros in an unexpanded MBQL query and substitute the macros for
+  "Middleware that looks for `:metric` and `:segment` macros in an unexpanded MBQL query and substitute the macros for
   their contents."
-  [qp] (comp qp expand-macros*))
+  [qp]
+  (comp qp expand-macros*))
diff --git a/src/metabase/query_processor/middleware/fetch_source_query.clj b/src/metabase/query_processor/middleware/fetch_source_query.clj
index c8498e412692453ad9dd4a47bc915ac615be02df..1c17298782dd899e9f4364f84d92362904111cfd 100644
--- a/src/metabase/query_processor/middleware/fetch_source_query.clj
+++ b/src/metabase/query_processor/middleware/fetch_source_query.clj
@@ -2,11 +2,9 @@
   "Middleware responsible for 'hydrating' the source query for queries that use another query as their source."
   (:require [clojure.string :as str]
             [clojure.tools.logging :as log]
-            [metabase.query-processor
-             [interface :as i]
-             [util :as qputil]]
+            [metabase.query-processor.interface :as i]
             [metabase.util :as u]
-            [puppetlabs.i18n.core :refer [trs]]
+            [metabase.util.i18n :refer [trs]]
             [toucan.db :as db]))
 
 (defn- trim-query
@@ -24,16 +22,17 @@
 (defn- card-id->source-query
   "Return the source query info for Card with CARD-ID."
   [card-id]
-  (let [card       (db/select-one ['Card :dataset_query :database_id] :id card-id)
+  (let [card       (db/select-one ['Card :dataset_query :database_id :result_metadata] :id card-id)
         card-query (:dataset_query card)]
     (assoc (or (:query card-query)
                (when-let [native (:native card-query)]
                  {:native        (trim-query card-id (:query native))
-                  :template_tags (:template_tags native)})
+                  :template-tags (:template-tags native)})
                (throw (Exception. (str "Missing source query in Card " card-id))))
       ;; include database ID as well; we'll pass that up the chain so it eventually gets put in its spot in the
       ;; outer-query
-      :database (:database card-query))))
+      :database        (:database card-query)
+      :result_metadata (:result_metadata card))))
 
 (defn- source-table-str->source-query
   "Given a SOURCE-TABLE-STR like `card__100` return the appropriate source query."
@@ -41,25 +40,28 @@
   (let [[_ card-id-str] (re-find #"^card__(\d+)$" source-table-str)]
     (u/prog1 (card-id->source-query (Integer/parseInt card-id-str))
       (when-not i/*disable-qp-logging*
-        (log/info "\nFETCHED SOURCE QUERY FROM CARD" card-id-str ":\n" (u/pprint-to-str 'yellow <>))))))
+        (log/infof "\nFETCHED SOURCE QUERY FROM CARD %s:\n%s"
+                   card-id-str
+                   ;; No need to include result metadata here, it can be large and will clutter the logs
+                   (u/pprint-to-str 'yellow (dissoc <> :result_metadata)))))))
 
-(defn- expand-card-source-tables
+(defn- ^:deprecated expand-card-source-tables
   "If `source-table` is a Card reference (a string like `card__100`) then replace that with appropriate
   `:source-query` information. Does nothing if `source-table` is a normal ID. Recurses for nested-nested queries."
-  [inner-query]
-  (let [source-table (qputil/get-normalized inner-query :source-table)]
-    (if-not (string? source-table)
-      inner-query
-      ;; (recursively) expand the source query
-      (let [source-query (expand-card-source-tables (source-table-str->source-query source-table))]
-        (-> inner-query
-            ;; remove `source-table` `card__id` key
-            (qputil/dissoc-normalized :source-table)
-            ;; Add new `source-query` info in its place. Pass the database ID up the chain, removing it from the
-            ;; source query
-            (assoc
-              :source-query (dissoc source-query :database)
-              :database     (:database source-query)))))))
+  [{:keys [source-table], :as inner-query}]
+  (if-not (string? source-table)
+    inner-query
+    ;; (recursively) expand the source query
+    (let [source-query (expand-card-source-tables (source-table-str->source-query source-table))]
+      (-> inner-query
+          ;; remove `source-table` `card__id` key
+          (dissoc :source-table)
+          ;; Add new `source-query` info in its place. Pass the database ID up the chain, removing it from the
+          ;; source query
+          (assoc
+              :source-query    (dissoc source-query :database :result_metadata)
+              :database        (:database source-query)
+              :result_metadata (:result_metadata source-query))))))
 
 (defn- fetch-source-query* [{inner-query :query, :as outer-query}]
   (if-not inner-query
@@ -68,9 +70,11 @@
     ;; otherwise attempt to expand any source queries as needed
     (let [expanded-inner-query (expand-card-source-tables inner-query)]
       (merge outer-query
-             {:query (dissoc expanded-inner-query :database)}
+             {:query (dissoc expanded-inner-query :database :result_metadata)}
              (when-let [database (:database expanded-inner-query)]
-               {:database database})))))
+               {:database database})
+             (when-let [result-metadata (:result_metadata expanded-inner-query)]
+               {:result_metadata result-metadata})))))
 
 (defn fetch-source-query
   "Middleware that assocs the `:source-query` for this query if it was specified using the shorthand `:source-table`
diff --git a/src/metabase/query_processor/middleware/normalize_query.clj b/src/metabase/query_processor/middleware/normalize_query.clj
new file mode 100644
index 0000000000000000000000000000000000000000..fbd576b52d2a8fb8bf4066caa116e73a536dad1e
--- /dev/null
+++ b/src/metabase/query_processor/middleware/normalize_query.clj
@@ -0,0 +1,10 @@
+(ns metabase.query-processor.middleware.normalize-query
+  "Middleware that converts a query into a normalized, canonical form."
+  (:require [metabase.mbql.normalize :as normalize]))
+
+(defn normalize
+  "Middleware that converts a query into a normalized, canonical form, including things like converting all identifiers
+  into standard `lisp-case` ones, removing/rewriting legacy clauses, removing empty ones, etc. This is done to
+  simplifiy the logic in the QP steps following this."
+  [qp]
+  (comp qp normalize/normalize))
diff --git a/src/metabase/query_processor/middleware/parameters.clj b/src/metabase/query_processor/middleware/parameters.clj
index f6d013ca8835d96a8cb69fb6316db395ee26e405..10f17a6fed99dd74dc4455a82806a1927beddfc8 100644
--- a/src/metabase/query_processor/middleware/parameters.clj
+++ b/src/metabase/query_processor/middleware/parameters.clj
@@ -12,8 +12,8 @@
             [metabase.util :as u]))
 
 (defn- expand-parameters*
-  "Expand any :parameters set on the QUERY-DICT and apply them to the query definition.
-   This function removes the :parameters attribute from the QUERY-DICT as part of its execution."
+  "Expand any `:parameters` set on the `query-dict` and apply them to the query definition. This function removes
+  the `:parameters` attribute from the `query-dict` as part of its execution."
   [{:keys [parameters], :as query-dict}]
   ;; params in native queries are currently only supported for SQL drivers
   (if (qputil/mbql-query? query-dict)
@@ -22,13 +22,13 @@
 
 (defn- expand-params-in-native-source-query
   "Expand parameters in a native source query."
-  [{{{original-query :native, tags :template_tags} :source-query} :query, :as outer-query}]
+  [{{{original-query :native, tags :template-tags} :source-query} :query, :as outer-query}]
   ;; TODO - This isn't recursive for nested-nested queries
   ;; TODO - Yes, this approach is hacky. But hacky & working > not working
   (let [{{new-query :query, new-params :params} :native} (sql-params/expand (assoc outer-query
                                                                               :type   :native
                                                                               :native {:query         original-query
-                                                                                       :template_tags tags}))]
+                                                                                       :template-tags tags}))]
     (if (= original-query new-query)
       ;; if the native query didn't change, we don't need to do anything; return as-is
       outer-query
diff --git a/src/metabase/query_processor/middleware/parameters/dates.clj b/src/metabase/query_processor/middleware/parameters/dates.clj
index 07314fb2a6eec89bb9ea35edb9175b6f9622f58c..9dd1799ee86baf9c43f96514678aa09ad5375cf2 100644
--- a/src/metabase/query_processor/middleware/parameters/dates.clj
+++ b/src/metabase/query_processor/middleware/parameters/dates.clj
@@ -3,9 +3,19 @@
   (:require [clj-time
              [core :as t]
              [format :as tf]]
-            [medley.core :as m])
+            [medley.core :as m]
+            [metabase.mbql.schema :as mbql.s]
+            [schema.core :as s]
+            [metabase.util.schema :as su]
+            [metabase.models.params :as params])
   (:import [org.joda.time DateTime DateTimeConstants]))
 
+(defn date-type?
+  "Is param type `:date` or some subtype like `:date/month-year`?"
+  [param-type]
+  (or (= param-type :date)
+      (= "date" (namespace param-type))))
+
 ;; Both in MBQL and SQL parameter substitution a field value is compared to a date range, either relative or absolute.
 ;; Currently the field value is casted to a day (ignoring the time of day), so the ranges should have the same
 ;; granularity level.
@@ -101,13 +111,13 @@
     :range  (fn [_ dt]
               {:start dt,
                :end   dt})
-    :filter (fn [_ field] ["=" field ["relative_datetime" "current"]])}
+    :filter (fn [_ field] [:= field [:relative-datetime :current]])}
 
    {:parser #(= % "yesterday")
     :range  (fn [_ dt]
               {:start (t/minus dt (t/days 1))
                :end   (t/minus dt (t/days 1))})
-    :filter (fn [_ field] ["=" field ["relative_datetime" -1 "day"]])}
+    :filter (fn [_ field] [:= field [:relative-datetime -1 :day]])}
 
    ;; adding a tilde (~) at the end of a past<n><unit> filter means we should include the current day/etc.
    ;; e.g. past30days  = past 30 days, not including partial data for today ({:include-current false})
@@ -117,74 +127,74 @@
               (unit-range (t/minus dt (to-period int-value))
                           (t/minus dt (to-period (if (seq include-current?) 0 1)))))
     :filter (fn [{:keys [unit int-value include-current?]} field]
-              ["TIME_INTERVAL" field (- int-value) unit {:include-current (boolean (seq include-current?))}])}
+              [:time-interval field (- int-value) (keyword unit) {:include-current (boolean (seq include-current?))}])}
 
    {:parser (regex->parser #"next([0-9]+)(day|week|month|year)s(~?)" [:int-value :unit :include-current?])
     :range  (fn [{:keys [unit int-value unit-range to-period include-current?]} dt]
               (unit-range (t/plus dt (to-period (if (seq include-current?) 0 1)))
                           (t/plus dt (to-period int-value))))
     :filter (fn [{:keys [unit int-value]} field]
-              ["TIME_INTERVAL" field int-value unit])}
+              [:time-interval field int-value (keyword unit)])}
 
    {:parser (regex->parser #"last(day|week|month|year)" [:unit])
     :range  (fn [{:keys [unit-range to-period]} dt]
               (let [last-unit (t/minus dt (to-period 1))]
                 (unit-range last-unit last-unit)))
     :filter (fn [{:keys [unit]} field]
-              ["TIME_INTERVAL" field "last" unit])}
+              [:time-interval field :last (keyword unit)])}
 
    {:parser (regex->parser #"this(day|week|month|year)" [:unit])
     :range  (fn [{:keys [unit-range]} dt]
               (unit-range dt dt))
     :filter (fn [{:keys [unit]} field]
-              ["TIME_INTERVAL" field "current" unit])}])
+              [:time-interval field :current (keyword unit)])}])
 
 (defn- day->iso8601 [date]
   (tf/unparse (tf/formatters :year-month-day) date))
 
 (defn- range->filter
   [{:keys [start end]} field]
-  ["BETWEEN" field (day->iso8601 start) (day->iso8601 end)])
+  [:between field (day->iso8601 start) (day->iso8601 end)])
 
 (def ^:private absolute-date-string-decoders
   ;; year and month
   [{:parser (regex->parser #"([0-9]{4}-[0-9]{2})" [:date])
     :range  (fn [{:keys [date]} _]
               (month-range date date))
-    :filter (fn [{:keys [date]} field]
-              (range->filter (month-range date date) field))}
+    :filter (fn [{:keys [date]} field-id-clause]
+              (range->filter (month-range date date) field-id-clause))}
    ;; quarter year
    {:parser (regex->parser #"(Q[1-4]{1})-([0-9]{4})" [:quarter :year])
     :range  (fn [{:keys [quarter year]} _]
               (quarter-range quarter (Integer/parseInt year)))
-    :filter (fn [{:keys [quarter year]} field]
+    :filter (fn [{:keys [quarter year]} field-id-clause]
               (range->filter (quarter-range quarter (Integer/parseInt year))
-                             field))}
+                             field-id-clause))}
    ;; single day
    {:parser (regex->parser #"([0-9-T:]+)" [:date])
     :range  (fn [{:keys [date]} _]
               {:start date, :end date})
-    :filter (fn [{:keys [date]} field]
+    :filter (fn [{:keys [date]} field-id-clause]
               (let [iso8601date (day->iso8601 date)]
-                ["BETWEEN" field iso8601date iso8601date]))}
+                [:between field-id-clause iso8601date iso8601date]))}
    ;; day range
    {:parser (regex->parser #"([0-9-T:]+)~([0-9-T:]+)" [:date-1 :date-2])
     :range  (fn [{:keys [date-1 date-2]} _]
               {:start date-1, :end date-2})
-    :filter (fn [{:keys [date-1 date-2]} field]
-              ["BETWEEN" field (day->iso8601 date-1) (day->iso8601 date-2)])}
+    :filter (fn [{:keys [date-1 date-2]} field-id-clause]
+              [:between field-id-clause (day->iso8601 date-1) (day->iso8601 date-2)])}
    ;; before day
    {:parser (regex->parser #"~([0-9-T:]+)" [:date])
     :range  (fn [{:keys [date]} _]
               {:end date})
-    :filter (fn [{:keys [date]} field]
-              ["<" field (day->iso8601 date)])}
+    :filter (fn [{:keys [date]} field-id-clause]
+              [:< field-id-clause (day->iso8601 date)])}
    ;; after day
    {:parser (regex->parser #"([0-9-T:]+)~" [:date])
     :range  (fn [{:keys [date]} _]
               {:start date})
-    :filter (fn [{:keys [date]} field]
-              [">" field (day->iso8601 date)])}])
+    :filter (fn [{:keys [date]} field-id-clause]
+              [:> field-id-clause (day->iso8601 date)])}])
 
 (def ^:private all-date-string-decoders
   (concat relative-date-string-decoders absolute-date-string-decoders))
@@ -215,8 +225,8 @@
         (->> (execute-decoders absolute-date-string-decoders :range nil date-string)
              (m/map-vals (partial tf/unparse formatter-no-tz))))))
 
-(defn date-string->filter
+(s/defn date-string->filter :- mbql.s/Filter
   "Takes a string description of a date range such as 'lastmonth' or '2016-07-15~2016-08-6' and returns a
    corresponding MBQL filter clause for a given field reference."
-  [date-string field-reference]
-  (execute-decoders all-date-string-decoders :filter field-reference date-string))
+  [date-string :- s/Str, field :- (s/cond-pre su/IntGreaterThanZero mbql.s/Field)]
+  (execute-decoders all-date-string-decoders :filter (params/wrap-field-id-if-needed field) date-string))
diff --git a/src/metabase/query_processor/middleware/parameters/mbql.clj b/src/metabase/query_processor/middleware/parameters/mbql.clj
index e3a2b1e20d1a54a1bcbda1b1d30858c18fe691e3..b4efb93df4a693fe443df8d71e45fdbbfcc0a7ce 100644
--- a/src/metabase/query_processor/middleware/parameters/mbql.clj
+++ b/src/metabase/query_processor/middleware/parameters/mbql.clj
@@ -1,26 +1,29 @@
 (ns metabase.query-processor.middleware.parameters.mbql
   "Code for handling parameter substitution in MBQL queries."
-  (:require [clojure.string :as str]
+  (:require [metabase.mbql
+             [schema :as mbql.s]
+             [util :as mbql.u]]
             [metabase.models
              [field :refer [Field]]
              [params :as params]]
             [metabase.query-processor.middleware.parameters.dates :as date-params]
+            [schema.core :as s]
             [toucan.db :as db]))
 
 (defn- parse-param-value-for-type
-  "Convert PARAM-VALUE to a type appropriate for PARAM-TYPE.
+  "Convert `param-value` to a type appropriate for `param-type`.
   The frontend always passes parameters in as strings, which is what we want in most cases; for numbers, instead
   convert the parameters to integers or floating-point numbers."
   [param-type param-value field-id]
   (cond
     ;; for `id` type params look up the base-type of the Field and see if it's a number or not. If it *is* a number
     ;; then recursively call this function and parse the param value as a number as appropriate.
-    (and (= (keyword param-type) :id)
+    (and (= param-type :id)
          (isa? (db/select-one-field :base_type Field :id field-id) :type/Number))
     (recur :number param-value field-id)
 
     ;; no conversion needed if PARAM-TYPE isn't :number or PARAM-VALUE isn't a string
-    (or (not= (keyword param-type) :number)
+    (or (not= param-type :number)
         (not (string? param-value)))
     param-value
 
@@ -32,45 +35,39 @@
     :else
     (Long/parseLong param-value)))
 
-(defn- build-filter-clause [{param-type :type, param-value :value, [_ field :as target] :target, :as param}]
+(s/defn ^:private build-filter-clause :- mbql.s/Filter
+  [{param-type :type, param-value :value, [_ field :as target] :target, :as param}]
   (cond
     ;; multipe values. Recursively handle them all and glue them all together with an OR clause
     (sequential? param-value)
-    (cons :or (for [value param-value]
-                (build-filter-clause {:type param-type, :value value, :target target})))
+    (mbql.u/simplify-compound-filter
+     (vec (cons :or (for [value param-value]
+                      (build-filter-clause {:type param-type, :value value, :target target})))))
 
     ;; single value, date range. Generate appropriate MBQL clause based on date string
-    (str/starts-with? param-type "date")
+    (date-params/date-type? param-type)
     (date-params/date-string->filter (parse-param-value-for-type param-type param-value (params/field-form->id field))
                                      field)
 
-    ;; single-value, non-date param. Generate MBQL [= <field> <value>] clause
+    ;; single-value, non-date param. Generate MBQL [= [field-id <field>] <value>] clause
     :else
-    [:= field (parse-param-value-for-type param-type param-value (params/field-form->id field))]))
-
-(defn- merge-filter-clauses [base addtl]
-  (cond
-    (and (seq base)
-         (seq addtl)) ["AND" base addtl]
-    (seq base)        base
-    (seq addtl)       addtl
-    :else             []))
+    [:=
+     (params/wrap-field-id-if-needed field)
+     (parse-param-value-for-type param-type param-value (params/field-form->id field))]))
 
 (defn expand
-  "Expand parameters for MBQL queries in QUERY-DICT (replacing Dashboard or Card-supplied params with the appropriate
+  "Expand parameters for MBQL queries in `query` (replacing Dashboard or Card-supplied params with the appropriate
   values in the queries themselves)."
-  [query-dict [{:keys [target value], :as param} & rest]]
+  [query [{:keys [target value], :as param} & rest]]
   (cond
     (not param)
-    query-dict
+    query
 
     (or (not target)
         (not value))
-    (recur query-dict rest)
+    (recur query rest)
 
     :else
-    (let [filter-subclause (build-filter-clause param)
-          query            (assoc-in query-dict [:query :filter] (merge-filter-clauses
-                                                                  (get-in query-dict [:query :filter])
-                                                                  filter-subclause))]
+    (let [filter-clause (build-filter-clause param)
+          query         (mbql.u/add-filter-clause query filter-clause)]
       (recur query rest))))
diff --git a/src/metabase/query_processor/middleware/parameters/sql.clj b/src/metabase/query_processor/middleware/parameters/sql.clj
index bc61f4c701bfe4a8c2eeff1401b2ee0ee8350566..38280370a44ee828c4fe1f7619e39c38d1b82af0 100644
--- a/src/metabase/query_processor/middleware/parameters/sql.clj
+++ b/src/metabase/query_processor/middleware/parameters/sql.clj
@@ -7,21 +7,22 @@
             [honeysql.core :as hsql]
             [medley.core :as m]
             [metabase.driver :as driver]
-            [metabase.models.field :as field :refer [Field]]
             [metabase.driver.generic-sql :as sql]
+            [metabase.models.field :as field :refer [Field]]
+            [metabase.query-processor.interface :as qp.i]
             [metabase.query-processor.middleware.expand :as ql]
             [metabase.query-processor.middleware.parameters.dates :as date-params]
             [metabase.util
              [date :as du]
+             [i18n :as ui18n :refer [tru]]
              [schema :as su]]
-            [puppetlabs.i18n.core :refer [tru]]
             [schema.core :as s]
             [toucan.db :as db])
   (:import clojure.lang.Keyword
            honeysql.types.SqlCall
            java.text.NumberFormat
            java.util.regex.Pattern
-           java.util.TimeZone
+           java.util.UUID
            metabase.models.field.FieldInstance))
 
 ;; The Basics:
@@ -41,13 +42,7 @@
 ;;; |                                                      ETC                                                       |
 ;;; +----------------------------------------------------------------------------------------------------------------+
 
-;; Dynamic variables and record types used by the other parts of this namespace.
-
-;; TODO - we have dynamic *driver* variables like this in several places; it probably makes more sense to see if we
-;; can share one used somewhere else instead
-(def ^:private ^:dynamic *driver* nil)
-
-;; various record types below are used as a convenience for differentiating the different param types.
+;; Various record types below are used as a convenience for differentiating the different param types.
 
 ;; "Dimension" here means a "FIELD FILTER", e.g. something that expands to a clause like "some_field BETWEEN 1 AND 10"
 (s/defrecord ^:private Dimension [field :- FieldInstance, param]) ; param is either single param or a vector of params
@@ -68,26 +63,29 @@
 (defn- no-value? [x]
   (instance? NoValue x))
 
+(def ^:private ParamType
+  (s/enum :number :dimension :text :date))
+
 ;; various schemas are used to check that various functions return things in expected formats
 
 ;; TAGS in this case are simple params like {{x}} that get replaced with a single value ("ABC" or 1) as opposed to a
 ;; "FieldFilter" clause like Dimensions
 ;;
 ;; Since 'Dimension' (Field Filters) are considered their own `:type`, to *actually* store the type of a Dimension
-;; look at the key `:widget_type`. This applies to things like the default value for a Dimension as well.
+;; look at the key `:widget-type`. This applies to things like the default value for a Dimension as well.
 (def ^:private TagParam
-  "Schema for values passed in as part of the `:template_tags` list."
+  "Schema for values passed in as part of the `:template-tags` list."
   {(s/optional-key :id)          su/NonBlankString ; this is used internally by the frontend
    :name                         su/NonBlankString
-   :display_name                 su/NonBlankString
-   :type                         (s/enum "number" "dimension" "text" "date")
+   :display-name                 su/NonBlankString
+   :type                         ParamType
    (s/optional-key :dimension)   [s/Any]
-   (s/optional-key :widget_type) su/NonBlankString ; type of the [default] value if `:type` itself is `dimension`
+   (s/optional-key :widget-type) s/Keyword ; type of the [default] value if `:type` itself is `dimension`
    (s/optional-key :required)    s/Bool
    (s/optional-key :default)     s/Any})
 
 (def ^:private DimensionValue
-  {:type                     su/NonBlankString
+  {:type                     s/Keyword ; TODO - what types are allowed? :text, ...?
    :target                   s/Any
    ;; not specified if the param has no value. TODO - make this stricter
    (s/optional-key :value)   s/Any
@@ -117,7 +115,7 @@
            "Valid param value(s)"))
 
 (def ^:private ParamValues
-  {s/Keyword ParamValue})
+  {su/NonBlankString ParamValue})
 
 (def ^:private ParamSnippetInfo
   {(s/optional-key :replacement-snippet)     s/Str     ; allowed to be blank if this is an optional param
@@ -128,14 +126,14 @@
 ;;; +----------------------------------------------------------------------------------------------------------------+
 
 ;; These functions build a map of information about the types and values of the params used in a query.
-;; (These functions don't parse the query itself, but instead look at the values of `:template_tags` and `:parameters`
+;; (These functions don't parse the query itself, but instead look at the values of `:template-tags` and `:parameters`
 ;; passed along with the query.)
 ;;
 ;;     (query->params-map some-query)
-;;     ;; -> {:checkin_date {:field {:name "\date"\, :parent_id nil, :table_id 1375}
-;;                           :param {:type   "\date/range"\
-;;                                   :target ["\dimension"\ ["\template-tag"\ "\checkin_date"\]]
-;;                                   :value  "\2015-01-01~2016-09-01"\}}}
+;;     ;; -> {"checkin_date" {:field {:name "\date"\, :parent_id nil, :table_id 1375}
+;;                            :param {:type   "\date/range"\
+;;                                    :target ["\dimension"\ ["\template-tag"\ "\checkin_date"\]]
+;;                                    :value  "\2015-01-01~2016-09-01"\}}}
 
 (s/defn ^:private param-with-target
   "Return the param in PARAMS with a matching TARGET. TARGET is something like:
@@ -158,8 +156,8 @@
   "Return the default value for a Dimension (Field Filter) param defined by the map TAG, if one is set."
   [tag :- TagParam]
   (when-let [default (:default tag)]
-    {:type   (:widget_type tag "dimension")             ; widget_type is the actual type of the default value if set
-     :target ["dimension" ["template-tag" (:name tag)]]
+    {:type   (:widget-type tag :dimension)             ; widget-type is the actual type of the default value if set
+     :target [:dimension [:template-tag (:name tag)]]
      :value  default}))
 
 (s/defn ^:private dimension->field-id :- su/IntGreaterThanZero
@@ -171,11 +169,13 @@
   Filter\" in the Native Query Editor."
   [tag :- TagParam, params :- (s/maybe [DimensionValue])]
   (when-let [dimension (:dimension tag)]
-    (map->Dimension {:field (or (db/select-one [Field :name :parent_id :table_id], :id (dimension->field-id dimension))
-                                (throw (Exception. (str "Can't find field with ID: " (dimension->field-id dimension)))))
+    (map->Dimension {:field (or (db/select-one [Field :name :parent_id :table_id :base_type],
+                                  :id (dimension->field-id dimension))
+                                (throw (Exception. (str (tru "Can't find field with ID: {0}"
+                                                             (dimension->field-id dimension))))))
                      :param (or
                              ;; look in the sequence of params we were passed to see if there's anything that matches
-                             (param-with-target params ["dimension" ["template-tag" (:name tag)]])
+                             (param-with-target params [:dimension [:template-tag (:name tag)]])
                              ;; if not, check and see if we have a default param
                              (default-value-for-dimension tag))})))
 
@@ -183,8 +183,8 @@
 ;;; Non-Dimension Params (e.g. WHERE x = {{x}})
 
 (s/defn ^:private param-value-for-tag [tag :- TagParam, params :- (s/maybe [DimensionValue])]
-  (when (not= (:type tag) "dimension")
-    (:value (param-with-target params ["variable" ["template-tag" (:name tag)]]))))
+  (when (not= (:type tag) :dimension)
+    (:value (param-with-target params [:variable [:template-tag (:name tag)]]))))
 
 (s/defn ^:private default-value-for-tag
   "Return the `:default` value for a param if no explicit values were passsed. This only applies to non-Dimension
@@ -193,7 +193,7 @@
   [{:keys [default display_name required]} :- TagParam]
   (or default
       (when required
-        (throw (Exception. (format "'%s' is a required param." display_name))))))
+        (throw (Exception. (str (tru "''{0}'' is a required param." display_name)))))))
 
 
 ;;; Parsing Values
@@ -228,21 +228,50 @@
         ;; otherwise just return the single number
         (first parts)))))
 
+(s/defn ^:private parse-value-for-field-base-type :- s/Any
+  "Do special parsing for value for a (presumably textual) FieldFilter 'dimension' param (i.e., attempt to parse it as
+  appropriate based on the base-type of the Field associated with it). These are special cases for handling types that
+  do not have an associated parameter type (such as `date` or `number`), such as UUID fields."
+  [base-type :- su/FieldType, value]
+  (cond
+    (isa? base-type :type/UUID) (UUID/fromString value)
+    :else                       value))
+
 (s/defn ^:private parse-value-for-type :- ParamValue
-  [param-type value]
+  "Parse a `value` based on the type chosen for the param, such as `text` or `number`. (Depending on the type of param
+  created, `value` here might be a raw value or a map including information about the Field it references as well as a
+  value.) For numbers, dates, and the like, this will parse the string appropriately; for `text` parameters, this will
+  additionally attempt handle special cases based on the base type of the Field, for example, parsing params for UUID
+  base type Fields as UUIDs."
+  [param-type :- ParamType, value]
   (cond
-    (no-value? value)                                value
-    (= param-type "number")                          (value->number value)
-    (= param-type "date")                            (map->Date {:s value})
-    (and (= param-type "dimension")
-         (= (get-in value [:param :type]) "number")) (update-in value [:param :value] value->number)
-    (sequential? value)                              (map->MultipleValues
-                                                      {:values (for [v value]
-                                                                 (parse-value-for-type param-type v))})
-    :else                                            value))
+    (no-value? value)
+    value
+
+    (= param-type :number)
+    (value->number value)
+
+    (= param-type :date)
+    (map->Date {:s value})
+
+    (and (= param-type :dimension)
+         (= (get-in value [:param :type]) :number))
+    (update-in value [:param :value] value->number)
+
+    (sequential? value)
+    (map->MultipleValues {:values (for [v value]
+                                    (parse-value-for-type param-type v))})
+
+    (and (= param-type :dimension)
+         (get-in value [:field :base_type])
+         (string? (get-in value [:param :value])))
+    (update-in value [:param :value] (partial parse-value-for-field-base-type (get-in value [:field :base_type])))
+
+    :else
+    value))
 
 (s/defn ^:private value-for-tag :- ParamValue
-  "Given a map TAG (a value in the `:template_tags` dictionary) return the corresponding value from the PARAMS
+  "Given a map TAG (a value in the `:template-tags` dictionary) return the corresponding value from the PARAMS
    sequence. The VALUE is something that can be compiled to SQL via `->replacement-snippet-info`."
   [tag :- TagParam, params :- (s/maybe [DimensionValue])]
   (parse-value-for-type (:type tag) (or (param-value-for-tag tag params)
@@ -257,10 +286,10 @@
      (query->params-map some-query)
       ->
       {:checkin_date {:field {:name \"date\", :parent_id nil, :table_id 1375}
-                      :param {:type   \"date/range\"
-                              :target [\"dimension\" [\"template-tag\" \"checkin_date\"]]
+                      :param {:type   :date/range
+                              :target [:dimension [:template-tag \"checkin_date\"]]
                               :value  \"2015-01-01~2016-09-01\"}}}"
-  [{{tags :template_tags} :native, params :parameters}]
+  [{{tags :template-tags} :native, params :parameters}]
   (into {} (for [[k tag] tags
                  :let    [v (value-for-tag tag params)]
                  :when   v]
@@ -289,10 +318,7 @@
 
 
 (defn- relative-date-param-type? [param-type]
-  (contains? #{"date/range" "date/month-year" "date/quarter-year" "date/relative" "date/all-options"} param-type))
-
-(defn- date-param-type? [param-type]
-  (str/starts-with? param-type "date/"))
+  (contains? #{:date/range :date/month-year :date/quarter-year :date/relative :date/all-options} param-type))
 
 ;; for relative dates convert the param to a `DateRange` record type and call `->replacement-snippet-info` on it
 (s/defn ^:private relative-date-dimension-value->replacement-snippet-info :- ParamSnippetInfo
@@ -314,13 +340,13 @@
       (update :replacement-snippet (partial format "IN (%s)"))))
 
 (s/defn ^:private dimension->replacement-snippet-info :- ParamSnippetInfo
-  "Return `[replacement-snippet & prepared-statement-args]` appropriate for a DIMENSION parameter."
+  "Return `[replacement-snippet & prepared-statement-args]` appropriate for a `dimension` parameter."
   [{param-type :type, value :value} :- DimensionValue]
   (cond
     ;; convert relative dates to approprate date range representations
     (relative-date-param-type? param-type) (relative-date-dimension-value->replacement-snippet-info value)
     ;; convert all other dates to `= <date>`
-    (date-param-type? param-type)          (dimension-value->equals-clause-sql (map->Date {:s value}))
+    (date-params/date-type? param-type)    (dimension-value->equals-clause-sql (map->Date {:s value}))
     ;; for sequences of multiple values we want to generate an `IN (...)` clause
     (sequential? value)                    (dimension-multiple-values->in-clause-sql value)
     ;; convert everything else to `= <value>`
@@ -329,7 +355,7 @@
 (s/defn ^:private honeysql->replacement-snippet-info :- ParamSnippetInfo
   "Convert X to a replacement snippet info map by passing it to HoneySQL's `format` function."
   [x]
-  (let [[snippet & args] (hsql/format x, :quoting (sql/quote-style *driver*))]
+  (let [[snippet & args] (hsql/format x, :quoting (sql/quote-style qp.i/*driver*))]
     {:replacement-snippet     snippet
      :prepared-statement-args args}))
 
@@ -338,9 +364,9 @@
    For non-date Fields, this is just a quoted identifier; for dates, the SQL includes appropriately bucketing based on
    the PARAM-TYPE."
   [field param-type]
-  (-> (honeysql->replacement-snippet-info (let [identifier (sql/field->identifier *driver* field)]
-                                            (if (date-param-type? param-type)
-                                              (sql/date *driver* :day identifier)
+  (-> (honeysql->replacement-snippet-info (let [identifier (sql/field->identifier qp.i/*driver* field)]
+                                            (if (date-params/date-type? param-type)
+                                              (sql/date qp.i/*driver* :day identifier)
                                               identifier)))
       :replacement-snippet))
 
@@ -351,13 +377,13 @@
    :prepared-statement-args (reduce concat (map :prepared-statement-args replacement-snippet-maps))})
 
 (defn- create-replacement-snippet [nil-or-obj]
-  (let [{:keys [sql-string param-values]} (sql/->prepared-substitution *driver* nil-or-obj)]
-    {:replacement-snippet sql-string
+  (let [{:keys [sql-string param-values]} (sql/->prepared-substitution qp.i/*driver* nil-or-obj)]
+    {:replacement-snippet     sql-string
      :prepared-statement-args param-values}))
 
 (defn- prepared-ts-subs [operator date-str]
-  (let [{:keys [sql-string param-values]} (sql/->prepared-substitution *driver* (du/->Timestamp date-str))]
-    {:replacement-snippet (str operator " " sql-string)
+  (let [{:keys [sql-string param-values]} (sql/->prepared-substitution qp.i/*driver* (du/->Timestamp date-str))]
+    {:replacement-snippet     (str operator " " sql-string)
      :prepared-statement-args param-values}))
 
 (extend-protocol ISQLParamSubstituion
@@ -367,6 +393,7 @@
   Boolean (->replacement-snippet-info [this] (create-replacement-snippet this))
   Keyword (->replacement-snippet-info [this] (create-replacement-snippet this))
   SqlCall (->replacement-snippet-info [this] (create-replacement-snippet this))
+  UUID    (->replacement-snippet-info [this] {:replacement-snippet (format "CAST('%s' AS uuid)" (str this))})
   NoValue (->replacement-snippet-info [_]    {:replacement-snippet ""})
 
   CommaSeparatedNumbers
@@ -396,7 +423,7 @@
       (prepared-ts-subs \> start)
 
       :else
-      (let [params (map (comp #(sql/->prepared-substitution *driver* %) du/->Timestamp) [start end])]
+      (let [params (map (comp #(sql/->prepared-substitution qp.i/*driver* %) du/->Timestamp) [start end])]
         {:replacement-snippet     (apply format "BETWEEN %s AND %s" (map :sql-string params)),
          :prepared-statement-args (vec (mapcat :param-values params))})))
 
@@ -415,7 +442,9 @@
       ;; otherwise convert single param to SQL.
       ;; Convert the value to a replacement snippet info map and then tack on the field identifier to the front
       :else
-      (update (dimension->replacement-snippet-info param) :replacement-snippet (partial str (field->identifier field (:type param)) " ")))))
+      (update (dimension->replacement-snippet-info param)
+              :replacement-snippet (partial str (field->identifier field (:type param)) " ")))))
+
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                            PARSING THE SQL TEMPLATE                                            |
@@ -451,19 +480,19 @@
   (-> s Pattern/quote re-pattern))
 
 (defn- split-delimited-string
-  "Interesting parts of the SQL string (vs. parts that are just passed through) are delimited,
-  i.e. {{something}}. This function takes a `delimited-begin` and `delimited-end` regex and uses that to separate the
-  string. Returns a map with the prefix (the string leading up to the first `delimited-begin`) and `:delimited-strings` as
-  a seq of maps where `:delimited-body` is what's in-between the delimited marks (i.e. foo in {{foo}} and then a
-  suffix, which is the characters after the trailing delimiter but before the next occurrence of the `delimited-end`."
+  "Interesting parts of the SQL string (vs. parts that are just passed through) are delimited, i.e. {{something}}. This
+  function takes a `delimited-begin` and `delimited-end` regex and uses that to separate the string. Returns a map
+  with the prefix (the string leading up to the first `delimited-begin`) and `:delimited-strings` as a seq of maps
+  where `:delimited-body` is what's in-between the delimited marks (i.e. foo in {{foo}} and then a suffix, which is
+  the characters after the trailing delimiter but before the next occurrence of the `delimited-end`."
   [delimited-begin delimited-end s]
   (let [begin-pattern                (quoted-re-pattern delimited-begin)
         end-pattern                  (quoted-re-pattern delimited-end)
         [prefix & segmented-strings] (str/split s begin-pattern)]
     (when-let [^String msg (and (seq segmented-strings)
                                 (not-every? #(str/index-of % delimited-end) segmented-strings)
-                                (tru "Found ''{0}'' with no terminating ''{1}'' in query ''{2}''"
-                                     delimited-begin delimited-end s))]
+                                (str (tru "Found ''{0}'' with no terminating ''{1}'' in query ''{2}''"
+                                          delimited-begin delimited-end s)))]
       (throw (IllegalArgumentException. msg)))
     {:prefix            prefix
      :delimited-strings (for [segmented-string segmented-strings
@@ -471,11 +500,11 @@
                           {:delimited-body token-str
                            :suffix         (apply str rest-of-segment)})}))
 
-(defn- token->param
+(s/defn ^:private token->param :- Param
   "Given a `token` and `param-key->value` return a `Param`. If no parameter value is found, return a `NoValue` param"
-  [token param-key->value]
-  (let [val                               (get param-key->value (keyword token) (NoValue.))
-        {:keys [replacement-snippet,
+  [token :- su/NonBlankString, param-key->value :- ParamValues]
+  (let [val                               (get param-key->value token (NoValue.))
+        {:keys [replacement-snippet
                 prepared-statement-args]} (->replacement-snippet-info val)]
     (map->Param (merge {:param-key token}
                        (if (no-value? val)
@@ -483,9 +512,9 @@
                          {:sql-value               replacement-snippet
                           :prepared-statement-args prepared-statement-args})))))
 
-(defn- parse-params
+(s/defn ^:private parse-params
   "Parse `s` for any parameters. Returns a seq of strings and `Param` instances"
-  [s param-key->value]
+  [s :- s/Str, param-key->value :- ParamValues]
   (let [{:keys [prefix delimited-strings]} (split-delimited-string "{{" "}}" s)]
     (cons prefix
           (mapcat (fn [{:keys [delimited-body suffix]}]
@@ -495,33 +524,39 @@
                      suffix])
                   delimited-strings))))
 
-(defn- parse-params-and-throw
+(s/defn ^:private parse-params-or-throw
   "Same as `parse-params` but will throw an exception if there are any `NoValue` parameters"
-  [s param-key->value]
+  [s :- s/Str, param-key->value :- ParamValues]
   (let [results (parse-params s param-key->value)]
     (if-let [{:keys [param-key]} (m/find-first no-value-param? results)]
-      (throw (ex-info (tru "Unable to substitute ''{0}'': param not specified.\nFound: {1}"
-                           (name param-key) (pr-str (map name (keys param-key->value))))
+      (throw (ui18n/ex-info (tru "Unable to substitute ''{0}'': param not specified.\nFound: {1}"
+                                 (name param-key) (pr-str (map name (keys param-key->value))))
                {:status-code 400}))
       results)))
 
-(defn- parse-optional
-  "Attempts to parse `s`. Parses any optional clauses or parameters found, returns a query map."
-  [s param-key->value]
+(def ^:private ParseTemplateResponse
+  {:query  s/Str
+   :params [s/Any]})
+
+(s/defn ^:private parse-optional :- ParseTemplateResponse
+  "Attempts to parse SQL parameter string `s`. Parses any optional clauses or parameters found, returns a query map."
+  [s :- s/Str, param-key->value :- ParamValues]
   (let [{:keys [prefix delimited-strings]} (split-delimited-string "[[" "]]" s)]
     (reduce merge-query-map empty-query-map
-            (apply concat (parse-params-and-throw prefix param-key->value)
+            (apply concat (parse-params-or-throw prefix param-key->value)
                    (for [{:keys [delimited-body suffix]} delimited-strings
                          :let [optional-clause (parse-params delimited-body param-key->value)]]
                      (if (some no-value-param? optional-clause)
-                       (parse-params-and-throw suffix param-key->value)
-                       (concat optional-clause (parse-params-and-throw suffix param-key->value))))))))
+                       (parse-params-or-throw suffix param-key->value)
+                       (concat optional-clause (parse-params-or-throw suffix param-key->value))))))))
 
-(defn- parse-template [sql param-key->value]
+(s/defn ^:private parse-template :- ParseTemplateResponse
+  [sql :- s/Str, param-key->value :- ParamValues]
   (-> sql
       (parse-optional param-key->value)
       (update :query str/trim)))
 
+
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                            PUTTING IT ALL TOGETHER                                             |
 ;;; +----------------------------------------------------------------------------------------------------------------+
@@ -530,6 +565,7 @@
   [{sql :query, :as native}, param-key->value :- ParamValues]
   (merge native (parse-template sql param-key->value)))
 
+;; TODO - this can probably be taken out since qp.i/*driver* should always be bound...
 (defn- ensure-driver
   "Depending on where the query came from (the user, permissions check etc) there might not be an driver associated to
   the query. If there is no driver, use the database to find the right driver or throw."
@@ -541,7 +577,7 @@
 (defn expand
   "Expand parameters inside a *SQL* QUERY."
   [query]
-  (binding [*driver*   (ensure-driver query)]
-    (if (driver/driver-supports? *driver* :native-query-params)
+  (binding [qp.i/*driver* (ensure-driver query)]
+    (if (driver/driver-supports? qp.i/*driver* :native-query-params)
       (update query :native expand-query-params (query->params-map query))
       query)))
diff --git a/src/metabase/query_processor/middleware/permissions.clj b/src/metabase/query_processor/middleware/permissions.clj
index 931bd91917b1d8d4820ecface7e9bdd50babc0cd..3fc51fb0f1ee42ff7cb7b02a3d7889e07c066c7b 100644
--- a/src/metabase/query_processor/middleware/permissions.clj
+++ b/src/metabase/query_processor/middleware/permissions.clj
@@ -6,8 +6,9 @@
              [interface :as mi]
              [permissions :as perms]]
             [metabase.models.query.permissions :as query-perms]
-            [metabase.util.schema :as su]
-            [puppetlabs.i18n.core :refer [tru]]
+            [metabase.util
+             [i18n :refer [tru]]
+             [schema :as su]]
             [schema.core :as s]
             [toucan.db :as db]))
 
diff --git a/src/metabase/query_processor/middleware/resolve.clj b/src/metabase/query_processor/middleware/resolve.clj
index 78cf8066fce793386cc16e3020a90d3fcf2703cf..d26ddb8a57195f9e25d60d092606ed5d238ba178 100644
--- a/src/metabase/query_processor/middleware/resolve.clj
+++ b/src/metabase/query_processor/middleware/resolve.clj
@@ -25,7 +25,7 @@
              [db :as db]
              [hydrate :refer [hydrate]]])
   (:import java.util.TimeZone
-           [metabase.query_processor.interface DateTimeField DateTimeValue ExpressionRef Field FieldPlaceholder
+           [metabase.query_processor.interface DateTimeField DateTimeValue ExpressionRef Field FieldLiteral FieldPlaceholder
             RelativeDatetime RelativeDateTimeValue TimeField TimeValue Value ValuePlaceholder]))
 
 ;;; ---------------------------------------------------- UTIL FNS ----------------------------------------------------
@@ -161,7 +161,7 @@
 
 ;;; ----------------------------------------------- FIELD PLACEHOLDER ------------------------------------------------
 
-(defn- resolve-binned-field [{:keys [binning-strategy binning-param] :as field-ph} field]
+(defn- resolve-binned-field [binning-strategy binning-param field]
   (let [binned-field (i/map->BinnedField {:field    field
                                           :strategy binning-strategy})]
     (case binning-strategy
@@ -200,7 +200,7 @@
       (i/map->TimeField {:field field})
 
       binning-strategy
-      (resolve-binned-field this field)
+      (resolve-binned-field binning-strategy binning-param field)
 
       :else field)
     ;; If that fails just return ourselves as-is
@@ -377,45 +377,114 @@
                               ;; the first 30 here
                               :join-alias  (apply str (take 30 (str target-table-name "__via__" source-field-name)))})))))
 
+(defn- create-fk-id+table-id->table
+  "Create the `fk-id+table-id->table` map used in resolving table names in `resolve-table` calls"
+  [{source-table-id :id :as source-table} joined-tables]
+  (into {[nil source-table-id] source-table}
+        (for [{:keys [source-field table-id join-alias]} joined-tables]
+          {[(:field-id source-field) table-id] {:name join-alias
+                                                :id   table-id}})))
+
+(defn- append-new-fields
+  "Returns a vector fields that have all `existing-fields` and any field in `new-fields` not already found in
+  `existing-fields`"
+  [existing-fields new-fields]
+  (let [existing-field-names (set (map name existing-fields))]
+    (vec (concat existing-fields
+                 (remove (comp existing-field-names name) new-fields)))))
+
+;; Needed as `resolve-tables-in-nested-query` and `resolved-tables` are mutually recursive
+(declare resolve-tables)
+
+(defn- resolve-tables-in-nested-query
+  "This function is pull up a nested query found in `expanded-query-dict` and run it through
+  `resolve-tables`. Unfortunately our work isn't done there. If `expanded-query-dict` has a breakout that refers to a
+  column from the nested query we will need to resolve the fields in that breakout after the nested query has been
+  resolved. More comments in-line that breakout the work for that."
+  [{{:keys [source-query]} :query, :as expanded-query-dict}]
+  ;; No need to try and resolve a nested native query
+  (if (:native source-query)
+    expanded-query-dict
+    (let [ ;; Resolve the nested query as if it were a top level query
+          {nested-q :query :as nested-qd} (resolve-tables (assoc expanded-query-dict :query source-query))
+          nested-source-table             (get-in nested-qd [:query :source-table])
+          ;; Build a list of join tables found from the newly resolved nested query
+          nested-joined-tables            (fk-field-ids->joined-tables (:id nested-source-table)
+                                                                       (:fk-field-ids nested-qd))
+          ;; Create the map of fk to table info from the resolved nested query
+          fk-id+table-id->table           (create-fk-id+table-id->table nested-source-table nested-joined-tables)
+          ;; Resolve the top level (original) breakout fields with the join information from the resolved nested query
+          resolved-breakout               (for [breakout (get-in expanded-query-dict [:query :breakout])]
+                                            (resolve-table breakout fk-id+table-id->table))]
+      (assoc-in expanded-query-dict [:query :source-query]
+                (if (and (contains? nested-q :fields)
+                         (seq resolved-breakout))
+                  (update nested-q :fields append-new-fields resolved-breakout)
+                  nested-q)))))
+
 (defn- resolve-tables
   "Resolve the `Tables` in an EXPANDED-QUERY-DICT."
-  [{:keys [table-ids fk-field-ids], :as expanded-query-dict}]
-  (let [{source-table-id :id :as source-table} (qputil/get-in-normalized expanded-query-dict [:query :source-table])]
-    (if-not source-table-id
-      ;; if we have a `source-query`, recurse and resolve tables in that
-      (update-in expanded-query-dict [:query :source-query] (fn [source-query]
-                                                              (if (:native source-query)
-                                                                source-query
-                                                                (:query (resolve-tables (assoc expanded-query-dict
-                                                                                          :query source-query))))))
-      ;; otherwise we can resolve tables in the (current) top-level
-      (let [table-ids             (conj table-ids source-table-id)
-            joined-tables         (fk-field-ids->joined-tables source-table-id fk-field-ids)
-            fk-id+table-id->table (into {[nil source-table-id] source-table}
-                                        (for [{:keys [source-field table-id join-alias]} joined-tables]
-                                          {[(:field-id source-field) table-id] {:name join-alias
-                                                                                :id   table-id}}))]
-        (as-> expanded-query-dict <>
-          (assoc-in <> [:query :join-tables]  joined-tables)
-          (walk/postwalk #(resolve-table % fk-id+table-id->table) <>))))))
+  [{:keys [fk-field-ids], {{source-table-id :id :as source-table} :source-table} :query, :as expanded-query-dict}]
+  (if-not source-table-id
+    ;; if we have a `source-query`, recurse and resolve tables in that
+    (resolve-tables-in-nested-query expanded-query-dict)
+    ;; otherwise we can resolve tables in the (current) top-level
+    (let [joined-tables         (fk-field-ids->joined-tables source-table-id fk-field-ids)
+          fk-id+table-id->table (create-fk-id+table-id->table source-table joined-tables)]
+      (as-> expanded-query-dict <>
+        (assoc-in <> [:query :join-tables]  joined-tables)
+        (walk/postwalk #(resolve-table % fk-id+table-id->table) <>)))))
+
+(defn- resolve-field-literals
+  "When resolving a field, we connect a `field-id` with a `Field` in our metadata tables. This is a similar process
+  for `FieldLiteral`s, except we are attempting to connect a `FieldLiteral` with an associated entry in the
+  `result_metadata` attached to the query (typically from the `Card` of a nested query)."
+  [{:keys [result_metadata] :as expanded-query-dict}]
+  (let [name->fingerprint (zipmap (map :name result_metadata)
+                                  (map :fingerprint result_metadata))]
+    (qputil/postwalk-pred #(instance? FieldLiteral %)
+                          (fn [{:keys [binning-strategy binning-param] :as node}]
+                            (let [fingerprint     (get name->fingerprint (:field-name node))
+                                  node-with-print (assoc node :fingerprint fingerprint)]
+                              (cond
+                                ;; We can't bin without min/max values found from a fingerprint
+                                (and binning-strategy (not fingerprint))
+                                (throw (Exception. "Binning not supported on a field literal with no fingerprint"))
+
+                                (and fingerprint binning-strategy)
+                                (resolve-binned-field binning-strategy binning-param node-with-print)
+
+                                :else
+                                node-with-print)))
+                          expanded-query-dict)))
 
 
 ;;; ------------------------------------------------ PUBLIC INTERFACE ------------------------------------------------
 
+(defn resolve-fields-if-needed
+  "Resolves any unresolved fields found in `fields`. Will just return resolved fields with no changes."
+  [fields]
+  (let [fields-to-resolve (map unresolved-field-id fields)]
+    (if-let [field-id->field (and (seq fields-to-resolve)
+                                  (u/key-by :field-id (fetch-fields fields-to-resolve)))]
+      (map #(resolve-field % field-id->field) fields)
+      fields)))
+
 (defn resolve
   "Resolve placeholders by fetching `Fields`, `Databases`, and `Tables` that are referred to in EXPANDED-QUERY-DICT."
   [expanded-query-dict]
   (some-> expanded-query-dict
           record-fk-field-ids
           resolve-fields
+          resolve-field-literals
           resolve-tables))
 
 (defn resolve-middleware
   "Wraps the `resolve` function in a query-processor middleware"
   [qp]
-  (fn [{database-id :database, :as query}]
+  (fn [{database-id :database, query-type :type, :as query}]
     (let [resolved-db (db/select-one [Database :name :id :engine :details :timezone], :id database-id)
-          query       (if (qputil/mbql-query? query)
+          query       (if (= query-type :query)
                         (resolve query)
                         query)]
       (qp (assoc query :database resolved-db)))))
diff --git a/src/metabase/query_processor/middleware/results_metadata.clj b/src/metabase/query_processor/middleware/results_metadata.clj
index 839a4ea8ebf7ce00b6a4d555f1702f82cf7add01..52c06d864b79a18b38dff8346beea1487e678732 100644
--- a/src/metabase/query_processor/middleware/results_metadata.clj
+++ b/src/metabase/query_processor/middleware/results_metadata.clj
@@ -5,51 +5,15 @@
   (:require [buddy.core.hash :as hash]
             [cheshire.core :as json]
             [clojure.tools.logging :as log]
-            [metabase.models.humanization :as humanization]
             [metabase.query-processor.interface :as i]
+            [metabase.sync.analyze.query-results :as qr]
             [metabase.util :as u]
             [metabase.util
              [encryption :as encryption]
-             [schema :as su]]
+             [i18n :refer [tru]]]
             [ring.util.codec :as codec]
-            [schema.core :as s]
             [toucan.db :as db]))
 
-(def ^:private DateTimeUnitKeywordOrString
-  "Schema for a valid datetime unit string like \"default\" or \"minute-of-hour\"."
-  (s/constrained su/KeywordOrString
-                 (fn [unit]
-                   (contains? i/datetime-field-units (keyword unit)))
-                 "Valid field datetime unit keyword or string"))
-
-(def ResultsMetadata
-  "Schema for valid values of the `result_metadata` column."
-  (su/with-api-error-message (s/named [{:name                          su/NonBlankString
-                                        :display_name                  su/NonBlankString
-                                        (s/optional-key :description)  (s/maybe su/NonBlankString)
-                                        :base_type                     su/FieldTypeKeywordOrString
-                                        (s/optional-key :special_type) (s/maybe su/FieldTypeKeywordOrString)
-                                        (s/optional-key :unit)         (s/maybe DateTimeUnitKeywordOrString)}]
-                                      "Valid array of results column metadata maps")
-    "value must be an array of valid results column metadata maps."))
-
-(s/defn ^:private results->column-metadata :- (s/maybe ResultsMetadata)
-  "Return the desired storage format for the column metadata coming back from RESULTS, or `nil` if no columns were returned."
-  [results]
-  ;; rarely certain queries will return columns with no names, for example `SELECT COUNT(*)` in SQL Server seems to come back with no name
-  ;; since we can't use those as field literals in subsequent queries just filter them out
-  (seq (for [col   (:cols results)
-             :when (seq (:name col))]
-         (merge
-          ;; if base-type isn't set put a default one in there. Similarly just use humanized value of `:name` for `:display_name` if one isn't set
-          {:base_type    :type/*
-           :display_name (humanization/name->human-readable-name (name (:name col)))}
-          (u/select-non-nil-keys col [:name :display_name :description :base_type :special_type :unit])
-          ;; since years are actually returned as text they can't be used for breakout purposes so don't advertise them as DateTime columns
-          (when (= (:unit col) :year)
-            {:base_type :type/Text
-             :unit      nil})))))
-
 ;; TODO - is there some way we could avoid doing this every single time a Card is ran? Perhaps by passing the current Card
 ;; metadata as part of the query context so we can compare for changes
 (defn- record-metadata! [card-id metadata]
@@ -83,24 +47,27 @@
           (encryption/maybe-decrypt checksum))))
 
 (defn record-and-return-metadata!
-  "Middleware that records metadata about the columns returned when running the query if it is associated with a Card."
+  "Middleware that records metadata about the columns returned when running the query."
   [qp]
   (fn [{{:keys [card-id nested?]} :info, :as query}]
     (let [results (qp query)]
-      (try
-        (let [metadata (results->column-metadata results)]
-          ;; At the very least we can skip the Extra DB call to update this Card's metadata results
-          ;; if its DB doesn't support nested queries in the first place
-          (when (i/driver-supports? :nested-queries)
-            (when (and card-id
+      (if (-> query :middleware :skip-results-metadata?)
+        results
+        (try
+          (let [metadata (seq (qr/results->column-metadata results))]
+            ;; At the very least we can skip the Extra DB call to update this Card's metadata results
+            ;; if its DB doesn't support nested queries in the first place
+            (when (and (i/driver-supports? :nested-queries)
+                       card-id
                        (not nested?))
-              (record-metadata! card-id metadata)))
-          ;; add the metadata and checksum to the response
-          (assoc results :results_metadata {:checksum (metadata-checksum metadata)
-                                            :columns  metadata}))
-        ;; if for some reason we weren't able to record results metadata for this query then just proceed as normal
-        ;; rather than failing the entire query
-        (catch Throwable e
-          (log/error "Error recording results metadata for query:" (.getMessage e) "\n"
-                     (u/pprint-to-str (u/filtered-stacktrace e)))
-          results)))))
+              (record-metadata! card-id metadata))
+            ;; add the metadata and checksum to the response
+            (assoc results :results_metadata {:checksum (metadata-checksum metadata)
+                                              :columns  metadata}))
+          ;; if for some reason we weren't able to record results metadata for this query then just proceed as normal
+          ;; rather than failing the entire query
+          (catch Throwable e
+            (log/error (tru "Error recording results metadata for query:")
+                       (.getMessage e) "\n"
+                       (u/pprint-to-str (u/filtered-stacktrace e)))
+            results))))))
diff --git a/src/metabase/query_processor/middleware/source_table.clj b/src/metabase/query_processor/middleware/source_table.clj
index 56718f02cff31be2443d5c91e7aa07a654ab5bbc..262de2f3517519115c03823f9088df780c59d70f 100644
--- a/src/metabase/query_processor/middleware/source_table.clj
+++ b/src/metabase/query_processor/middleware/source_table.clj
@@ -1,29 +1,28 @@
 (ns metabase.query-processor.middleware.source-table
   (:require [metabase.models.table :refer [Table]]
-            [metabase.query-processor.util :as qputil]
             [toucan.db :as db]))
 
 (defn- resolve-source-table
-  [expanded-query-dict]
-  (let [source-table-id (qputil/get-in-normalized expanded-query-dict [:query :source-table])]
-    (cond
-      (not (qputil/mbql-query? expanded-query-dict))
-      expanded-query-dict
+  [{{source-table-id :source-table} :query, query-type :type, :as expanded-query-dict}]
+  (cond
+    (not= query-type :query)
+    expanded-query-dict
 
-      (nil? source-table-id)
-      (update-in expanded-query-dict [:query :source-query] (fn [source-query]
-                                                              (if (:native source-query)
-                                                                source-query
-                                                                (:query (resolve-source-table (assoc expanded-query-dict :query source-query))))))
+    (nil? source-table-id)
+    (update-in expanded-query-dict [:query :source-query] (fn [source-query]
+                                                            (if (:native source-query)
+                                                              source-query
+                                                              (:query (resolve-source-table (assoc expanded-query-dict
+                                                                                              :query source-query))))))
 
-      :else
-      (let [source-table (or (db/select-one [Table :schema :name :id], :id source-table-id)
-                             (throw (Exception. (format "Query expansion failed: could not find source table %d." source-table-id))))]
-        (assoc-in expanded-query-dict [:query :source-table] source-table)))))
+    :else
+    (let [source-table (or (db/select-one [Table :schema :name :id], :id source-table-id)
+                           (throw (Exception. (format "Query expansion failed: could not find source table %d."
+                                                      source-table-id))))]
+      (assoc-in expanded-query-dict [:query :source-table] source-table))))
 
 (defn resolve-source-table-middleware
-  "Middleware that will take the source-table (an integer) and hydrate
-  that source table from the the database and attach it as
-  `:source-table`"
+  "Middleware that will take the source-table (an integer) and hydrate that source table from the the database and
+  attach it as `:source-table`"
   [qp]
   (comp qp resolve-source-table))
diff --git a/src/metabase/query_processor/middleware/validate.clj b/src/metabase/query_processor/middleware/validate.clj
new file mode 100644
index 0000000000000000000000000000000000000000..5c914a4b6adefdd4a11274cc7dff11ea02201798
--- /dev/null
+++ b/src/metabase/query_processor/middleware/validate.clj
@@ -0,0 +1,8 @@
+(ns metabase.query-processor.middleware.validate
+  "Middleware for checking that a normalized query is valid."
+  (:require [metabase.mbql.schema :as mbql.s]))
+
+(defn validate-query
+  "Middleware that validates a query immediately after normalization."
+  [qp]
+  (comp qp mbql.s/validate-query))
diff --git a/src/metabase/query_processor/util.clj b/src/metabase/query_processor/util.clj
index 5441fa30c01096ff1fed8fcd3795fafcc819d0a0..c77e12bf88a71b363be7fb410e470ee99c6d520f 100644
--- a/src/metabase/query_processor/util.clj
+++ b/src/metabase/query_processor/util.clj
@@ -4,12 +4,15 @@
              [codecs :as codecs]
              [hash :as hash]]
             [cheshire.core :as json]
-            [clojure.string :as str]
-            [metabase.util :as u]
+            [clojure
+             [string :as str]
+             [walk :as walk]]
             [metabase.util.schema :as su]
             [schema.core :as s]))
 
-(defn mbql-query?
+;; TODO - I think most of the functions in this namespace that we don't remove could be moved to `metabase.mbql.util`
+
+(defn ^:deprecated mbql-query? ;; not really needed anymore since we don't need to normalize tokens
   "Is the given query an MBQL query?"
   [query]
   (= :query (keyword (:type query))))
@@ -25,13 +28,13 @@
   [{{aggregations :aggregation, :keys [limit page]} :query}]
   (and (not limit)
        (not page)
-       (or (empty? aggregations)
-           (= (:aggregation-type (first aggregations)) :rows))))
+       (nil? aggregations)))
 
 (defn query->remark
   "Generate an approparite REMARK to be prepended to a query to give DBAs additional information about the query being
   executed. See documentation for `mbql->native` and [issue #2386](https://github.com/metabase/metabase/issues/2386)
-  for more information."  ^String [{{:keys [executed-by query-hash query-type], :as info} :info}]
+  for more information."
+  ^String [{{:keys [executed-by query-hash query-type], :as info} :info}]
   (str "Metabase" (when info
                     (assert (instance? (Class/forName "[B") query-hash))
                     (format ":: userID: %s queryType: %s queryHash: %s"
@@ -40,16 +43,8 @@
 
 ;;; ------------------------------------------------- Normalization --------------------------------------------------
 
-;; The following functions make it easier to deal with MBQL queries, which are case-insensitive, string/keyword
-;; insensitive, and underscore/hyphen insensitive.  These should be preferred instead of assuming the frontend will
-;; always pass in clauses the same way, since different variation are all legal under MBQL '98.
-
-;; TODO - In the future it might make sense to simply walk the entire query and normalize the whole thing when it
-;; comes in. I've tried implementing middleware to do that but it ended up breaking a few things that wrongly assume
-;; different clauses will always use a certain case (e.g. SQL `:template_tags`). Fixing all of that is out-of-scope
-;; for the nested queries PR but should possibly be revisited in the future.
-
-(s/defn normalize-token :- s/Keyword
+;; TODO - this has been moved to `metabase.mbql.util`; use that implementation instead.
+(s/defn ^:deprecated normalize-token :- s/Keyword
   "Convert a string or keyword in various cases (`lisp-case`, `snake_case`, or `SCREAMING_SNAKE_CASE`) to a lisp-cased
   keyword."
   [token :- su/KeywordOrString]
@@ -58,55 +53,28 @@
       (str/replace #"_" "-")
       keyword))
 
-(defn get-normalized
-  "Get the value for normalized key K in map M, regardless of how the key was specified in M,
-   whether string or keyword, lisp-case, snake_case, or SCREAMING_SNAKE_CASE.
-
-     (get-normalized {\"NUM_TOUCANS\" 2} :num-toucans) ; -> 2"
-  ([m k]
-   {:pre [(or (u/maybe? map? m)
-              (println "Not a map:" m))]}
-   (when (seq m)
-     (let [k (normalize-token k)]
-       (loop [[[map-k v] & more] (seq m)]
-         (cond
-           (= k (normalize-token map-k)) v
-           (seq more)                    (recur more))))))
-  ([m k not-found]
-   (let [v (get-normalized m k)]
-     (if (some? v)
-       v
-       not-found))))
-
-(defn get-in-normalized
-  "Like `get-normalized`, but accepts a sequence of keys KS, like `get-in`.
-
-    (get-in-normalized {\"NUM_BIRDS\" {\"TOUCANS\" 2}} [:num-birds :toucans]) ; -> 2"
+(defn get-in-query
+  "Similar to `get-in` but will look in either `:query` or recursively in `[:query :source-query]`. Using
+  this function will avoid having to check if there's a nested query vs. top-level query."
   ([m ks]
-   {:pre [(u/maybe? sequential? ks)]}
-   (loop [m m, [k & more] ks]
-     (if-not k
-       m
-       (recur (get-normalized m k) more))))
+   (get-in-query m ks nil))
   ([m ks not-found]
-   (let [v (get-in-normalized m ks)]
-     (if (some? v)
-       v
-       not-found))))
-
-(defn dissoc-normalized
-  "Remove all matching keys from map M regardless of case, string/keyword, or hypens/underscores.
-
-     (dissoc-normalized {\"NUM_TOUCANS\" 3} :num-toucans) ; -> {}"
-  [m k]
-  {:pre [(or (u/maybe? map? m)
-             (println "Not a map:" m))]}
-  (let [k (normalize-token k)]
-    (loop [m m, [map-k & more, :as ks] (keys m)]
-      (cond
-        (not (seq ks)) m
-        (= k (normalize-token map-k)) (recur (dissoc m map-k) more)
-        :else                         (recur m                more)))))
+   (if-let [source-query (get-in m [:query :source-query])]
+     (recur (assoc m :query source-query) ks not-found)
+     (get-in m (cons :query ks) not-found))))
+
+(defn assoc-in-query
+  "Similar to `assoc-in but will look in either `:query` or recursively in `[:query :source-query]`. Using
+  this function will avoid having to check if there's a nested query vs. top-level query."
+  [m ks v]
+  (if-let [source-query (get-in m [:query :source-query])]
+    ;; We have a soure-query, we need to recursively `assoc-in` with the source query as the query
+    (assoc-in m
+              [:query :source-query]
+              (-> (assoc m :query source-query)
+                  (assoc-in-query ks v)
+                  :query))
+    (assoc-in m (cons :query ks) v)))
 
 
 ;;; ---------------------------------------------------- Hashing -----------------------------------------------------
@@ -134,7 +102,37 @@
 (defn query->source-card-id
   "Return the ID of the Card used as the \"source\" query of this query, if applicable; otherwise return `nil`."
   ^Integer [outer-query]
-  (let [source-table (get-in-normalized outer-query [:query :source-table])]
+  (let [source-table (get-in outer-query [:query :source-table])]
     (when (string? source-table)
       (when-let [[_ card-id-str] (re-matches #"^card__(\d+$)" source-table)]
         (Integer/parseInt card-id-str)))))
+
+
+;;; ---------------------------------------- General Tree Manipulation Helpers ---------------------------------------
+
+(defn ^:deprecated postwalk-pred
+  "Transform `form` by applying `f` to each node where `pred` returns true
+
+  DEPRECATED: use `mbql.u/replace-clauses` instead, or if that's not sophisticated enough, use a `clojure.walk` fn
+  directly."
+  [pred f form]
+  (walk/postwalk (fn [node]
+                   (if (pred node)
+                     (f node)
+                     node))
+                 form))
+
+(defn ^:deprecated postwalk-collect
+  "Invoke `collect-fn` on each node satisfying `pred`. If `collect-fn` returns a value, accumulate that and return the
+  results.
+
+  DEPRECATED: Use `mbql.u/clause-instances` instead to find all instances of a clause."
+  [pred collect-fn form]
+  (let [results (atom [])]
+    (postwalk-pred pred
+                   (fn [node]
+                     (when-let [result (collect-fn node)]
+                       (swap! results conj result))
+                     node)
+                   form)
+    @results))
diff --git a/src/metabase/related.clj b/src/metabase/related.clj
index 6d66efbe25352b2c0b611a1db3755b017634b9f3..c7bf4acf721e20d3551c3655696b80c1d1473b9b 100644
--- a/src/metabase/related.clj
+++ b/src/metabase/related.clj
@@ -93,29 +93,31 @@
     (concat best (->> rest shuffle (take max-serendipity-matches)))))
 
 (def ^:private ^{:arglists '([entities])} filter-visible
-  (partial filter (fn [{:keys [archived visibility_type] :as entity}]
+  (partial filter (fn [{:keys [archived visibility_type active] :as entity}]
                     (and (or (nil? visibility_type)
-                             (= (name visibility_type) "normal"))
+                             (= (qp.util/normalize-token visibility_type) :normal))
                          (not archived)
+                         (not= active false)
                          (mi/can-read? entity)))))
 
 (defn- metrics-for-table
   [table]
   (filter-visible (db/select Metric
-                    :table_id  (:id table)
+                    :table_id (:id table)
                     :archived false)))
 
 (defn- segments-for-table
   [table]
   (filter-visible (db/select Segment
-                    :table_id  (:id table)
+                    :table_id (:id table)
                     :archived false)))
 
 (defn- linking-to
   [table]
   (->> (db/select-field :fk_target_field_id Field
-         :table_id (:id table)
-         :fk_target_field_id [:not= nil])
+         :table_id           (:id table)
+         :fk_target_field_id [:not= nil]
+         :active             true)
        (map (comp Table :table_id Field))
        distinct
        filter-visible
@@ -123,9 +125,12 @@
 
 (defn- linked-from
   [table]
-  (if-let [fields (not-empty (db/select-field :id Field :table_id (:id table)))]
+  (if-let [fields (not-empty (db/select-field :id Field
+                               :table_id (:id table)
+                               :active   true))]
     (->> (db/select-field :table_id Field
-           :fk_target_field_id [:in fields])
+           :fk_target_field_id [:in fields]
+           :active             true)
          (map Table)
          filter-visible
          (take max-matches))
@@ -134,10 +139,10 @@
 (defn- cards-sharing-dashboard
   [card]
   (if-let [dashboards (not-empty (db/select-field :dashboard_id DashboardCard
-                                     :card_id (:id card)))]
+                                   :card_id (:id card)))]
     (->> (db/select-field :card_id DashboardCard
            :dashboard_id [:in dashboards]
-           :card_id [:not= (:id card)])
+           :card_id      [:not= (:id card)])
          (map Card)
          filter-visible
          (take max-matches))
@@ -165,8 +170,8 @@
 (defn- recently-modified-dashboards
   []
   (->> (db/select-field :model_id 'Revision
-         :model   "Dashboard"
-         :user_id api/*current-user-id*
+         :model     "Dashboard"
+         :user_id   api/*current-user-id*
          {:order-by [[:timestamp :desc]]})
        (map Dashboard)
        filter-visible
@@ -266,7 +271,8 @@
                          :db_id           (:db_id table)
                          :schema          (:schema table)
                          :id              [:not= (:id table)]
-                         :visibility_type nil)
+                         :visibility_type nil
+                         :active          true)
                        (remove (set (concat linking-to linked-from)))
                        filter-visible
                        interesting-mix)}))
@@ -287,7 +293,8 @@
      :fields   (->> (db/select Field
                       :table_id        (:id table)
                       :id              [:not= (:id field)]
-                      :visibility_type "normal")
+                      :visibility_type "normal"
+                      :active          true)
                     filter-visible
                     interesting-mix)}))
 
diff --git a/src/metabase/routes.clj b/src/metabase/routes.clj
index e1f76ef64c632ef0fde256d30ed11808a4db4665..f0edc0493f2b92f15b26ab204f68f81eb0bd112f 100644
--- a/src/metabase/routes.clj
+++ b/src/metabase/routes.clj
@@ -16,7 +16,7 @@
              [routes :as api]]
             [metabase.core.initialization-status :as init-status]
             [metabase.util.embed :as embed]
-            [puppetlabs.i18n.core :refer [trs *locale*]]
+            [puppetlabs.i18n.core :refer [*locale*]]
             [ring.util.response :as resp]
             [stencil.core :as stencil]))
 
diff --git a/src/metabase/setup.clj b/src/metabase/setup.clj
index bdb1f1be029e927c39774b2a9b69b3eb0d734509..da1c2828b1c0ebd64fde1ec991e0f37d63423eed 100644
--- a/src/metabase/setup.clj
+++ b/src/metabase/setup.clj
@@ -1,27 +1,33 @@
-(ns metabase.setup)
+(ns metabase.setup
+  (:require [metabase.models.setting :refer [Setting defsetting]]
+            [toucan.db :as db]))
 
-(defonce ^:private setup-token
-  (atom nil))
+(defsetting ^:private setup-token
+  "A token used to signify that an instance has permissions to create the initial User. This is created upon the first
+  launch of Metabase, by the first instance; once used, it is cleared out, never to be used again."
+  :internal? true)
 
 (defn token-value
   "Return the value of the setup token, if any."
   []
-  @setup-token)
+  (setup-token))
 
 (defn token-match?
   "Function for checking if the supplied string matches our setup token.
-   Returns boolean `true` if supplied token matches `@setup-token`, `false` otherwise."
+   Returns boolean `true` if supplied token matches the setup token, `false` otherwise."
   [token]
   {:pre [(string? token)]}
-  (= token @setup-token))
+  (= token (setup-token)))
 
 (defn create-token!
-  "Create and set a new `@setup-token`.
-   Returns the newly created token."
+  "Create and set a new setup token, if one has not already been created. Returns the newly created token."
   []
-  (reset! setup-token (str (java.util.UUID/randomUUID))))
+  ;; fetch the value directly from the DB; *do not* rely on cached value, in case a different instance came along and
+  ;; already created it
+  (or (db/select-one-field :value Setting :key "setup-token")
+      (setup-token (str (java.util.UUID/randomUUID)))))
 
 (defn clear-token!
-  "Clear the `@setup-token` if it exists and reset it to nil."
+  "Clear the setup token if it exists and reset it to `nil`."
   []
-  (reset! setup-token nil))
+  (setup-token nil))
diff --git a/src/metabase/sync/analyze.clj b/src/metabase/sync/analyze.clj
index dc2a298bd4fce60430ff6f5ab2cd53d74f57a9c2..9c249104b3a484e890e5d154f8a9af317d32e0cd 100644
--- a/src/metabase/sync/analyze.clj
+++ b/src/metabase/sync/analyze.clj
@@ -13,8 +13,9 @@
              [fingerprint :as fingerprint]
              #_[table-row-count :as table-row-count]]
             [metabase.util :as u]
-            [metabase.util.date :as du]
-            [puppetlabs.i18n.core :refer [trs]]
+            [metabase.util
+             [date :as du]
+             [i18n :refer [trs]]]
             [schema.core :as s]
             [toucan.db :as db]))
 
diff --git a/src/metabase/sync/analyze/classifiers/category.clj b/src/metabase/sync/analyze/classifiers/category.clj
index 7c7a49373808884c643bd73ea3899d7de7ee1a80..7575735470772a32786253777ff09d0a2bbc690b 100644
--- a/src/metabase/sync/analyze/classifiers/category.clj
+++ b/src/metabase/sync/analyze/classifiers/category.clj
@@ -30,11 +30,19 @@
       (isa? special-type :type/PK)
       (isa? special-type :type/FK)))
 
+(defn- not-all-nil?
+  [fingerprint]
+  (or (some-> fingerprint :type :type/Number :min some?)
+      (some-> fingerprint :type :type/Text :average-length pos?)))
+
 (s/defn ^:private field-should-be-category? :- (s/maybe s/Bool)
-  [distinct-count :- s/Int, field :- su/Map]
-  ;; only mark a Field as a Category if it doesn't already have a special type
-  (when-not (:special_type field)
-    (when (<= distinct-count field-values/category-cardinality-threshold)
+  [fingerprint :- (s/maybe i/Fingerprint), field :- su/Map]
+  (let [distinct-count (get-in fingerprint [:global :distinct-count])]
+    ;; Only mark a Field as a Category if it doesn't already have a special type.
+    (when (and (nil? (:special_type field))
+               (or (not-all-nil? fingerprint)
+                   (isa? (:base_type field) :type/Boolean))
+               (<= distinct-count field-values/category-cardinality-threshold))
       (log/debug (format "%s has %d distinct values. Since that is less than %d, we're marking it as a category."
                          (sync-util/name-for-logging field)
                          distinct-count
@@ -62,5 +70,5 @@
     (when-not (cannot-be-category-or-list? (:base_type field) (:special_type field))
       (when-let [distinct-count (get-in fingerprint [:global :distinct-count])]
         (cond-> field
-          (field-should-be-category?  distinct-count field) (assoc :special_type :type/Category)
+          (field-should-be-category? fingerprint field)     (assoc :special_type :type/Category)
           (field-should-be-auto-list? distinct-count field) (assoc :has_field_values :auto-list))))))
diff --git a/src/metabase/sync/analyze/classifiers/name.clj b/src/metabase/sync/analyze/classifiers/name.clj
index 8d326bc9b3c5fdc9e75019da2f4b5341a6b42112..ff8aecec192f86db48488cdfae012973d27c491b 100644
--- a/src/metabase/sync/analyze/classifiers/name.clj
+++ b/src/metabase/sync/analyze/classifiers/name.clj
@@ -21,6 +21,7 @@
 (def ^:private time-type        #{:type/Time})
 (def ^:private date-type        #{:type/Date})
 (def ^:private number-type      #{:type/Number})
+(def ^:private any-type         #{:type/*})
 
 
 (def ^:private pattern+base-types+special-type
@@ -31,7 +32,8 @@
 
    *  Convert field name to lowercase before matching against a pattern
    *  Consider a nil set-of-valid-base-types to mean \"match any base type\""
-  [[#"^.*_lat$"                    float-type       :type/Latitude]
+  [[#"^id$"                        any-type         :type/PK]
+   [#"^.*_lat$"                    float-type       :type/Latitude]
    [#"^.*_lon$"                    float-type       :type/Longitude]
    [#"^.*_lng$"                    float-type       :type/Longitude]
    [#"^.*_long$"                   float-type       :type/Longitude]
@@ -114,24 +116,36 @@
 (s/defn ^:private special-type-for-name-and-base-type :- (s/maybe su/FieldType)
   "If `name` and `base-type` matches a known pattern, return the `special_type` we should assign to it."
   [field-name :- su/NonBlankString, base-type :- su/FieldType]
-  (or (when (= "id" (str/lower-case field-name)) :type/PK)
-      (some (fn [[name-pattern valid-base-types special-type]]
-              (when (and (some (partial isa? base-type) valid-base-types)
-                         (re-find name-pattern (str/lower-case field-name)))
-                special-type))
-            pattern+base-types+special-type)))
+  (let [field-name (str/lower-case field-name)]
+    (some (fn [[name-pattern valid-base-types special-type]]
+            (when (and (some (partial isa? base-type) valid-base-types)
+                       (re-find name-pattern field-name))
+              special-type))
+          pattern+base-types+special-type)))
 
-(s/defn infer-special-type :- (s/maybe i/FieldInstance)
+(def ^:private FieldOrColumn
+  "Schema that allows a `metabase.model.field/Field` or a column from a query resultset"
+  {:name                          su/NonBlankString
+   :base_type                     s/Keyword
+   (s/optional-key :special_type) (s/maybe s/Keyword)
+   s/Any                          s/Any})
+
+(s/defn infer-special-type :- (s/maybe s/Keyword)
   "Classifer that infers the special type of a FIELD based on its name and base type."
-  [field :- i/FieldInstance, _ :- (s/maybe i/Fingerprint)]
+  [field-or-column :- FieldOrColumn]
   ;; Don't overwrite keys, else we're ok with overwriting as a new more precise type might have
   ;; been added.
-  (when (not-any? (partial isa? (:special_type field)) [:type/PK :type/FK])
-    (when-let [inferred-special-type (special-type-for-name-and-base-type (:name field) (:base_type field))]
-      (log/debug (format "Based on the name of %s, we're giving it a special type of %s."
-                         (sync-util/name-for-logging field)
-                         inferred-special-type))
-      (assoc field :special_type inferred-special-type))))
+  (when (not-any? (partial isa? (:special_type field-or-column)) [:type/PK :type/FK])
+    (special-type-for-name-and-base-type (:name field-or-column) (:base_type field-or-column))))
+
+(s/defn infer-and-assoc-special-type  :- (s/maybe FieldOrColumn)
+  "Returns `field-or-column` with a computed special type based on the name and base type of the `field-or-column`"
+  [field-or-column :- FieldOrColumn, _ :- (s/maybe i/Fingerprint)]
+  (when-let [inferred-special-type (infer-special-type field-or-column)]
+    (log/debug (format "Based on the name of %s, we're giving it a special type of %s."
+                       (sync-util/name-for-logging field-or-column)
+                       inferred-special-type))
+    (assoc field-or-column :special_type inferred-special-type)))
 
 (defn- prefix-or-postfix
   [s]
diff --git a/src/metabase/sync/analyze/classify.clj b/src/metabase/sync/analyze/classify.clj
index fa323457be2b1bbe89b1f58d5bb0aa77ce527ff5..8438a84a7a34d2e5760acc53d22e2dbd24e9e8c3 100644
--- a/src/metabase/sync/analyze/classify.clj
+++ b/src/metabase/sync/analyze/classify.clj
@@ -69,7 +69,7 @@
   "Various classifier functions available. These should all take two args, a `FieldInstance` and a possibly `nil`
   `Fingerprint`, and return `FieldInstance` with any inferred property changes, or `nil` if none could be inferred.
   Order is important!"
-  [name/infer-special-type
+  [name/infer-and-assoc-special-type
    category/infer-is-category-or-list
    no-preview-display/infer-no-preview-display
    text-fingerprint/infer-special-type])
diff --git a/src/metabase/sync/analyze/fingerprint.clj b/src/metabase/sync/analyze/fingerprint.clj
index 72de0c977b040b0bcf8d2c0f876624c211fd49d7..60bff48feca9c22a9cf60c0bfac4956de6506642 100644
--- a/src/metabase/sync/analyze/fingerprint.clj
+++ b/src/metabase/sync/analyze/fingerprint.clj
@@ -4,54 +4,31 @@
   (:require [clojure.set :as set]
             [clojure.tools.logging :as log]
             [honeysql.helpers :as h]
+            [metabase.db :as mdb]
+            [metabase.driver :as driver]
             [metabase.models.field :refer [Field]]
             [metabase.sync
              [interface :as i]
              [util :as sync-util]]
-            [metabase.sync.analyze.fingerprint
-             [datetime :as datetime]
-             [global :as global]
-             [number :as number]
-             [sample :as sample]
-             [text :as text]]
+            [metabase.sync.analyze.fingerprint.fingerprinters :as f]
             [metabase.util :as u]
             [metabase.util
              [date :as du]
              [schema :as su]]
+            [redux.core :as redux]
             [schema.core :as s]
             [toucan.db :as db]))
 
-(s/defn ^:private type-specific-fingerprint :- (s/maybe i/TypeSpecificFingerprint)
-  "Return type-specific fingerprint info for FIELD AND. a FieldSample of Values if it has an elligible base type"
-  [field :- i/FieldInstance, values :- i/FieldSample]
-  (condp #(isa? %2 %1) (:base_type field)
-    :type/Text     {:type/Text (text/text-fingerprint values)}
-    :type/Number   {:type/Number (number/number-fingerprint values)}
-    :type/DateTime {:type/DateTime (datetime/datetime-fingerprint values)}
-    nil))
-
-(s/defn ^:private fingerprint :- i/Fingerprint
-  "Generate a 'fingerprint' from a FieldSample of VALUES."
-  [field :- i/FieldInstance, values :- i/FieldSample]
-  (merge
-   (when-let [global-fingerprint (global/global-fingerprint values)]
-     {:global global-fingerprint})
-   (when-let [type-specific-fingerprint (type-specific-fingerprint field values)]
-     {:type type-specific-fingerprint})))
-
-
 (s/defn ^:private save-fingerprint!
-  [field :- i/FieldInstance, fingerprint :- i/Fingerprint]
-  ;; don't bother saving fingerprint if it's completely empty
-  (when (seq fingerprint)
-    (log/debug (format "Saving fingerprint for %s" (sync-util/name-for-logging field)))
-    ;; All Fields who get new fingerprints should get marked as having the latest fingerprint version, but we'll
-    ;; clear their values for `last_analyzed`. This way we know these fields haven't "completed" analysis for the
-    ;; latest fingerprints.
-    (db/update! Field (u/get-id field)
-      :fingerprint         fingerprint
-      :fingerprint_version i/latest-fingerprint-version
-      :last_analyzed       nil)))
+  [field :- i/FieldInstance, fingerprint :- (s/maybe i/Fingerprint)]
+  (log/debug (format "Saving fingerprint for %s" (sync-util/name-for-logging field)))
+  ;; All Fields who get new fingerprints should get marked as having the latest fingerprint version, but we'll
+  ;; clear their values for `last_analyzed`. This way we know these fields haven't "completed" analysis for the
+  ;; latest fingerprints.
+  (db/update! Field (u/get-id field)
+    :fingerprint         fingerprint
+    :fingerprint_version i/latest-fingerprint-version
+    :last_analyzed       nil))
 
 (defn- empty-stats-map [fields-count]
   {:no-data-fingerprints   0
@@ -61,19 +38,25 @@
 
 (s/defn ^:private fingerprint-table!
   [table :- i/TableInstance, fields :- [i/FieldInstance]]
-  (let [fields-to-sample (sample/sample-fields table fields)]
-    (reduce (fn [count-info [field sample]]
-              (if-not sample
-                (update count-info :no-data-fingerprints inc)
-                (let [result (sync-util/with-error-handling (format "Error generating fingerprint for %s"
-                                                                    (sync-util/name-for-logging field))
-                               (save-fingerprint! field (fingerprint field sample)))]
-                  (if (instance? Exception result)
-                    (update count-info :failed-fingerprints inc)
-                    (update count-info :updated-fingerprints inc)))))
-            (empty-stats-map (count fields-to-sample))
-            fields-to-sample)))
-
+  (transduce identity
+             (redux/post-complete
+              (f/fingerprint-fields fields)
+              (fn [fingerprints]
+                (reduce (fn [count-info [field fingerprint]]
+                          (cond
+                            (instance? Throwable fingerprint)
+                            (update count-info :failed-fingerprints inc)
+
+                            (some-> fingerprint :global :distinct-count zero?)
+                            (update count-info :no-data-fingerprints inc)
+
+                            :else
+                            (do
+                              (save-fingerprint! field fingerprint)
+                              (update count-info :updated-fingerprints inc))))
+                        (empty-stats-map (count fingerprints))
+                        (map vector fields fingerprints))))
+             (driver/table-rows-sample table fields)))
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                    WHICH FIELDS NEED UPDATED FINGERPRINTS?                                     |
@@ -86,6 +69,7 @@
 ;; SELECT *
 ;; FROM metabase_field
 ;; WHERE active = true
+;;   AND (special_type NOT IN ('type/PK') OR special_type IS NULL)
 ;;   AND preview_display = true
 ;;   AND visibility_type <> 'retired'
 ;;   AND table_id = 1
@@ -146,6 +130,9 @@
   ([]
    {:where [:and
             [:= :active true]
+            [:or
+             [:not (mdb/isa :special_type :type/PK)]
+             [:= :special_type nil]]
             [:not= :visibility_type "retired"]
             (cons :or (versions-clauses))]})
 
diff --git a/src/metabase/sync/analyze/fingerprint/datetime.clj b/src/metabase/sync/analyze/fingerprint/datetime.clj
deleted file mode 100644
index 6b2a6f448c95c6cfd6503f2b687eca5177c353e8..0000000000000000000000000000000000000000
--- a/src/metabase/sync/analyze/fingerprint/datetime.clj
+++ /dev/null
@@ -1,21 +0,0 @@
-(ns metabase.sync.analyze.fingerprint.datetime
-  "Logic for generating a `DateTimeFingerprint` from a sequence of values for a `:type/DateTime` Field."
-  (:require [clj-time
-             [coerce :as t.coerce]
-             [core :as t]]
-            [medley.core :as m]
-            [metabase.sync.interface :as i]
-            [metabase.util.date :as du]
-            [redux.core :as redux]
-            [schema.core :as s]))
-
-(s/defn datetime-fingerprint :- i/DateTimeFingerprint
-  "Generate a fingerprint containing information about values that belong to a `DateTime` Field."
-  [values :- i/FieldSample]
-  (transduce (map du/str->date-time)
-             (redux/post-complete
-              (redux/fuse {:earliest t/min-date
-                           :latest   t/max-date})
-              (partial m/map-vals str))
-             [(t.coerce/from-long Long/MAX_VALUE) (t.coerce/from-long 0)]
-             values))
diff --git a/src/metabase/sync/analyze/fingerprint/fingerprinters.clj b/src/metabase/sync/analyze/fingerprint/fingerprinters.clj
new file mode 100644
index 0000000000000000000000000000000000000000..b77744c26f933bef6506fcdca6300bc030262719
--- /dev/null
+++ b/src/metabase/sync/analyze/fingerprint/fingerprinters.clj
@@ -0,0 +1,198 @@
+(ns metabase.sync.analyze.fingerprint.fingerprinters
+  "Non-identifying fingerprinters for various field types."
+  (:require [cheshire.core :as json]
+            [clj-time.coerce :as t.coerce]
+            [kixi.stats.core :as stats]
+            [metabase.models.field :as field]
+            [metabase.sync.analyze.classifiers.name :as classify.name]
+            [metabase.sync.util :as sync-util]
+            [metabase.util :as u]
+            [metabase.util
+             [date :as du]
+             [i18n :refer [trs]]]
+            [redux.core :as redux])
+  (:import com.clearspring.analytics.stream.cardinality.HyperLogLogPlus
+           org.joda.time.DateTime))
+
+(defn col-wise
+  "Apply reducing functinons `rfs` coll-wise to a seq of seqs."
+  [& rfs]
+  (fn
+    ([] (mapv (fn [rf] (rf)) rfs))
+    ([accs] (mapv (fn [rf acc] (rf (unreduced acc))) rfs accs))
+    ([accs row]
+     (let [all-reduced? (volatile! true)
+           results      (mapv (fn [rf acc x]
+                                (if-not (reduced? acc)
+                                  (do (vreset! all-reduced? false)
+                                      (rf acc x))
+                                  acc))
+                              rfs accs row)]
+       (if @all-reduced?
+         (reduced results)
+         results)))))
+
+(defn constant-fingerprinter
+  "Constantly return `init`."
+  [init]
+  (fn
+    ([] (reduced init))
+    ([_] init)
+    ([_ _] (reduced init))))
+
+(defn- cardinality
+  "Transducer that sketches cardinality using HyperLogLog++.
+   https://research.google.com/pubs/pub40671.html"
+  ([] (HyperLogLogPlus. 14 25))
+  ([^HyperLogLogPlus acc] (.cardinality acc))
+  ([^HyperLogLogPlus acc x]
+   (.offer acc x)
+   acc))
+
+(defmulti
+  ^{:doc "Return a fingerprinter transducer for a given field based on the field's type."
+    :arglists '([field])}
+  fingerprinter (fn [{:keys [base_type special_type unit] :as field}]
+                  [(cond
+                     (du/date-extract-units unit)  :type/Integer
+                     (field/unix-timestamp? field) :type/DateTime
+                     :else                         base_type)
+                   (or special_type :type/*)]))
+
+(def ^:private global-fingerprinter
+  (redux/post-complete
+   (redux/fuse {:distinct-count cardinality})
+   (partial hash-map :global)))
+
+(defmethod fingerprinter :default
+  [_]
+  global-fingerprinter)
+
+(defmethod fingerprinter [:type/* :type/FK]
+  [_]
+  global-fingerprinter)
+
+(defmethod fingerprinter [:type/* :type/PK]
+  [_]
+  (constant-fingerprinter nil))
+
+(prefer-method fingerprinter [:type/* :type/FK] [:type/Number :type/*])
+(prefer-method fingerprinter [:type/* :type/FK] [:type/Text :type/*])
+(prefer-method fingerprinter [:type/* :type/PK] [:type/Number :type/*])
+(prefer-method fingerprinter [:type/* :type/PK] [:type/Text :type/*])
+(prefer-method fingerprinter [:type/DateTime :type/*] [:type/* :type/PK])
+
+(defn- with-global-fingerprinter
+  [fingerprinter]
+  (redux/post-complete
+   (redux/juxt
+    fingerprinter
+    global-fingerprinter)
+   (fn [[type-fingerprint global-fingerprint]]
+     (merge global-fingerprint
+            type-fingerprint))))
+
+(defmacro ^:private with-reduced-error
+  [msg & body]
+  `(let [result# (sync-util/with-error-handling ~msg ~@body)]
+     (if (instance? Throwable result#)
+       (reduced result#)
+       result#)))
+
+(defn- with-error-handling
+  [rf msg]
+  (fn
+    ([] (with-reduced-error msg (rf)))
+    ([acc]
+     (unreduced
+      (if (or (reduced? acc)
+              (instance? Throwable acc))
+        acc
+        (with-reduced-error msg (rf acc)))))
+    ([acc e] (with-reduced-error msg (rf acc e)))))
+
+(defmacro ^:private deffingerprinter
+  [field-type transducer]
+  (let [field-type (if (vector? field-type)
+                     field-type
+                     [field-type :type/*])]
+    `(defmethod fingerprinter ~field-type
+       [field#]
+       (with-error-handling
+         (with-global-fingerprinter
+           (redux/post-complete
+            ~transducer
+            (fn [fingerprint#]
+              {:type {~(first field-type) fingerprint#}})))
+         (str (trs "Error generating fingerprint for {0}" (sync-util/name-for-logging field#)))))))
+
+(defn- earliest
+  ([] (java.util.Date. Long/MAX_VALUE))
+  ([acc]
+   (when (not= acc (earliest))
+     (du/date->iso-8601 acc)))
+  ([^java.util.Date acc dt]
+   (if dt
+     (if (.before ^java.util.Date dt acc)
+       dt
+       acc)
+     acc)))
+
+(defn- latest
+  ([] (java.util.Date. 0))
+  ([acc]
+   (when (not= acc (latest))
+     (du/date->iso-8601 acc)))
+  ([^java.util.Date acc dt]
+   (if dt
+     (if (.after ^java.util.Date dt acc)
+       dt
+       acc)
+     acc)))
+
+(defprotocol ^:private IDateCoercible
+  "Protocol for converting objects to `java.util.Date`"
+  (->date ^java.util.Date [this]
+    "Coerce object to a `java.util.Date`."))
+
+(extend-protocol IDateCoercible
+  nil                    (->date [_] nil)
+  String                 (->date [this] (-> this du/str->date-time t.coerce/to-date))
+  java.util.Date         (->date [this] this)
+  DateTime               (->date [this] (t.coerce/to-date this))
+  Long                   (->date [^Long this] (java.util.Date. this)))
+
+(deffingerprinter :type/DateTime
+  ((map ->date)
+   (redux/fuse {:earliest earliest
+                :latest   latest})))
+
+(deffingerprinter :type/Number
+  (redux/fuse {:min stats/min
+               :max stats/max
+               :avg stats/mean}))
+
+(defn- valid-serialized-json?
+  "Is x a serialized JSON dictionary or array."
+  [x]
+  (u/ignore-exceptions
+    ((some-fn map? sequential?) (json/parse-string x))))
+
+(deffingerprinter :type/Text
+  ((map (comp str u/jdbc-clob->str)) ; we cast to str to support `field-literal` type overwriting:
+                                     ; `[:field-literal "A_NUMBER" :type/Text]` (which still
+                                     ; returns numbers in the result set)
+   (redux/fuse {:percent-json   (stats/share valid-serialized-json?)
+                :percent-url    (stats/share u/url?)
+                :percent-email  (stats/share u/email?)
+                :average-length ((map count) stats/mean)})))
+
+(defn fingerprint-fields
+  "Return a transducer for fingerprinting a resultset with fields `fields`."
+  [fields]
+  (apply col-wise (for [field fields]
+                    (fingerprinter
+                     (cond-> field
+                       ;; Try to get a better guestimate of what we're dealing with  on first sync
+                       (every? nil? ((juxt :special_type :last_analyzed) field))
+                       (assoc :special_type (classify.name/infer-special-type field)))))))
diff --git a/src/metabase/sync/analyze/fingerprint/global.clj b/src/metabase/sync/analyze/fingerprint/global.clj
deleted file mode 100644
index 81b1252df06bfc7e80203e58002f429da09d94d3..0000000000000000000000000000000000000000
--- a/src/metabase/sync/analyze/fingerprint/global.clj
+++ /dev/null
@@ -1,13 +0,0 @@
-(ns metabase.sync.analyze.fingerprint.global
-  "Logic for generating a `GlobalFingerprint` from a sequence of values for a Field of *any* type."
-  (:require [metabase.sync.interface :as i]
-            [schema.core :as s]))
-
-(s/defn global-fingerprint :- i/GlobalFingerprint
-  "Generate a fingerprint of global information for Fields of all types."
-  [values :- i/FieldSample]
-  ;; TODO - this logic isn't as nice as the old logic that actually called the DB
-  ;; We used to do (queries/field-distinct-count field field-values/auto-list-cardinality-threshold)
-  ;; Consider whether we are so married to the idea of only generating fingerprints from samples that we
-  ;; are ok with inaccurate counts like the one we'll surely be getting here
-  {:distinct-count (count (distinct values))})
diff --git a/src/metabase/sync/analyze/fingerprint/number.clj b/src/metabase/sync/analyze/fingerprint/number.clj
deleted file mode 100644
index dc85fa80b5e58184ea84b8dfae02589b73e24152..0000000000000000000000000000000000000000
--- a/src/metabase/sync/analyze/fingerprint/number.clj
+++ /dev/null
@@ -1,12 +0,0 @@
-(ns metabase.sync.analyze.fingerprint.number
-  "Logic for generating a `NumberFingerprint` from a sequence of values for a `:type/Number` Field."
-  (:require [kixi.stats.core :as stats]
-            [metabase.sync.interface :as i]
-            [schema.core :as s]))
-
-(s/defn number-fingerprint :- i/NumberFingerprint
-  "Generate a fingerprint containing information about values that belong to a `:type/Number` Field."
-  [values :- i/FieldSample]
-  {:min (apply min values)
-   :max (apply max values)
-   :avg (transduce (map double) stats/mean values)})
diff --git a/src/metabase/sync/analyze/fingerprint/sample.clj b/src/metabase/sync/analyze/fingerprint/sample.clj
deleted file mode 100644
index ed25fce6a0d770f8dd082cc5da080fb1da6d42d1..0000000000000000000000000000000000000000
--- a/src/metabase/sync/analyze/fingerprint/sample.clj
+++ /dev/null
@@ -1,32 +0,0 @@
-(ns metabase.sync.analyze.fingerprint.sample
-  "Analysis sub-step that fetches a sample of rows for a given Table and some set of Fields belonging to it, which is
-   used to generate fingerprints for those Fields. Currently this is dumb and just fetches a contiguous sequence of
-   rows, but in the future we plan to make this more sophisticated and have different types of samples for different
-   Fields, or do a better job getting a more-random sample of rows."
-  (:require [medley.core :as m]
-            [metabase.driver :as driver]
-            [metabase.sync.interface :as i]
-            [schema.core :as s]))
-
-(s/defn ^:private basic-sample :- (s/maybe i/TableSample)
-  "Procure a sequence of table rows, up to `max-sample-rows` (10,000 at the time of this writing), for
-   use in the fingerprinting sub-stage of analysis. Returns `nil` if no rows are available."
-  [table :- i/TableInstance, fields :- [i/FieldInstance]]
-  (seq (driver/table-rows-sample table fields)))
-
-(s/defn ^:private table-sample->field-sample :- (s/maybe i/FieldSample)
-  "Fetch a sample for the Field whose values are at INDEX in the TABLE-SAMPLE.
-   Filters out `nil` values; returns `nil` if a non-empty sample cannot be obtained."
-  [table-sample :- i/TableSample, i :- s/Int]
-  (->> (for [row table-sample]
-         (nth row i))
-       (filter (complement nil?))
-       seq))
-
-(s/defn sample-fields :- [(s/pair i/FieldInstance "Field", (s/maybe i/FieldSample) "FieldSample")]
-  "Fetch samples for a series of FIELDS. Returns tuples of Field and sample.
-   This may return `nil` if the sample could not be fetched for some other reason."
-  [table :- i/TableInstance, fields :- [i/FieldInstance]]
-  (when-let [table-sample (basic-sample table fields)]
-    (for [[i field] (m/indexed fields)]
-      [field (table-sample->field-sample table-sample i)])))
diff --git a/src/metabase/sync/analyze/fingerprint/text.clj b/src/metabase/sync/analyze/fingerprint/text.clj
deleted file mode 100644
index 767c429b8b2a8ef3c3c766d3388c339906069fb7..0000000000000000000000000000000000000000
--- a/src/metabase/sync/analyze/fingerprint/text.clj
+++ /dev/null
@@ -1,39 +0,0 @@
-(ns metabase.sync.analyze.fingerprint.text
-  "Logic for generating a `TextFingerprint` from a sequence of values for a `:type/Text` Field."
-  (:require [cheshire.core :as json]
-            [metabase.sync.interface :as i]
-            [metabase.util :as u]
-            [schema.core :as s]))
-
-(s/defn ^:private average-length :- (s/constrained Double #(>= % 0))
-  "Return the average length of VALUES."
-  [values :- i/FieldSample]
-  (let [total-length (reduce + (for [value values]
-                                 (count (str value))))]
-    (/ (double total-length)
-       (double (count values)))))
-
-(s/defn ^:private percent-satisfying-predicate :- i/Percent
-  "Return the percentage of VALUES that satisfy PRED."
-  [pred :- (s/pred fn?), values :- i/FieldSample]
-  (let [total-count    (count values)
-        pred           #(boolean (u/ignore-exceptions (pred %)))
-        matching-count (count (get (group-by pred values) true []))]
-    (/ (double matching-count)
-       (double total-count))))
-
-(defn- valid-serialized-json?
-  "True if X is a serialized JSON dictionary or array."
-  [x]
-  (boolean
-   (when-let [parsed-json (json/parse-string x)]
-     (or (map? parsed-json)
-         (sequential? parsed-json)))))
-
-(s/defn text-fingerprint :- i/TextFingerprint
-  "Generate a fingerprint containing information about values that belong to a `:type/Text` Field."
-  [values :- i/FieldSample]
-  {:percent-json   (percent-satisfying-predicate valid-serialized-json? values)
-   :percent-url    (percent-satisfying-predicate u/url? values)
-   :percent-email  (percent-satisfying-predicate u/email? values)
-   :average-length (average-length values)})
diff --git a/src/metabase/sync/analyze/query_results.clj b/src/metabase/sync/analyze/query_results.clj
new file mode 100644
index 0000000000000000000000000000000000000000..d8fbbcdea0e0a3c332db4aba63da9a2e6832dae6
--- /dev/null
+++ b/src/metabase/sync/analyze/query_results.clj
@@ -0,0 +1,88 @@
+(ns metabase.sync.analyze.query-results
+  "Analysis similar to what we do as part of the Sync process, but aimed at analyzing and introspecting query
+  results. The current focus of this namespace is around column metadata from the results of a query. Going forward
+  this is likely to extend beyond just metadata about columns but also about the query results as a whole and over
+  time."
+  (:require [clojure.string :as str]
+            [metabase.query-processor.interface :as qp.i]
+            [metabase.sync.analyze.classifiers.name :as classify-name]
+            [metabase.sync.analyze.fingerprint.fingerprinters :as f]
+            [metabase.sync.interface :as i]
+            [metabase.util :as u]
+            [metabase.util.schema :as su]
+            [redux.core :as redux]
+            [schema.core :as s]))
+
+(def ^:private DateTimeUnitKeywordOrString
+  "Schema for a valid datetime unit string like \"default\" or \"minute-of-hour\"."
+  (s/constrained su/KeywordOrString
+                 qp.i/datetime-field-unit?
+                 "Valid field datetime unit keyword or string"))
+
+(def ^:private ResultColumnMetadata
+  "Result metadata for a single column"
+  {:name                          su/NonBlankString
+   :display_name                  su/NonBlankString
+   (s/optional-key :description)  (s/maybe su/NonBlankString)
+   :base_type                     su/FieldTypeKeywordOrString
+   (s/optional-key :special_type) (s/maybe su/FieldTypeKeywordOrString)
+   (s/optional-key :unit)         (s/maybe DateTimeUnitKeywordOrString)
+   (s/optional-key :fingerprint)  (s/maybe i/Fingerprint)})
+
+(def ResultsMetadata
+  "Schema for valid values of the `result_metadata` column."
+  (su/with-api-error-message (s/named [ResultColumnMetadata]
+                                      "Valid array of results column metadata maps")
+    "value must be an array of valid results column metadata maps."))
+
+(s/defn ^:private maybe-infer-special-type :- ResultColumnMetadata
+  "Infer the special type and add it to the result metadata. If the inferred special type is nil, don't override the
+  special type with a nil special type"
+  [result-metadata col]
+  (update result-metadata :special_type (fn [original-value]
+                                          ;; If we already know the special type, becouse it is stored, don't classify again,
+                                          ;; but try to refine special type set upstream for aggregation cols (which come back as :type/Number).
+                                          (case original-value
+                                            (nil :type/Number) (classify-name/infer-special-type col)
+                                            original-value))))
+
+(s/defn ^:private stored-column-metadata->result-column-metadata :- ResultColumnMetadata
+  "The metadata in the column of our resultsets come from the metadata we store in the `Field` associated with the
+  column. It is cheapest and easiest to just use that. This function takes what it can from the column metadata to
+  populate the ResultColumnMetadata"
+  [column]
+  (merge
+   (u/select-non-nil-keys column [:name :display_name :description :base_type :special_type :unit :fingerprint])
+   ;; since years are actually returned as text they can't be used for breakout purposes so don't advertise them as DateTime columns
+   (when (= (:unit column) :year)
+     {:base_type :type/Text
+      :unit      nil})))
+
+(s/defn results->column-metadata :- ResultsMetadata
+  "Return the desired storage format for the column metadata coming back from RESULTS and fingerprint the RESULTS."
+  [results]
+  (let [result-metadata (for [col (:cols results)]
+                          ;; Rarely certain queries will return columns with no names. For example
+                          ;; `SELECT COUNT(*)` in SQL Server seems to come back with no name.
+                          ;; Since we can't use those as field literals in subsequent queries,
+                          ;; filter them out.
+                          (when-not (str/blank? (:name col))
+                            (-> col
+                                stored-column-metadata->result-column-metadata
+                                (maybe-infer-special-type col))))]
+    (transduce identity
+               (redux/post-complete
+                (apply f/col-wise (for [metadata result-metadata]
+                                    (if (and metadata
+                                             (nil? (:fingerprint metadata)))
+                                      (f/fingerprinter metadata)
+                                      (f/constant-fingerprinter (:fingerprint metadata)))))
+                (fn [fingerprints]
+                  (->> (map (fn [fingerprint metadata]
+                              (if (instance? Throwable fingerprint)
+                                metadata
+                                (some-> metadata (assoc :fingerprint fingerprint))))
+                            fingerprints
+                            result-metadata)
+                       (remove nil?))))
+               (:rows results))))
diff --git a/src/metabase/sync/field_values.clj b/src/metabase/sync/field_values.clj
index b27d6844879a4a42605545cb10443eec491384d0..24b3c6f8366951647e682d89f81708b475193423 100644
--- a/src/metabase/sync/field_values.clj
+++ b/src/metabase/sync/field_values.clj
@@ -8,7 +8,7 @@
              [interface :as i]
              [util :as sync-util]]
             [metabase.util :as u]
-            [puppetlabs.i18n.core :refer [trs]]
+            [metabase.util.i18n :refer [trs]]
             [schema.core :as s]
             [toucan.db :as db]))
 
diff --git a/src/metabase/sync/interface.clj b/src/metabase/sync/interface.clj
index 07a6a4e2be87000cd46b1fcfb50bf9b59832c593..a1802a678509e8c125fc8a3ce6e5eb824074572d 100644
--- a/src/metabase/sync/interface.clj
+++ b/src/metabase/sync/interface.clj
@@ -22,7 +22,7 @@
 (def TableMetadataField
   "Schema for a given Field as provided in `describe-table`."
   {:name                           su/NonBlankString
-   :database-type                  su/NonBlankString
+   :database-type                  (s/maybe su/NonBlankString) ; blank if the Field is all NULL & untyped, i.e. in Mongo
    :base-type                      su/FieldType
    (s/optional-key :special-type)  (s/maybe su/FieldType)
    (s/optional-key :pk?)           s/Bool
@@ -57,9 +57,10 @@
 ;; out from the ns declaration when running `cljr-clean-ns`. Plus as a bonus in the future we could add additional
 ;; validations to these, e.g. requiring that a Field have a base_type
 
-(def DatabaseInstance "Schema for a valid instance of a Metabase Database." (class Database))
-(def TableInstance    "Schema for a valid instance of a Metabase Table."    (class Table))
-(def FieldInstance    "Schema for a valid instance of a Metabase Field."    (class Field))
+(def DatabaseInstance             "Schema for a valid instance of a Metabase Database." (class Database))
+(def TableInstance                "Schema for a valid instance of a Metabase Table."    (class Table))
+(def FieldInstance                "Schema for a valid instance of a Metabase Field."    (class Field))
+(def ResultColumnMetadataInstance "Schema for a valid instance of a Metabase Field."    (class {}))
 
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
@@ -95,21 +96,21 @@
 
 (def NumberFingerprint
   "Schema for fingerprint information for Fields deriving from `:type/Number`."
-  {(s/optional-key :min) s/Num
-   (s/optional-key :max) s/Num
-   (s/optional-key :avg) s/Num})
+  {(s/optional-key :min) (s/maybe s/Num)
+   (s/optional-key :max) (s/maybe s/Num)
+   (s/optional-key :avg) (s/maybe s/Num)})
 
 (def TextFingerprint
   "Schema for fingerprint information for Fields deriving from `:type/Text`."
-  {(s/optional-key :percent-json)   Percent
-   (s/optional-key :percent-url)    Percent
-   (s/optional-key :percent-email)  Percent
-   (s/optional-key :average-length) (s/constrained Double #(>= % 0) "Valid number greater than or equal to zero")})
+  {(s/optional-key :percent-json)   (s/maybe Percent)
+   (s/optional-key :percent-url)    (s/maybe Percent)
+   (s/optional-key :percent-email)  (s/maybe Percent)
+   (s/optional-key :average-length) s/Num})
 
 (def DateTimeFingerprint
   "Schema for fingerprint information for Fields deriving from `:type/DateTime`."
-  {(s/optional-key :earliest) s/Str
-   (s/optional-key :latest)   s/Str})
+  {(s/optional-key :earliest) (s/maybe s/Str)
+   (s/optional-key :latest)   (s/maybe s/Str)})
 
 (def TypeSpecificFingerprint
   "Schema for type-specific fingerprint information."
@@ -156,7 +157,7 @@
   "Map of fingerprint version to the set of Field base types that need to be upgraded to this version the next
    time we do analysis. The highest-numbered entry is considered the latest version of fingerprints."
   {1 #{:type/*}
-   2 #{:type/DateTime}})
+   3 #{:type/DateTime}})
 
 (def latest-fingerprint-version
   "The newest (highest-numbered) version of our Field fingerprints."
diff --git a/src/metabase/sync/sync_metadata.clj b/src/metabase/sync/sync_metadata.clj
index ff191a50b30fffcfd1937864a103a0f34f652c09..a610f00fd21a6c8343a8568813a63528c56e2abe 100644
--- a/src/metabase/sync/sync_metadata.clj
+++ b/src/metabase/sync/sync_metadata.clj
@@ -15,7 +15,7 @@
              [metabase-metadata :as metabase-metadata]
              [sync-timezone :as sync-tz]
              [tables :as sync-tables]]
-            [puppetlabs.i18n.core :refer [trs]]
+            [metabase.util.i18n :refer [trs]]
             [schema.core :as s]))
 
 (defn- sync-fields-summary [{:keys [total-fields updated-fields] :as step-info}]
diff --git a/src/metabase/sync/sync_metadata/fields.clj b/src/metabase/sync/sync_metadata/fields.clj
index 1aa22346f0148869fb7d004508d4ff36e59251ff..eb7780b0aa13586ef4a7d18cfe015a16295c29fb 100644
--- a/src/metabase/sync/sync_metadata/fields.clj
+++ b/src/metabase/sync/sync_metadata/fields.clj
@@ -74,7 +74,7 @@
         {:table_id      (u/get-id table)
          :name          field-name
          :display_name  (humanization/name->human-readable-name field-name)
-         :database_type database-type
+         :database_type (or database-type "NULL") ; placeholder for Fields w/ no type info (e.g. Mongo) & all NULL
          :base_type     base-type
          :special_type  (special-type field)
          :parent_id     parent-id}))))
@@ -194,36 +194,38 @@
   (let [known-fields (u/key-by canonical-name our-metadata)]
     (+
      ;; Loop thru fields in DB-METADATA. Create/reactivate any fields that don't exist in OUR-METADATA.
-     (sync-util/sum-numbers (fn [db-field-chunk]
-                              (sync-util/with-error-handling (format "Error checking if Fields '%s' needs to be created or reactivated"
-                                                                     (pr-str (map :name db-field-chunk)))
-                                (let [known-field-pred    (comp known-fields canonical-name)
-                                      fields-to-update    (filter known-field-pred db-field-chunk)
-                                      new-fields          (remove known-field-pred db-field-chunk)
-                                      updated-chunk-count (sync-util/sum-numbers #(update-field-chunk! table known-fields %) fields-to-update)]
-
-                                  ;; otherwise if field doesn't exist, create or reactivate it
-                                  (when (seq new-fields)
-                                    (create-or-reactivate-field-chunk! table new-fields parent-id))
-                                  ;; Add the updated number of fields with the number of newly created fields
-                                  (+ updated-chunk-count (count new-fields)))))
-                            (partition-all 1000 db-metadata))
+     (sync-util/sum-numbers
+      (fn [db-field-chunk]
+        (sync-util/with-error-handling (format "Error checking if Fields '%s' needs to be created or reactivated"
+                                               (pr-str (map :name db-field-chunk)))
+          (let [known-field-pred    (comp known-fields canonical-name)
+                fields-to-update    (filter known-field-pred db-field-chunk)
+                new-fields          (remove known-field-pred db-field-chunk)
+                updated-chunk-count (sync-util/sum-numbers #(update-field-chunk! table known-fields %) fields-to-update)]
+
+            ;; otherwise if field doesn't exist, create or reactivate it
+            (when (seq new-fields)
+              (create-or-reactivate-field-chunk! table new-fields parent-id))
+            ;; Add the updated number of fields with the number of newly created fields
+            (+ updated-chunk-count (count new-fields)))))
+      (partition-all 1000 db-metadata))
 
      ;; ok, loop thru Fields in OUR-METADATA. Mark Fields as inactive if they don't exist in DB-METADATA.
-     (sync-util/sum-numbers (fn [our-field]
-                              (sync-util/with-error-handling (format "Error checking if '%s' needs to be retired" (:name our-field))
-                                (if-let [db-field (matching-field-metadata our-field db-metadata)]
-                                  ;; if field exists in both metadata sets we just need to recursively check the nested fields
-                                  (if-let [our-nested-fields (seq (:nested-fields our-field))]
-                                    (sync-field-instances! table (:nested-fields db-field) (set our-nested-fields) (:id our-field))
-                                    ;; No fields were updated
-                                    0)
-                                  ;; otherwise if field exists in our metadata but not DB metadata time to make it inactive
-                                  (do
-                                    (retire-field! table our-field)
-                                    ;; 1 field was updated (retired)
-                                    1))))
-                            our-metadata))))
+     (sync-util/sum-numbers
+      (fn [our-field]
+        (sync-util/with-error-handling (format "Error checking if '%s' needs to be retired" (:name our-field))
+          (if-let [db-field (matching-field-metadata our-field db-metadata)]
+            ;; if field exists in both metadata sets we just need to recursively check the nested fields
+            (if-let [our-nested-fields (seq (:nested-fields our-field))]
+              (sync-field-instances! table (:nested-fields db-field) (set our-nested-fields) (:id our-field))
+              ;; No fields were updated
+              0)
+            ;; otherwise if field exists in our metadata but not DB metadata time to make it inactive
+            (do
+              (retire-field! table our-field)
+              ;; 1 field was updated (retired)
+              1))))
+      our-metadata))))
 
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
@@ -239,32 +241,34 @@
                                   :parent_id parent-id)
         field-name->db-metadata (u/key-by canonical-name db-metadata)]
     ;; Make sure special types are up-to-date for all the fields
-    (sync-util/sum-numbers (fn [field]
-                             (let [db-field         (get field-name->db-metadata (canonical-name field))
-                                   new-special-type (special-type db-field)]
-                               (if (and db-field
-                                        (or
-                                         ;; If the base_type has changed, we need to updated it
-                                         (not= (:base_type field) (:base-type db-field))
-                                         ;; If the base_type hasn't changed, but we now have a special_type, we should update
-                                         ;; it. We should not overwrite a special_type that is already present (could have
-                                         ;; been specified by the user).
-                                         (and (not (:special_type field)) new-special-type)))
-                                 (do
-                                   ;; update special type if one came back from DB metadata but Field doesn't currently have one
-                                   (db/update! Field (u/get-id field)
-                                     (merge {:base_type (:base-type db-field)}
-                                            (when-not (:special_type field)
-                                              {:special_type new-special-type})))
-                                   ;; now recursively do the same for any nested fields
-                                   (if-let [db-nested-fields (seq (:nested-fields db-field))]
-                                     ;; This field was updated + any nested fields
-                                     (+ 1 (update-metadata! table (set db-nested-fields) (u/get-id field)))
-                                     ;; No nested fields, so just this field was updated
-                                     1))
-                                 ;; The field was not updated
-                                 0)))
-                           existing-fields)))
+    (sync-util/sum-numbers
+     (fn [field]
+       (let [db-field         (get field-name->db-metadata (canonical-name field))
+             new-special-type (special-type db-field)]
+         (if (and db-field
+                  (or
+                   ;; If the base_type has changed, we need to updated it
+                   (not= (:base_type field) (:base-type db-field))
+                   ;; If the base_type hasn't changed, but we now have a special_type, we should
+                   ;; update it. We should not overwrite a special_type that is already present
+                   ;; (could have been specified by the user).
+                   (and (not (:special_type field)) new-special-type)))
+           (do
+             ;; update special type if one came back from DB metadata but Field doesn't
+             ;; currently have one
+             (db/update! Field (u/get-id field)
+               (merge {:base_type (:base-type db-field)}
+                      (when-not (:special_type field)
+                        {:special_type new-special-type})))
+             ;; now recursively do the same for any nested fields
+             (if-let [db-nested-fields (seq (:nested-fields db-field))]
+               ;; This field was updated + any nested fields
+               (+ 1 (update-metadata! table (set db-nested-fields) (u/get-id field)))
+               ;; No nested fields, so just this field was updated
+               1))
+           ;; The field was not updated
+           0)))
+     existing-fields)))
 
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
diff --git a/src/metabase/sync/util.clj b/src/metabase/sync/util.clj
index 54928f17a692d10ff3e738c5af2bf501ad4c2bb5..40d9a4796cd9c23305ab45ce518cd6fa97843f76 100644
--- a/src/metabase/sync/util.clj
+++ b/src/metabase/sync/util.clj
@@ -13,15 +13,20 @@
              [driver :as driver]
              [events :as events]
              [util :as u]]
-            [metabase.models.table :refer [Table]]
+            [metabase.models
+             [table :refer [Table]]
+             [task-history :refer [TaskHistory]]]
             [metabase.query-processor.interface :as qpi]
             [metabase.sync.interface :as i]
-            [metabase.util.date :as du]
-            [puppetlabs.i18n.core :refer [trs]]
+            [metabase.util
+             [date :as du]
+             [i18n :refer [trs]]
+             [schema :as su]]
             [ring.util.codec :as codec]
             [schema.core :as s]
             [taoensso.nippy :as nippy]
-            [toucan.db :as db]))
+            [toucan.db :as db])
+  (:import org.joda.time.DateTime))
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                          SYNC OPERATION "MIDDLEWARE"                                           |
@@ -146,7 +151,8 @@
                        message
                        (or (.getMessage e) (class e))
                        (u/pprint-to-str (or (seq (u/filtered-stacktrace e))
-                                            (.getStackTrace e)))))))))
+                                            (.getStackTrace e)))))
+          e))))
 
 (defmacro with-error-handling
   "Execute BODY in a way that catches and logs any Exceptions thrown, and returns `nil` if they do so.
@@ -256,15 +262,19 @@
 (extend-protocol INameForLogging
   i/DatabaseInstance
   (name-for-logging [{database-name :name, id :id, engine :engine,}]
-    (format "%s Database %s '%s'" (name engine) (or id "") database-name))
+    (str (trs "{0} Database {1} ''{2}''" (name engine) (or id "") database-name)))
 
   i/TableInstance
   (name-for-logging [{schema :schema, id :id, table-name :name}]
-    (format "Table %s '%s'" (or id "") (str (when (seq schema) (str schema ".")) table-name)))
+    (str (trs "Table {0} ''{1}''" (or id "") (str (when (seq schema) (str schema ".")) table-name))))
 
   i/FieldInstance
   (name-for-logging [{field-name :name, id :id}]
-    (format "Field %s '%s'" (or id "") field-name)))
+    (str (trs "Field {0} ''{1}''" (or id "") field-name)))
+
+  i/ResultColumnMetadataInstance
+  (name-for-logging [{field-name :name}]
+    (str (trs "Field ''{0}''" field-name))))
 
 (defn calculate-hash
   "Calculate a cryptographic hash on `clj-data` and return that hash as a string"
@@ -276,11 +286,11 @@
        ;; Convert the hash bytes to a string for storage/comparison with the hash in the database
        codec/base64-encode))
 
-(s/defn calculate-duration :- s/Str
+(s/defn calculate-duration-str :- s/Str
   "Given two datetimes, caculate the time between them, return the result as a string"
   [begin-time :- (s/protocol tcoerce/ICoerce)
    end-time :- (s/protocol tcoerce/ICoerce)]
-  (-> (- (tcoerce/to-long end-time) (tcoerce/to-long begin-time))
+  (-> (du/calculate-duration begin-time end-time)
       ;; Millis -> Nanos
       (* 1000000)
       du/format-nanoseconds))
@@ -292,9 +302,8 @@
 
 (def ^:private TimedSyncMetadata
   "Metadata common to both sync steps and an entire sync/analyze operation run"
-  {:start-time     s/Str
-   :end-time       s/Str
-   :duration       s/Str})
+  {:start-time DateTime
+   :end-time   DateTime})
 
 (def StepRunMetadata
   "Map with metadata about the step. Contains both generic information like `start-time` and `end-time` and step
@@ -327,9 +336,9 @@
   ([step-name sync-fn]
    (create-sync-step step-name sync-fn nil))
   ([step-name sync-fn log-summary-fn]
-   {:sync-fn sync-fn
-    :step-name step-name
-    :log-summary-fn log-summary-fn}))
+   {:sync-fn        sync-fn
+    :step-name      step-name
+    :log-summary-fn (comp str log-summary-fn)}))
 
 (defn- datetime->str [datetime]
   (du/->iso-8601-datetime datetime "UTC"))
@@ -339,50 +348,78 @@
   [database :- i/DatabaseInstance
    {:keys [step-name sync-fn log-summary-fn] :as step} :- StepDefinition]
   (let [start-time (time/now)
-        results    (with-start-and-finish-debug-logging (trs "step ''{0}'' for {1}"
-                                                             step-name
-                                                             (name-for-logging database))
+        results    (with-start-and-finish-debug-logging (str (trs "step ''{0}'' for {1}"
+                                                                  step-name
+                                                                  (name-for-logging database)))
                      #(sync-fn database))
         end-time   (time/now)]
     [step-name (assoc results
-                 :start-time (datetime->str start-time)
-                 :end-time (datetime->str end-time)
-                 :duration (calculate-duration start-time end-time)
+                 :start-time start-time
+                 :end-time end-time
                  :log-summary-fn log-summary-fn)]))
 
 (s/defn ^:private  log-sync-summary
   "Log a sync/analyze summary message with info from each step"
   [operation :- s/Str
    database :- i/DatabaseInstance
-   {:keys [start-time end-time duration steps log-summary-fn]} :- SyncOperationMetadata]
+   {:keys [start-time end-time steps log-summary-fn]} :- SyncOperationMetadata]
   ;; Note this needs to either stay nested in the `debug` macro call or be guarded by an log/enabled?
   ;; call. Constructing the log below requires some work, no need to incur that cost debug logging isn't enabled
   (log/debug
    (str
-    (format
+    (apply format
      (str "\n#################################################################\n"
           "# %s\n"
           "# %s\n"
           "# %s\n"
           "# %s\n")
-     (trs "Completed {0} on {1}" operation (:name database))
-     (trs "Start: {0}" start-time)
-     (trs "End: {0}" end-time)
-     (trs "Duration: {0}" duration))
+     (map str [(trs "Completed {0} on {1}" operation (:name database))
+               (trs "Start: {0}" (datetime->str start-time))
+               (trs "End: {0}" (datetime->str end-time))
+               (trs "Duration: {0}" (calculate-duration-str start-time end-time))]))
     (apply str (for [[step-name {:keys [start-time end-time duration log-summary-fn] :as step-info}] steps]
-                 (format (str "# ---------------------------------------------------------------\n"
+                 (apply format (str "# ---------------------------------------------------------------\n"
                               "# %s\n"
                               "# %s\n"
                               "# %s\n"
                               "# %s\n"
                               (when log-summary-fn
                                 (format "# %s\n" (log-summary-fn step-info))))
-                         (trs "Completed step ''{0}''" step-name)
-                         (trs "Start: {0}" start-time)
-                         (trs "End: {0}" end-time)
-                         (trs "Duration: {0}" duration))))
+                        (map str [(trs "Completed step ''{0}''" step-name)
+                                  (trs "Start: {0}" (datetime->str start-time))
+                                  (trs "End: {0}" (datetime->str end-time))
+                                  (trs "Duration: {0}" (calculate-duration-str start-time end-time))]))))
     "#################################################################\n")))
 
+(def ^:private SyncOperationOrStepRunMetadata
+  (s/conditional
+   #(contains? % :steps)
+   SyncOperationMetadata
+   :else
+   StepRunMetadata))
+
+(s/defn ^:private create-task-history
+  [task-name :- su/NonBlankString
+   database  :- i/DatabaseInstance
+   {:keys [start-time end-time]} :- SyncOperationOrStepRunMetadata]
+  {:task task-name
+   :db_id (u/get-id database)
+   :started_at (du/->Timestamp start-time)
+   :ended_at (du/->Timestamp end-time)
+   :duration (du/calculate-duration start-time end-time)})
+
+(s/defn ^:private store-sync-summary!
+  [operation :- s/Str
+   database  :- i/DatabaseInstance
+   {:keys [steps] :as sync-md} :- SyncOperationMetadata]
+  (db/insert-many! TaskHistory
+    (cons (create-task-history operation database sync-md)
+          (for [[step-name step-info] steps
+                :let [task-details (dissoc step-info :start-time :end-time :log-summary-fn)]]
+            (assoc (create-task-history step-name database step-info)
+              :task_details (when (seq task-details)
+                              task-details))))))
+
 (s/defn run-sync-operation
   "Run `sync-steps` and log a summary message"
   [operation :- s/Str
@@ -390,16 +427,17 @@
    sync-steps :- [StepDefinition]]
   (let [start-time    (time/now)
         step-metadata (mapv #(run-step-with-metadata database %) sync-steps)
-        end-time      (time/now)]
-    (log-sync-summary operation database {:start-time (datetime->str start-time)
-                                          :end-time   (datetime->str end-time)
-                                          :duration   (calculate-duration start-time end-time)
-                                          :steps      step-metadata})))
+        end-time      (time/now)
+        sync-metadata {:start-time start-time
+                       :end-time   end-time
+                       :steps      step-metadata}]
+    (store-sync-summary! operation database sync-metadata)
+    (log-sync-summary operation database sync-metadata)))
 
 (defn sum-numbers
   "Similar to a 2-arg call to `map`, but will add all numbers that result from the invocations of `f`"
   [f coll]
-  (apply + (for [item coll
-                 :let [result (f item)]
-                 :when (number? result)]
-             result)))
+  (reduce + (for [item coll
+                  :let [result (f item)]
+                  :when (number? result)]
+              result)))
diff --git a/src/metabase/task.clj b/src/metabase/task.clj
index 0302afb23563a0b2665607d92f23a6ed310a117a..2bb14b8dc1d1b328f12e931e099abbf49c785059 100644
--- a/src/metabase/task.clj
+++ b/src/metabase/task.clj
@@ -1,57 +1,80 @@
 (ns metabase.task
   "Background task scheduling via Quartzite. Individual tasks are defined in `metabase.task.*`.
 
-   ## Regarding Task Initialization:
+  ## Regarding Task Initialization:
 
-   The most appropriate way to initialize tasks in any `metabase.task.*` namespace is to implement the
-   `task-init` function which accepts zero arguments. This function is dynamically resolved and called
-   exactly once when the application goes through normal startup procedures. Inside this function you
-   can do any work needed and add your task to the scheduler as usual via `schedule-task!`."
+  The most appropriate way to initialize tasks in any `metabase.task.*` namespace is to implement the `task-init`
+  function which accepts zero arguments. This function is dynamically resolved and called exactly once when the
+  application goes through normal startup procedures. Inside this function you can do any work needed and add your
+  task to the scheduler as usual via `schedule-task!`."
   (:require [clojure.tools.logging :as log]
             [clojurewerkz.quartzite.scheduler :as qs]
-            [metabase.util :as u]
+            [metabase
+             [db :as mdb]
+             [util :as u]]
+            [metabase.util.i18n :refer [trs]]
             [schema.core :as s])
   (:import [org.quartz JobDetail JobKey Scheduler Trigger TriggerKey]))
 
-;;; +------------------------------------------------------------------------------------------------------------------------+
-;;; |                                                   SCHEDULER INSTANCE                                                   |
-;;; +------------------------------------------------------------------------------------------------------------------------+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                               SCHEDULER INSTANCE                                               |
+;;; +----------------------------------------------------------------------------------------------------------------+
 
 (defonce ^:private quartz-scheduler
   (atom nil))
 
 (defn- scheduler
-  "Fetch the instance of our Quartz scheduler. Call this function rather than dereffing the atom directly
-   because there are a few places (e.g., in tests) where we swap the instance out."
+  "Fetch the instance of our Quartz scheduler. Call this function rather than dereffing the atom directly because there
+  are a few places (e.g., in tests) where we swap the instance out."
+  ;; TODO - why can't we just swap the atom out in the tests?
   ^Scheduler []
   @quartz-scheduler)
 
 
-;;; +------------------------------------------------------------------------------------------------------------------------+
-;;; |                                                FINDING & LOADING TASKS                                                 |
-;;; +------------------------------------------------------------------------------------------------------------------------+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                            FINDING & LOADING TASKS                                             |
+;;; +----------------------------------------------------------------------------------------------------------------+
 
 (defn- find-and-load-tasks!
   "Search Classpath for namespaces that start with `metabase.tasks.`, then `require` them so initialization can happen."
   []
   (doseq [ns-symb @u/metabase-namespace-symbols
           :when   (.startsWith (name ns-symb) "metabase.task.")]
-    (log/info "Loading tasks namespace:" (u/format-color 'blue ns-symb) (u/emoji "📆"))
+    (log/info (trs "Loading tasks namespace:") (u/format-color 'blue ns-symb) (u/emoji "📆"))
     (require ns-symb)
     ;; look for `task-init` function in the namespace and call it if it exists
     (when-let [init-fn (ns-resolve ns-symb 'task-init)]
       (init-fn))))
 
 
-;;; +------------------------------------------------------------------------------------------------------------------------+
-;;; |                                              STARTING/STOPPING SCHEDULER                                               |
-;;; +------------------------------------------------------------------------------------------------------------------------+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                          STARTING/STOPPING SCHEDULER                                           |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+(defn- set-jdbc-backend-properties!
+  "Set the appropriate system properties needed so Quartz can connect to the JDBC backend. (Since we don't know our DB
+  connection properties ahead of time, we'll need to set these at runtime rather than Setting them in the
+  `quartz.properties` file.)"
+  []
+  (let [{:keys [classname user password subname subprotocol type]} (mdb/jdbc-details)]
+    ;; If we're using a Postgres application DB the driverDelegateClass has to be the Postgres-specific one rather
+    ;; than the Standard JDBC one we define in `quartz.properties`
+    (when (= type :postgres)
+      (System/setProperty "org.quartz.jobStore.driverDelegateClass" "org.quartz.impl.jdbcjobstore.PostgreSQLDelegate"))
+    ;; set other properties like URL, user, and password so Quartz knows how to connect
+    (doseq [[k, ^String v] {:driver   classname
+                            :URL      (str "jdbc:" subprotocol \: subname)
+                            :user     user
+                            :password password}]
+      (when v
+        (System/setProperty (str "org.quartz.dataSource.db." (name k)) v)))))
 
 (defn start-scheduler!
   "Start our Quartzite scheduler which allows jobs to be submitted and triggers to begin executing."
   []
   (when-not @quartz-scheduler
-    (log/debug "Starting Quartz Scheduler")
+    (set-jdbc-backend-properties!)
+    (log/debug (trs "Starting Quartz Scheduler"))
     ;; keep a reference to our scheduler
     (reset! quartz-scheduler (qs/start (qs/initialize)))
     ;; look for job/trigger definitions
@@ -60,7 +83,7 @@
 (defn stop-scheduler!
   "Stop our Quartzite scheduler and shutdown any running executions."
   []
-  (log/debug "Stopping Quartz Scheduler")
+  (log/debug (trs "Stopping Quartz Scheduler"))
   ;; tell quartz to stop everything
   (when-let [scheduler (scheduler)]
     (qs/shutdown scheduler))
@@ -68,15 +91,18 @@
   (reset! quartz-scheduler nil))
 
 
-;;; +------------------------------------------------------------------------------------------------------------------------+
-;;; |                                               SCHEDULING/DELETING TASKS                                                |
-;;; +------------------------------------------------------------------------------------------------------------------------+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                           SCHEDULING/DELETING TASKS                                            |
+;;; +----------------------------------------------------------------------------------------------------------------+
 
 (s/defn schedule-task!
   "Add a given job and trigger to our scheduler."
   [job :- JobDetail, trigger :- Trigger]
   (when-let [scheduler (scheduler)]
-    (qs/schedule scheduler job trigger)))
+    (try
+      (qs/schedule scheduler job trigger)
+      (catch org.quartz.ObjectAlreadyExistsException _
+        (log/info (trs "Job already exists:") (-> job .getKey .getName))))))
 
 (s/defn delete-task!
   "delete a task from the scheduler"
diff --git a/src/metabase/task/DynamicClassLoadHelper.clj b/src/metabase/task/DynamicClassLoadHelper.clj
new file mode 100644
index 0000000000000000000000000000000000000000..612f211c2430f9550eede1f9ed99d6487ea1a733
--- /dev/null
+++ b/src/metabase/task/DynamicClassLoadHelper.clj
@@ -0,0 +1,54 @@
+(ns metabase.task.DynamicClassLoadHelper
+  "This is needed to get the JDBC backend for Quartz working, or something like that. See
+  http://clojurequartz.info/articles/durable_quartz_stores.html for details."
+  (:require [clojure.string :as str])
+  (:gen-class
+   :extends clojure.lang.DynamicClassLoader
+   :exposes-methods {loadClass superLoadClass}
+   :implements [org.quartz.spi.ClassLoadHelper]))
+
+;; docstrings are copies of the ones for the corresponding methods of the ClassLoadHelper interface
+
+(defn -initialize
+  "void initialize()
+
+  Called to give the ClassLoadHelper a chance to initialize itself, including the opportunity to \"steal\" the class
+  loader off of the calling thread, which is the thread that is initializing Quartz."
+  [_])
+
+(defn- task-class-name->namespace-str
+  "Determine the namespace we need to load for one of our tasks.
+
+    (task-class-name->namespace-str \"metabase.task.upgrade_checks.CheckForNewVersions\")
+    ;; -> \"metabase.task.upgrade-checks\""
+  [class-name]
+  (-> class-name
+      (str/replace \_ \-)
+      (str/replace #"\.\w+$" "")))
+
+(defn- require-task-namespace
+  "Since Metabase tasks are defined in Clojure-land we need to make sure we `require` the namespaces where they are
+  defined before we try to load the task classes."
+  [class-name]
+  ;; only try to `require` metabase.task classes; don't do this for other stuff that gets shuffled thru here like
+  ;; Quartz classes
+  (when (str/starts-with? class-name "metabase.task.")
+    (require (symbol (task-class-name->namespace-str class-name)))))
+
+(defn -loadClass
+  "Class loadClass(String className)
+
+  Return the class with the given name."
+  ([^metabase.task.DynamicClassLoadHelper this, ^String class-name]
+   (require-task-namespace class-name)
+   (.superLoadClass this class-name true)) ; loadClass(String name, boolean resolve)
+  ([^metabase.task.DynamicClassLoadHelper this, ^String class-name, _]
+   (require-task-namespace class-name)
+   (.superLoadClass this class-name true)))
+
+(defn -getClassLoader
+  "ClassLoader getClassLoader()
+
+  Enable sharing of the class-loader with 3rd party"
+  [this]
+  this)
diff --git a/src/metabase/task/send_pulses.clj b/src/metabase/task/send_pulses.clj
index b8765ce3c771a44c2255baa91970e0d279289f12..1473ab1aa3e8953be1ab7d8cd36764916a592e60 100644
--- a/src/metabase/task/send_pulses.clj
+++ b/src/metabase/task/send_pulses.clj
@@ -63,26 +63,25 @@
   []
   ;; build our job
   (reset! send-pulses-job (jobs/build
-                               (jobs/of-type SendPulses)
-                               (jobs/with-identity (jobs/key send-pulses-job-key))))
+                           (jobs/of-type SendPulses)
+                           (jobs/with-identity (jobs/key send-pulses-job-key))))
   ;; build our trigger
   (reset! send-pulses-trigger (triggers/build
-                                   (triggers/with-identity (triggers/key send-pulses-trigger-key))
-                                   (triggers/start-now)
-                                   (triggers/with-schedule
-                                     ;; run at the top of every hour
-                                     (cron/cron-schedule "0 0 * * * ? *"))))
+                               (triggers/with-identity (triggers/key send-pulses-trigger-key))
+                               (triggers/start-now)
+                               (triggers/with-schedule
+                                 ;; run at the top of every hour
+                                 (cron/cron-schedule "0 0 * * * ? *"))))
   ;; submit ourselves to the scheduler
   (task/schedule-task! @send-pulses-job @send-pulses-trigger))
 
 
-;;; ## ---------------------------------------- PULSE SENDING ----------------------------------------
-
+;;; ------------------------------------------------- PULSE SENDING --------------------------------------------------
 
 (defn- send-pulses!
-  "Send any `Pulses` which are scheduled to run in the current day/hour.  We use the current time and determine the
-   hour of the day and day of the week according to the defined reporting timezone, or UTC.  We then find all `Pulses`
-   that are scheduled to run and send them."
+  "Send any `Pulses` which are scheduled to run in the current day/hour. We use the current time and determine the hour
+  of the day and day of the week according to the defined reporting timezone, or UTC. We then find all `Pulses` that
+  are scheduled to run and send them."
   [hour weekday monthday monthweek]
   {:pre [(integer? hour)
          (and (<= 0 hour) (>= 23 hour))
@@ -93,7 +92,7 @@
     (doseq [pulse-id (keys channels-by-pulse)]
       (try
         (log/debug (format "Starting Pulse Execution: %d" pulse-id))
-        (when-let [pulse (pulse/retrieve-notification pulse-id)]
+        (when-let [pulse (pulse/retrieve-notification pulse-id :archived false)]
           (p/send-pulse! pulse :channel-ids (mapv :id (get channels-by-pulse pulse-id))))
         (log/debug (format "Finished Pulse Execution: %d" pulse-id))
         (catch Throwable e
diff --git a/src/metabase/task/sync_databases.clj b/src/metabase/task/sync_databases.clj
index 3c1394e53f02721e5bb1f310c21442b22a948815..234ec36cd489c423f2e382259a54b4d54b37df8a 100644
--- a/src/metabase/task/sync_databases.clj
+++ b/src/metabase/task/sync_databases.clj
@@ -25,7 +25,7 @@
 ;;; +----------------------------------------------------------------------------------------------------------------+
 
 (s/defn ^:private job-context->database :- DatabaseInstance
-  "Get the Database referred to in JOB-CONTEXT. Guaranteed to return a valid Database."
+  "Get the Database referred to in `job-context`. Guaranteed to return a valid Database."
   [job-context]
   (Database (u/get-id (get (qc/from-job-data job-context) "db-id"))))
 
@@ -68,27 +68,27 @@
 ;; using them
 
 (s/defn ^:private job-key :- JobKey
-  "Return an appropriate string key for the job described by TASK-INFO for DATABASE-OR-ID."
+  "Return an appropriate string key for the job described by `task-info` for `database-or-id`."
   [task-info :- TaskInfo]
   (jobs/key (format "metabase.task.%s.job" (name (:key task-info)))))
 
 (s/defn ^:private trigger-key :- TriggerKey
-  "Return an appropriate string key for the trigger for TASK-INFO and DATABASE-OR-ID."
+  "Return an appropriate string key for the trigger for `task-info` and `database-or-id`."
   [database :- DatabaseInstance, task-info :- TaskInfo]
   (triggers/key (format "metabase.task.%s.trigger.%d" (name (:key task-info)) (u/get-id database))))
 
 (s/defn ^:private cron-schedule :- cron-util/CronScheduleString
-  "Fetch the appropriate cron schedule string for DATABASE and TASK-INFO."
+  "Fetch the appropriate cron schedule string for `database` and `task-info`."
   [database :- DatabaseInstance, task-info :- TaskInfo]
   (get database (:db-schedule-column task-info)))
 
 (s/defn ^:private job-class :- Class
-  "Get the Job class for TASK-INFO."
+  "Get the Job class for `task-info`."
   [task-info :- TaskInfo]
   (:job-class task-info))
 
 (s/defn ^:private trigger-description :- s/Str
-  "Return an appropriate description string for a job/trigger for Database described by TASK-INFO."
+  "Return an appropriate description string for a job/trigger for Database described by `task-info`."
   [database :- DatabaseInstance, task-info :- TaskInfo]
   (format "%s Database %d" (name (:key task-info)) (u/get-id database)))
 
@@ -103,7 +103,7 @@
 ;;; +----------------------------------------------------------------------------------------------------------------+
 
 (s/defn ^:private delete-task!
-  "Cancel a single sync task for DATABASE-OR-ID and TASK-INFO."
+  "Cancel a single sync task for `database-or-id` and `task-info`."
   [database :- DatabaseInstance, task-info :- TaskInfo]
   (let [trigger-key (trigger-key database task-info)]
     (log/debug (u/format-color 'red "Unscheduling task for Database %d: trigger: %s"
@@ -111,7 +111,7 @@
     (task/delete-trigger! trigger-key)))
 
 (s/defn unschedule-tasks-for-db!
-  "Cancel *all* scheduled sync and FieldValues caching tassks for DATABASE-OR-ID."
+  "Cancel *all* scheduled sync and FieldValues caching tasks for `database-or-id`."
   [database :- DatabaseInstance]
   (delete-task! database sync-analyze-task-info)
   (delete-task! database field-values-task-info))
@@ -122,7 +122,7 @@
 ;;; +----------------------------------------------------------------------------------------------------------------+
 
 (s/defn ^:private job :- JobDetail
-  "Build a durable Quartz Job for TASK-INFO. Durable in Quartz allows the job to exist even if there are no triggers
+  "Build a durable Quartz Job for `task-info`. Durable in Quartz allows the job to exist even if there are no triggers
   for it."
   [task-info :- TaskInfo]
   (jobs/build
@@ -135,7 +135,7 @@
 (s/def ^:private field-values-job (job field-values-task-info))
 
 (s/defn ^:private trigger :- CronTrigger
-  "Build a Quartz Trigger for DATABASE and TASK-INFO."
+  "Build a Quartz Trigger for `database` and `task-info`."
   [database :- DatabaseInstance, task-info :- TaskInfo]
   (triggers/build
    (triggers/with-description (trigger-description database task-info))
@@ -153,7 +153,7 @@
       (cron/with-misfire-handling-instruction-fire-and-proceed)))))
 
 (s/defn ^:private schedule-tasks-for-db!
-  "Schedule a new Quartz job for DATABASE and TASK-INFO."
+  "Schedule a new Quartz job for `database` and `task-info`."
   [database :- DatabaseInstance]
   (let [sync-trigger (trigger database sync-analyze-task-info)
         fv-trigger   (trigger database field-values-task-info)]
diff --git a/src/metabase/task/task_history_cleanup.clj b/src/metabase/task/task_history_cleanup.clj
new file mode 100644
index 0000000000000000000000000000000000000000..fc5de21be383e2a4c4f96010b6545d9cd35ed5ba
--- /dev/null
+++ b/src/metabase/task/task_history_cleanup.clj
@@ -0,0 +1,58 @@
+(ns metabase.task.task-history-cleanup
+  (:require [clj-time.core :as time]
+            [clojure.tools.logging :as log]
+            [clojurewerkz.quartzite
+             [jobs :as jobs]
+             [triggers :as triggers]]
+            [clojurewerkz.quartzite.schedule.cron :as cron]
+            [metabase.models.task-history :as thist :refer [TaskHistory]]
+            [metabase.task :as task]
+            [metabase.util.date :as du]
+            [puppetlabs.i18n.core :refer [trs]]
+            [toucan.db :as db]))
+
+(def ^:private ^:const job-name    "task-history-cleanup")
+(def ^:private ^:const job-key     (format "metabase.task.%s.job" job-name))
+(def ^:private ^:const trigger-key (format "metabase.task.%s.trigger" job-name))
+
+(defonce ^:private job     (atom nil))
+(defonce ^:private trigger (atom nil))
+
+(def ^:private history-rows-to-keep
+  "Maximum number of TaskHistory rows. This is not a `const` so that we can redef it in tests"
+  100000)
+
+(defn- task-history-cleanup!
+  []
+  (log/debug "Cleaning up task history")
+  (let [before-cleanup (time/now)
+        result         (thist/cleanup-task-history! history-rows-to-keep)
+        after-cleanup  (time/now)]
+    (db/insert! TaskHistory {:task       job-name
+                             :started_at (du/->Timestamp before-cleanup)
+                             :ended_at   (du/->Timestamp after-cleanup)
+                             :duration   (du/calculate-duration before-cleanup after-cleanup)})
+    (log/debug (trs "Task history cleanup successful, rows were {0}deleted"
+                    (when-not result (str (trs "not")
+                                          " "))))))
+
+(jobs/defjob TaskHistoryCleanup
+  [_]
+  (task-history-cleanup!))
+
+(defn task-init
+  "Job initialization"
+  []
+  ;; build our job
+  (reset! job (jobs/build
+               (jobs/of-type TaskHistoryCleanup)
+               (jobs/with-identity (jobs/key job-key))))
+  ;; build our trigger
+  (reset! trigger (triggers/build
+                   (triggers/with-identity (triggers/key trigger-key))
+                   (triggers/start-now)
+                   (triggers/with-schedule
+                     ;; run every day at midnight
+                     (cron/cron-schedule "0 0 * * * ? *"))))
+  ;; submit ourselves to the scheduler
+  (task/schedule-task! @job @trigger))
diff --git a/src/metabase/util.clj b/src/metabase/util.clj
index 9d39e7ad08da90faf39d42a830250f236a66f4fd..7f99e67b89e9287e65004158e82331e1aaa1e7aa 100644
--- a/src/metabase/util.clj
+++ b/src/metabase/util.clj
@@ -4,18 +4,15 @@
              [data :as data]
              [pprint :refer [pprint]]
              [string :as s]]
-            [clojure.java
-             [classpath :as classpath]
-             [jdbc :as jdbc]]
+            [clojure.java.classpath :as classpath]
             [clojure.math.numeric-tower :as math]
             [clojure.tools.logging :as log]
             [clojure.tools.namespace.find :as ns-find]
             [colorize.core :as colorize]
             [metabase.config :as config]
-            [puppetlabs.i18n.core :as i18n :refer [trs]]
+            [metabase.util.i18n :refer [trs]]
             [ring.util.codec :as codec])
   (:import [java.net InetAddress InetSocketAddress Socket]
-           java.sql.SQLException
            [java.text Normalizer Normalizer$Form]))
 
 ;; This is the very first log message that will get printed.  It's here because this is one of the very first
@@ -290,45 +287,6 @@
                                                          :when (re-find #"metabase" s)]
                                                      (s/replace s #"^metabase\." ""))))})
 
-(defn wrap-try-catch
-  "Returns a new function that wraps F in a `try-catch`. When an exception is caught, it is logged
-   with `log/error` and returns `nil`."
-  ([f]
-   (wrap-try-catch f nil))
-  ([f f-name]
-   (let [exception-message (if f-name
-                             (format "Caught exception in %s: " f-name)
-                             "Caught exception: ")]
-     (fn [& args]
-       (try
-         (apply f args)
-         (catch SQLException e
-           (log/error (format-color 'red "%s\n%s\n%s"
-                                    exception-message
-                                    (with-out-str (jdbc/print-sql-exception-chain e))
-                                    (pprint-to-str (filtered-stacktrace e)))))
-         (catch Throwable e
-           (log/error (format-color 'red "%s %s\n%s"
-                                    exception-message
-                                    (or (.getMessage e) e)
-                                    (pprint-to-str (filtered-stacktrace e))))))))))
-
-(defn try-apply
-  "Like `apply`, but wraps F inside a `try-catch` block and logs exceptions caught.
-   (This is actaully more flexible than `apply` -- the last argument doesn't have to be
-   a sequence:
-
-     (try-apply vector :a :b [:c :d]) -> [:a :b :c :d]
-     (apply vector :a :b [:c :d])     -> [:a :b :c :d]
-     (try-apply vector :a :b :c :d)   -> [:a :b :c :d]
-     (apply vector :a :b :c :d)       -> Not ok - :d is not a sequence
-
-   This allows us to use `try-apply` in more situations than we'd otherwise be able to."
-  [^clojure.lang.IFn f & args]
-  (apply (wrap-try-catch f) (concat (butlast args) (if (sequential? (last args))
-                                                     (last args)
-                                                     [(last args)]))))
-
 (defn deref-with-timeout
   "Call `deref` on a FUTURE and throw an exception if it takes more than TIMEOUT-MS."
   [futur timeout-ms]
@@ -515,7 +473,7 @@
                   (select-nested-keys v nested-keys))})))
 
 (defn base64-string?
-  "Is S a Base-64 encoded string?"
+  "Is `s` a Base-64 encoded string?"
   ^Boolean [s]
   (boolean (when (string? s)
              (re-find #"^[0-9A-Za-z/+]+=*$" s))))
@@ -591,9 +549,8 @@
 
 (defn is-java-9-or-higher?
   "Are we running on Java 9 or above?"
-  []
-  (when-let [java-major-version (some-> (System/getProperty "java.version")
-                                        (s/split #"\.")
-                                        first
-                                        Integer/parseInt)]
-    (>= java-major-version 9)))
+  ([]
+   (is-java-9-or-higher? (System/getProperty "java.version")))
+  ([java-version-str]
+   (when-let [[_ java-major-version-str] (re-matches #"^(?:1\.)?(\d+).*$" java-version-str)]
+     (>= (Integer/parseInt java-major-version-str) 9))))
diff --git a/src/metabase/util/date.clj b/src/metabase/util/date.clj
index 23b2775a420bca234a70dd8be031577966c467dd..31e2ef008d1a8e0ca4aeb1a4c5a4024af9b6e683 100644
--- a/src/metabase/util/date.clj
+++ b/src/metabase/util/date.clj
@@ -1,4 +1,5 @@
 (ns metabase.util.date
+  "Utility functions for working with datetimes of different types, and other related tasks."
   (:require [clj-time
              [coerce :as coerce]
              [core :as t]
@@ -6,7 +7,10 @@
             [clojure.math.numeric-tower :as math]
             [clojure.tools.logging :as log]
             [metabase.util :as u]
-            [puppetlabs.i18n.core :refer [trs]])
+            [metabase.util
+             [i18n :refer [trs]]
+             [schema :as su]]
+            [schema.core :as s])
   (:import clojure.lang.Keyword
            [java.sql Time Timestamp]
            [java.util Calendar Date TimeZone]
@@ -23,7 +27,7 @@
        :tag     TimeZone}
   *data-timezone*)
 
-(defprotocol ITimeZoneCoercible
+(defprotocol ^:private ITimeZoneCoercible
   "Coerce object to `java.util.TimeZone`"
   (coerce-to-timezone ^TimeZone [this]
     "Coerce `this` to `java.util.TimeZone`"))
@@ -40,7 +44,8 @@
   "UTC TimeZone"
   (coerce-to-timezone "UTC"))
 
-(def ^:private jvm-timezone
+(def jvm-timezone
+  "Machine time zone"
   (delay (coerce-to-timezone (System/getProperty "user.timezone"))))
 
 (defn- warn-on-timezone-conflict
@@ -84,7 +89,7 @@
   [db & body]
   `(call-with-effective-timezone ~db (fn [] ~@body)))
 
-(defprotocol ITimestampCoercible
+(defprotocol ^:private ITimestampCoercible
   "Coerce object to a `java.sql.Timestamp`."
   (coerce-to-timestamp ^java.sql.Timestamp [this] [this timezone-coercible]
     "Coerce this object to a `java.sql.Timestamp`. Strings are parsed as ISO-8601."))
@@ -109,7 +114,12 @@
 
 (defn ^Timestamp ->Timestamp
   "Converts `coercible-to-ts` to a `java.util.Timestamp`. Requires a `coercible-to-tz` if converting a string. Leans
-  on clj-time to ensure correct conversions between the various types"
+  on clj-time to ensure correct conversions between the various types
+
+  NOTE: This function requires you to pass in a timezone or bind `*report-timezone*`, probably to make sure you're not
+  doing something dumb by forgetting it. For cases where you'd just like to parse an ISO-8601-encoded String in peace
+  without specifying a timezone, pass in `:no-timezone` as the second param to explicitly have things parsed without
+  one. (Keep in mind that if your string does not specify a timezone, it will be parsed as UTC by default.)"
   ([coercible-to-ts]
    {:pre [(or (not (string? coercible-to-ts))
               (and (string? coercible-to-ts) (bound? #'*report-timezone*)))]}
@@ -118,10 +128,11 @@
    {:pre [(or (not (string? coercible-to-ts))
               (and (string? coercible-to-ts) timezone))]}
    (if (string? coercible-to-ts)
-     (coerce-to-timestamp (str->date-time coercible-to-ts (coerce-to-timezone timezone)))
+     (coerce-to-timestamp (str->date-time coercible-to-ts (when-not (= timezone :no-timezone)
+                                                            (coerce-to-timezone timezone))))
      (coerce-to-timestamp coercible-to-ts))))
 
-(defprotocol IDateTimeFormatterCoercible
+(defprotocol ^:private IDateTimeFormatterCoercible
   "Protocol for converting objects to `DateTimeFormatters`."
   (->DateTimeFormatter ^org.joda.time.format.DateTimeFormatter [this]
     "Coerce object to a `DateTimeFormatter`."))
@@ -138,15 +149,15 @@
 
 
 (defn parse-date
-  "Parse a datetime string S with a custom DATE-FORMAT, which can be a format string, clj-time formatter keyword, or
+  "Parse a datetime string `s` with a custom `date-format`, which can be a format string, clj-time formatter keyword, or
   anything else that can be coerced to a `DateTimeFormatter`.
 
-     (parse-date \"yyyyMMdd\" \"20160201\") -> #inst \"2016-02-01\"
-     (parse-date :date-time \"2016-02-01T00:00:00.000Z\") -> #inst \"2016-02-01\""
+    (parse-date \"yyyyMMdd\" \"20160201\") -> #inst \"2016-02-01\"
+    (parse-date :date-time \"2016-02-01T00:00:00.000Z\") -> #inst \"2016-02-01\""
   ^java.sql.Timestamp [date-format, ^String s]
   (->Timestamp (time/parse (->DateTimeFormatter date-format) s)))
 
-(defprotocol ISO8601
+(defprotocol ^:private ISO8601
   "Protocol for converting objects to ISO8601 formatted strings."
   (->iso-8601-datetime ^String [this timezone-id]
     "Coerce object to an ISO8601 date-time string such as \"2015-11-18T23:55:03.841Z\" with a given TIMEZONE."))
@@ -173,12 +184,12 @@
                (time/formatters :time)))))
 
 (defn format-time
-  "Returns a string representation of the time found in `T`"
+  "Returns a string representation of the time found in `t`"
   [t time-zone-id]
   (time/unparse (time-formatter time-zone-id) (coerce/to-date-time t)))
 
 (defn is-time?
-  "Returns true if `V` is a Time object"
+  "Returns true if `v` is a Time object"
   [v]
   (and v (instance? Time v)))
 
@@ -197,13 +208,13 @@
   (->Timestamp (System/currentTimeMillis)))
 
 (defn format-date
-  "Format DATE using a given DATE-FORMAT. NOTE: This will create a date string in the JVM's timezone, not the report
+  "Format `date` using a given `date-format`. NOTE: This will create a date string in the JVM's timezone, not the report
   timezone.
 
-   DATE is anything that can coerced to a `Timestamp` via `->Timestamp`, such as a `Date`, `Timestamp`,
-   `Long` (ms since the epoch), or an ISO-8601 `String`. DATE defaults to the current moment in time.
+   `date` is anything that can coerced to a `Timestamp` via `->Timestamp`, such as a `Date`, `Timestamp`,
+   `Long` (ms since the epoch), or an ISO-8601 `String`. `date` defaults to the current moment in time.
 
-   DATE-FORMAT is anything that can be passed to `->DateTimeFormatter`, such as `String`
+   `date-format` is anything that can be passed to `->DateTimeFormatter`, such as `String`
    (using [the usual date format args](http://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html)),
    `Keyword`, or `DateTimeFormatter`.
 
@@ -217,7 +228,7 @@
    (time/unparse (->DateTimeFormatter date-format) (coerce/from-sql-time (->Timestamp date)))))
 
 (def ^{:arglists '([] [date])} date->iso-8601
-  "Format DATE a an ISO-8601 string."
+  "Format `date` a an ISO-8601 string."
   (partial format-date :date-time))
 
 (defn date-string?
@@ -230,14 +241,14 @@
                (->Timestamp s utc)))))
 
 (defn ->Date
-  "Coerece DATE to a `java.util.Date`."
+  "Coerece `date` to a `java.util.Date`."
   (^java.util.Date []
    (java.util.Date.))
   (^java.util.Date [date]
    (java.util.Date. (.getTime (->Timestamp date)))))
 
 (defn ->Calendar
-  "Coerce DATE to a `java.util.Calendar`."
+  "Coerce `date` to a `java.util.Calendar`."
   (^java.util.Calendar []
    (doto (Calendar/getInstance)
      (.setTimeZone (TimeZone/getTimeZone "UTC"))))
@@ -249,7 +260,7 @@
      (.setTimeZone (TimeZone/getTimeZone timezone-id)))))
 
 (defn relative-date
-  "Return a new `Timestamp` relative to the current time using a relative date UNIT.
+  "Return a new Timestamp relative to the current time using a relative date `unit`.
 
      (relative-date :year -1) -> #inst 2014-11-12 ..."
   (^java.sql.Timestamp [unit amount]
@@ -269,12 +280,13 @@
                        (* amount multiplier)))
      (->Timestamp cal))))
 
-(def ^:private ^:const date-extract-units
+(def ^:const date-extract-units
+  "Units which return a (numerical, periodic) component of a date"
   #{:minute-of-hour :hour-of-day :day-of-week :day-of-month :day-of-year :week-of-year :month-of-year :quarter-of-year
     :year})
 
 (defn date-extract
-  "Extract UNIT from DATE. DATE defaults to now.
+  "Extract `unit` from `date`. `date` defaults to now.
 
      (date-extract :year) -> 2015"
   ([unit]
@@ -323,7 +335,7 @@
     (format "%d-%02d-01'T'ZZ" year month)))
 
 (defn date-trunc
-  "Truncate DATE to UNIT. DATE defaults to now.
+  "Truncate `date` to `unit`. `date` defaults to now.
 
      (date-trunc :month).
      ;; -> #inst \"2015-11-01T00:00:00\""
@@ -343,7 +355,7 @@
      :year    (trunc-with-format "yyyy-01-01'T'ZZ" date timezone-id))))
 
 (defn date-trunc-or-extract
-  "Apply date bucketing with UNIT to DATE. DATE defaults to now."
+  "Apply date bucketing with `unit` to `date`. `date` defaults to now."
   ([unit]
    (date-trunc-or-extract unit (System/currentTimeMillis) "UTC"))
   ([unit date]
@@ -368,9 +380,25 @@
       (recur (/ n divisor) more)
       (format "%.0f %s" (double n) (name unit)))))
 
+(defn format-microseconds
+  "Format a time interval in microseconds into something more readable."
+  ^String [microseconds]
+  (format-nanoseconds (* 1000.0 microseconds)))
+
+(defn format-milliseconds
+  "Format a time interval in milliseconds into something more readable."
+  ^String [milliseconds]
+  (format-microseconds (* 1000.0 milliseconds)))
+
+(defn format-seconds
+  "Format a time interval in seconds into something more readable."
+  ^String [seconds]
+  (format-milliseconds (* 1000.0 seconds)))
+
+;; TODO - Not sure this belongs in the datetime util namespace
 (defmacro profile
-  "Like `clojure.core/time`, but lets you specify a MESSAGE that gets printed with the total time,
-   and formats the time nicely using `format-nanoseconds`."
+  "Like `clojure.core/time`, but lets you specify a `message` that gets printed with the total time, and formats the
+  time nicely using `format-nanoseconds`."
   {:style/indent 1}
   ([form]
    `(profile ~(str form) ~form))
@@ -382,8 +410,8 @@
                    (format-nanoseconds (- (System/nanoTime) start-time#))))))))
 
 (defn- str->date-time-with-formatters
-  "Attempt to parse `DATE-STR` using `FORMATTERS`. First successful
-  parse is returned, or nil"
+  "Attempt to parse `date-str` using `formatters`. First successful parse is returned, or `nil` if it cannot be
+  successfully parsed."
   ([formatters date-str]
    (str->date-time-with-formatters formatters date-str nil))
   ([formatters ^String date-str ^TimeZone tz]
@@ -400,9 +428,9 @@
   (->DateTimeFormatter "yyyy-MM-dd HH:mm:ss.SSS"))
 
 (def ^:private ordered-date-parsers
-  "When using clj-time.format/parse without a formatter, it tries all default formatters, but not ordered by how
-  likely the date formatters will succeed. This leads to very slow parsing as many attempts fail before the right one
-  is found. Using this retains that flexibility but improves performance by trying the most likely ones first"
+  "When using clj-time.format/parse without a formatter, it tries all default formatters, but not ordered by how likely
+  the date formatters will succeed. This leads to very slow parsing as many attempts fail before the right one is
+  found. Using this retains that flexibility but improves performance by trying the most likely ones first"
   (let [most-likely-default-formatters [:mysql :date-hour-minute-second :date-time :date
                                         :basic-date-time :basic-date-time-no-ms
                                         :date-time :date-time-no-ms]]
@@ -411,7 +439,7 @@
             (vals (apply dissoc time/formatters most-likely-default-formatters)))))
 
 (defn str->date-time
-  "Like clj-time.format/parse but uses an ordered list of parsers to be faster. Returns the parsed date or nil if it
+  "Like clj-time.format/parse but uses an ordered list of parsers to be faster. Returns the parsed date, or `nil` if it
   was unable to be parsed."
   (^org.joda.time.DateTime [^String date-str]
    (str->date-time date-str nil))
@@ -424,11 +452,16 @@
             [(time/formatter "HH:mmZ") (time/formatter "HH:mm:SSZ") (time/formatter "HH:mm:SS.SSSZ")])))
 
 (defn str->time
-  "Parse `TIME-STR` and return a `java.sql.Time` instance. Returns nil
-  if `TIME-STR` can't be parsed."
+  "Parse `time-str` and return a `java.sql.Time` instance. Returns `nil` if `time-str` can't be parsed."
   ([^String date-str]
    (str->time date-str nil))
   ([^String date-str ^TimeZone tz]
    (some-> (str->date-time-with-formatters ordered-time-parsers date-str tz)
            coerce/to-long
            Time.)))
+
+(s/defn calculate-duration :- su/NonNegativeInt
+  "Given two datetimes, caculate the time between them, return the result in millis"
+  [begin-time :- (s/protocol coerce/ICoerce)
+   end-time :- (s/protocol coerce/ICoerce)]
+  (- (coerce/to-long end-time) (coerce/to-long begin-time)))
diff --git a/src/metabase/util/embed.clj b/src/metabase/util/embed.clj
index d38571bd20f9ae644853635fae04ccec71a3c375..51ec4130c6bca09cb1dbf8f08800554ae554b2d1 100644
--- a/src/metabase/util/embed.clj
+++ b/src/metabase/util/embed.clj
@@ -9,7 +9,7 @@
             [hiccup.core :refer [html]]
             [metabase.models.setting :as setting]
             [metabase.public-settings :as public-settings]
-            [puppetlabs.i18n.core :refer [tru]]
+            [metabase.util.i18n :refer [tru]]
             [ring.util.codec :as codec]))
 
 ;;; ------------------------------------------------------------ PUBLIC LINKS UTIL FNS ------------------------------------------------------------
diff --git a/src/metabase/util/encryption.clj b/src/metabase/util/encryption.clj
index fd920c98e479c8e84c9a472acf04d4d3cc8d0d9b..24afed693af1f7f3cfdfbfdcb111df797bbfa0f6 100644
--- a/src/metabase/util/encryption.clj
+++ b/src/metabase/util/encryption.clj
@@ -1,61 +1,72 @@
 (ns metabase.util.encryption
-  "Utility functions for encrypting and decrypting strings using AES256 CBC + HMAC SHA512 and the `MB_ENCRYPTION_SECRET_KEY` env var."
+  "Utility functions for encrypting and decrypting strings using AES256 CBC + HMAC SHA512 and the
+  `MB_ENCRYPTION_SECRET_KEY` env var."
   (:require [buddy.core
              [codecs :as codecs]
              [crypto :as crypto]
              [kdf :as kdf]
              [nonce :as nonce]]
+            [clojure.string :as str]
             [clojure.tools.logging :as log]
             [environ.core :as env]
             [metabase.util :as u]
+            [metabase.util.i18n :refer [trs]]
             [ring.util.codec :as codec]))
 
 (defn secret-key->hash
-  "Generate a 64-byte byte array hash of SECRET-KEY using 100,000 iterations of PBKDF2+SHA512."
+  "Generate a 64-byte byte array hash of `secret-key` using 100,000 iterations of PBKDF2+SHA512."
   ^bytes [^String secret-key]
   (kdf/get-bytes (kdf/engine {:alg        :pbkdf2+sha512
                               :key        secret-key
                               :iterations 100000}) ; 100,000 iterations takes about ~160ms on my laptop
                  64))
 
-;; apperently if you're not tagging in an arglist, `^bytes` will set the `:tag` metadata to `clojure.core/bytes` (ick) so you have to do `^{:tag 'bytes}` instead
+;; apperently if you're not tagging in an arglist, `^bytes` will set the `:tag` metadata to `clojure.core/bytes` (ick)
+;; so you have to do `^{:tag 'bytes}` instead
 (defonce ^:private ^{:tag 'bytes} default-secret-key
   (when-let [secret-key (env/env :mb-encryption-secret-key)]
     (when (seq secret-key)
       (assert (>= (count secret-key) 16)
-        "MB_ENCRYPTION_SECRET_KEY must be at least 16 characters.")
+        (trs "MB_ENCRYPTION_SECRET_KEY must be at least 16 characters."))
       (secret-key->hash secret-key))))
 
 ;; log a nice message letting people know whether DB details encryption is enabled
 (log/info
- (format "DB details encryption is %s for this Metabase instance. %s"
-         (if default-secret-key "ENABLED" "DISABLED")
-         (u/emoji (if default-secret-key "🔐" "🔓")))
- "\nSee"
- "http://www.metabase.com/docs/latest/operations-guide/start.html#encrypting-your-database-connection-details-at-rest"
- "for more information.")
+ (if default-secret-key
+   (trs "Saved credentials encryption is ENABLED for this Metabase instance.")
+   (trs "Saved credentials encryption is DISABLED for this Metabase instance."))
+ (u/emoji (if default-secret-key "🔐" "🔓"))
+ "\n"
+ (trs "For more information, see")
+ "https://www.metabase.com/docs/latest/operations-guide/start.html#encrypting-your-database-connection-details-at-rest")
 
 (defn encrypt
-  "Encrypt string S as hex bytes using a SECRET-KEY (a 64-byte byte array), by default the hashed value of `MB_ENCRYPTION_SECRET_KEY`."
+  "Encrypt string `s` as hex bytes using a `secret-key` (a 64-byte byte array), by default the hashed value of
+  `MB_ENCRYPTION_SECRET_KEY`."
   (^String [^String s]
    (encrypt default-secret-key s))
   (^String [^String secret-key, ^String s]
    (let [initialization-vector (nonce/random-bytes 16)]
-     (codec/base64-encode (byte-array (concat initialization-vector
-                                              (crypto/encrypt (codecs/to-bytes s) secret-key initialization-vector {:algorithm :aes256-cbc-hmac-sha512})))))))
+     (codec/base64-encode
+      (byte-array
+       (concat initialization-vector
+               (crypto/encrypt (codecs/to-bytes s) secret-key initialization-vector
+                               {:algorithm :aes256-cbc-hmac-sha512})))))))
 
 (defn decrypt
-  "Decrypt string S  using a SECRET-KEY (a 64-byte byte array), by default the hashed value of `MB_ENCRYPTION_SECRET_KEY`."
+  "Decrypt string `s` using a `secret-key` (a 64-byte byte array), by default the hashed value of
+  `MB_ENCRYPTION_SECRET_KEY`."
   (^String [^String s]
    (decrypt default-secret-key s))
   (^String [secret-key, ^String s]
    (let [bytes                           (codec/base64-decode s)
          [initialization-vector message] (split-at 16 bytes)]
-     (codecs/bytes->str (crypto/decrypt (byte-array message) secret-key (byte-array initialization-vector) {:algorithm :aes256-cbc-hmac-sha512})))))
+     (codecs/bytes->str (crypto/decrypt (byte-array message) secret-key (byte-array initialization-vector)
+                                        {:algorithm :aes256-cbc-hmac-sha512})))))
 
 
 (defn maybe-encrypt
-  "If `MB_ENCRYPTION_SECRET_KEY` is set, return an encrypted version of S; otherwise return S as-is."
+  "If `MB_ENCRYPTION_SECRET_KEY` is set, return an encrypted version of `s`; otherwise return `s` as-is."
   (^String [^String s]
    (maybe-encrypt default-secret-key s))
   (^String [secret-key, ^String s]
@@ -64,18 +75,37 @@
        (encrypt secret-key s))
      s)))
 
+(def ^:private ^:const aes256-tag-length 32)
+(def ^:private ^:const aes256-block-size 16)
+
+(defn- possibly-encrypted-string?
+  "Returns true if it's likely that `s` is an encrypted string. Specifically we need `s` to be a non-blank, base64
+  encoded string of the correct length. The correct length is determined by the cipher's type tag and the cipher's
+  block size (AES+CBC). To compute this, we need the number of bytes in the input, subtract the bytes used by the
+  cipher type tag (`aes256-tag-length`) and what is left should be divisible by the cipher's block size
+  (`aes256-block-size`). If it's not divisible by that number it is either not encrypted or it has been corrupted as
+  it must always have a multiple of the block size or it won't decrypt."
+  [s]
+  (when-let [str-byte-length (and (not (str/blank? s))
+                                  (u/base64-string? s)
+                                  (alength ^bytes (codec/base64-decode s)))]
+    (zero? (mod (- str-byte-length aes256-tag-length)
+                aes256-block-size))))
+
 (defn maybe-decrypt
-  "If `MB_ENCRYPTION_SECRET_KEY` is set and S is encrypted, decrypt S; otherwise return S as-is."
+  "If `MB_ENCRYPTION_SECRET_KEY` is set and `s` is encrypted, decrypt `s`; otherwise return `s` as-is."
   (^String [^String s]
    (maybe-decrypt default-secret-key s))
   (^String [secret-key, ^String s]
-   (if (and secret-key (seq s))
+   (if (and secret-key (possibly-encrypted-string? s))
      (try
        (decrypt secret-key s)
        (catch Throwable e
-         (if (u/base64-string? s)
-           ;; if we can't decrypt `s`, but it *is* encrypted, log an error message and return `nil`
-           (log/error "Cannot decrypt encrypted details. Have you changed or forgot to set MB_ENCRYPTION_SECRET_KEY?" (.getMessage e))
-           ;; otherwise return S without decrypting. It's probably not decrypted in the first place
-           s)))
+         ;; if we can't decrypt `s`, but it *is* probably encrypted, log a warning
+         (log/warn
+          (trs "Cannot decrypt encrypted string. Have you changed or forgot to set MB_ENCRYPTION_SECRET_KEY?")
+          (.getMessage e)
+          (u/pprint-to-str (u/filtered-stacktrace e)))
+         s))
+     ;; otherwise return `s` without decrypting. It's probably not decrypted in the first place
      s)))
diff --git a/src/metabase/util/i18n.clj b/src/metabase/util/i18n.clj
index dd7a4d5768a46d8d6f8aadbd5a95e286c9dd33e3..7302c9c993e79e7a305209391d2a4dc6f3073730 100644
--- a/src/metabase/util/i18n.clj
+++ b/src/metabase/util/i18n.clj
@@ -1,6 +1,9 @@
 (ns metabase.util.i18n
-  (:require
-    [puppetlabs.i18n.core :refer [available-locales]])
+  (:refer-clojure :exclude [ex-info])
+  (:require [cheshire.generate :as json-gen]
+            [clojure.walk :as walk]
+            [puppetlabs.i18n.core :as i18n :refer [available-locales]]
+            [schema.core :as s])
   (:import java.util.Locale))
 
 (defn available-locales-with-names
@@ -12,3 +15,75 @@
   "This sets the local for the instance"
   [locale]
   (Locale/setDefault (Locale/forLanguageTag locale)))
+
+(defrecord UserLocalizedString [ns-str msg args]
+  java.lang.Object
+  (toString [_]
+    (apply i18n/translate ns-str (i18n/user-locale) msg args))
+  schema.core.Schema
+  (explain [this]
+    (str this)))
+
+(defrecord SystemLocalizedString [ns-str msg args]
+  java.lang.Object
+  (toString [_]
+    (apply i18n/translate ns-str (i18n/system-locale) msg args))
+  s/Schema
+  (explain [this]
+    (str this)))
+
+(defn- localized-to-json
+  "Write a UserLocalizedString or SystemLocalizedString to the `json-generator`. This is intended for
+  `json-gen/add-encoder`. Ideallys we'd implement those protocols directly as it's faster, but currently that doesn't
+  work with Cheshire"
+  [localized-string json-generator]
+  (json-gen/write-string json-generator (str localized-string)))
+
+(json-gen/add-encoder UserLocalizedString localized-to-json)
+(json-gen/add-encoder SystemLocalizedString localized-to-json)
+
+(def LocalizedString
+  "Schema for user and system localized string instances"
+  (s/cond-pre UserLocalizedString SystemLocalizedString))
+
+(defmacro tru
+  "Similar to `puppetlabs.i18n.core/tru` but creates a `UserLocalizedString` instance so that conversion to the
+  correct locale can be delayed until it is needed. The user locale comes from the browser, so conversion to the
+  localized string needs to be 'late bound' and only occur when the user's locale is in scope. Calling `str` on the
+  results of this invocation will lookup the translated version of the string."
+  [msg & args]
+  `(UserLocalizedString. (namespace-munge *ns*) ~msg ~(vec args)))
+
+(defmacro trs
+  "Similar to `puppetlabs.i18n.core/trs` but creates a `SystemLocalizedString` instance so that conversion to the
+  correct locale can be delayed until it is needed. This is needed as the system locale from the JVM can be
+  overridden/changed by a setting. Calling `str` on the results of this invocation will lookup the translated version
+  of the string."
+  [msg & args]
+  `(SystemLocalizedString. (namespace-munge *ns*) ~msg ~(vec args)))
+
+(def ^:private localized-string-checker
+  "Compiled checker for `LocalizedString`s which is more efficient when used repeatedly like in `localized-string?`
+  below"
+  (s/checker LocalizedString))
+
+(defn localized-string?
+  "Returns `true` if `maybe-a-localized-string` is a system or user localized string instance"
+  [maybe-a-localized-string]
+  (not (localized-string-checker maybe-a-localized-string)))
+
+(defn localized-strings->strings
+  "Walks the datastructure `x` and converts any localized strings to regular string"
+  [x]
+  (walk/postwalk (fn [node]
+                   (if (localized-string? node)
+                     (str node)
+                     node)) x))
+
+(defn ex-info
+  "Just like `clojure.core/ex-info` but it is i18n-aware. It will call `str` on `msg` and walk `ex-data` converting any
+  `SystemLocalizedString` and `UserLocalizedString`s to a regular string"
+  ([msg ex-data-map]
+   (clojure.core/ex-info (str msg) (localized-strings->strings ex-data-map)))
+  ([msg ex-data-map cause]
+   (clojure.core/ex-info (str msg) (localized-strings->strings ex-data-map) cause)))
diff --git a/src/metabase/util/password.clj b/src/metabase/util/password.clj
index 31893cca15781baafa6b0cb76203a34bb3c11eb8..762f019f9abfec257400e3518c885a96d178d30c 100644
--- a/src/metabase/util/password.clj
+++ b/src/metabase/util/password.clj
@@ -1,4 +1,5 @@
 (ns metabase.util.password
+  "Utility functions for checking passwords against hashes and for making sure passwords match complexity requirements."
   (:require [cemerick.friend.credentials :as creds]
             [metabase
              [config :as config]
diff --git a/src/metabase/util/query.clj b/src/metabase/util/query.clj
deleted file mode 100644
index 3fd55bb12a7aa78d1b3c43f43ad06bc28eb71ab8..0000000000000000000000000000000000000000
--- a/src/metabase/util/query.clj
+++ /dev/null
@@ -1,36 +0,0 @@
-(ns metabase.util.query
-  "Utility functions for dealing with structured queries."
-  (:require [clojure.core.match :refer [match]]))
-
-;; TODO These functions are written for MBQL '95. MBQL '98 doesn't require that clause identifiers be uppercased, or
-;; strings. Also, I'm not convinced `:segment` or `:metric` are actually part of MBQL since they aren't in the MBQL
-;; '98 reference
-
-(defn- parse-filter-subclause [subclause]
-  (match subclause
-    ["SEGMENT" (segment-id :guard integer?)] segment-id
-    _                                        nil))
-
-;; TODO This doesn't handle the NOT clause
-(defn- parse-filter [clause]
-  (match clause
-    ["AND" & subclauses] (mapv parse-filter subclauses)
-    ["OR" & subclauses]  (mapv parse-filter subclauses)
-    subclause            (parse-filter-subclause subclause)))
-
-(defn extract-segment-ids
-  "Return the IDs of all `Segments` in the query. (I think that's what this does? :flushed:)"
-  [query]
-  (when-let [filter-clause (:filter query)]
-    (->> (parse-filter filter-clause)
-         flatten
-         (filter identity)
-         set)))
-
-(defn extract-metric-ids
-  "Return the IDs of all `Metrics` in the query. (I think that's what this does? :flushed:)"
-  [query]
-  (when-let [aggregation-clause (:aggregation query)]
-    (match aggregation-clause
-      ["METRIC" (metric-id :guard integer?)] #{metric-id}
-      _                                       nil)))
diff --git a/src/metabase/util/schema.clj b/src/metabase/util/schema.clj
index 195f16fe49953549ae310f8d6f2f1bb796a4ca56..b6f46b63be121122d72749bfbb19ffb54d723e9e 100644
--- a/src/metabase/util/schema.clj
+++ b/src/metabase/util/schema.clj
@@ -5,13 +5,32 @@
             [medley.core :as m]
             [metabase.util :as u]
             [metabase.util.password :as password]
-            [puppetlabs.i18n.core :refer [tru]]
-            [schema.core :as s]))
+            [metabase.util.i18n :refer [tru]]
+            [schema.core :as s]
+            [schema.macros :as s.macros]
+            [schema.utils :as s.utils]))
 
 ;; 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)
 
+;; swap out the default impl of `schema.core/validator` with one that does not barf out the entire schema, since it's
+;; way too huge with things like our MBQL query schema
+(defn- schema-core-validator [schema]
+  (let [c (s/checker schema)]
+    (fn [value]
+      (when-let [error (c value)]
+        (s.macros/error! (s.utils/format* "Value does not match schema: %s" (pr-str error))
+                         {:value value, :error error}))
+      value)))
+
+(intern 'schema.core 'validator schema-core-validator)
+
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                     API Schema Validation & Error Messages                                     |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
 (defn with-api-error-message
   "Return SCHEMA with an additional API-ERROR-MESSAGE that will be used to explain the error if a parameter fails
    validation."
@@ -111,6 +130,12 @@
   (with-api-error-message (s/constrained s/Str (complement str/blank?) "Non-blank string")
     (tru "value must be a non-blank string.")))
 
+(def IntGreaterThanOrEqualToZero
+  "Schema representing an integer than must also be greater than or equal to zero."
+  (with-api-error-message
+      (s/constrained s/Int (partial <= 0) (tru "Integer greater than or equal to zero"))
+    (tru "value must be an integer greater than or equal to zero.")))
+
 ;; TODO - rename this to `PositiveInt`?
 (def IntGreaterThanZero
   "Schema representing an integer than must also be greater than zero."
@@ -118,6 +143,18 @@
       (s/constrained s/Int (partial < 0) (tru "Integer greater than zero"))
     (tru "value must be an integer greater than zero.")))
 
+(def NonNegativeInt
+  "Schema representing an integer 0 or greater"
+  (with-api-error-message
+      (s/constrained s/Int (partial <= 0) (tru "Integer greater than or equal to zero"))
+    (tru "value must be an integer zero or greater.")))
+
+(def PositiveNum
+  "Schema representing a numeric value greater than zero. This allows floating point numbers and integers."
+  (with-api-error-message
+      (s/constrained s/Num (partial < 0) (tru "Number greater than zero"))
+    (tru "value must be a number greater than zero.")))
+
 (def KeywordOrString
   "Schema for something that can be either a `Keyword` or a `String`."
   (s/named (s/cond-pre s/Keyword s/Str) (tru "Keyword or string")))
diff --git a/src/metabase/util/ssh.clj b/src/metabase/util/ssh.clj
index 95e4ce487d1ed9a69b701bd5fadbfc300d37b603..831a6654d660c71f1cc3099478e47849ac2b035b 100644
--- a/src/metabase/util/ssh.clj
+++ b/src/metabase/util/ssh.clj
@@ -82,7 +82,7 @@
           [connection tunnel-entrance-port] (start-ssh-tunnel (assoc details :host host)) ;; don't include L7 protocol in ssh tunnel
           details-with-tunnel (assoc details
                                 :port tunnel-entrance-port ;; This parameter is set dynamically when the connection is established
-                                :host (str proto (:tunnel-host details))
+                                :host (str proto "localhost")
                                 :tunnel-entrance-port tunnel-entrance-port ;; the input port is not known until the connection is opened
                                 :tunnel-connection connection)]
       details-with-tunnel)
diff --git a/src/metabase/util/stats.clj b/src/metabase/util/stats.clj
index 361cba0fdf3db1c121b085e9f7217d1ec4f6d2f9..2072d90d3807af719f23eebfb9a3046fce9962ba 100644
--- a/src/metabase/util/stats.clj
+++ b/src/metabase/util/stats.clj
@@ -170,7 +170,7 @@
   {:groups (db/count PermissionsGroup)})
 
 (defn- card-has-params? [card]
-  (boolean (get-in card [:dataset_query :native :template_tags])))
+  (boolean (get-in card [:dataset_query :native :template-tags])))
 
 (defn- question-metrics
   "Get metrics based on questions
diff --git a/test/metabase/api/alert_test.clj b/test/metabase/api/alert_test.clj
index 81cff74164c2a839e9170041108150f089543d3f..f2f3d80e4736accbb2dfe36a02415367d8c6abca 100644
--- a/test/metabase/api/alert_test.clj
+++ b/test/metabase/api/alert_test.clj
@@ -6,7 +6,6 @@
              [http-client :as http]
              [middleware :as middleware]
              [util :as u]]
-            [metabase.api.card-test :as card-api-test]
             [metabase.models
              [card :refer [Card]]
              [collection :refer [Collection]]
@@ -22,9 +21,7 @@
              [util :as tu]]
             [metabase.test.data.users :as users :refer :all]
             [metabase.test.mock.util :refer [pulse-channel-defaults]]
-            [toucan
-             [db :as db]
-             [hydrate :refer [hydrate]]]
+            [toucan.db :as db]
             [toucan.util.test :as tt]))
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
@@ -79,7 +76,8 @@
        ~@body)))
 
 (defmacro ^:private with-alert-in-collection
-  "Do `body` with a temporary Alert whose Card is in a Collection, setting the stage to write various tests below."
+  "Do `body` with a temporary Alert whose Card is in a Collection, setting the stage to write various tests below. (Make
+  sure to grant All Users permissions to the Collection if needed.)"
   {:style/indent 1}
   [[db-binding collection-binding alert-binding card-binding] & body]
   `(pulse-test/with-pulse-in-collection [~(or db-binding '_) collection# alert# card#]
@@ -100,15 +98,15 @@
   (The name of this function is somewhat of a misnomer since the Alerts themselves aren't in Collections; it is their
   Cards that are. Alerts do not go in Collections; their perms are derived from their Cards.)"
   [grant-collection-perms-fn! alerts-or-ids f]
-  (tt/with-temp Collection [collection]
-    (grant-collection-perms-fn! (group/all-users) collection)
-    ;; Go ahead and put all the Cards for all of the Alerts in the temp Collection
-    (when (seq alerts-or-ids)
-      (doseq [alert (hydrate (db/select Pulse :id [:in (map u/get-id alerts-or-ids)])
-                             :cards)
-              card  (:cards alert)]
-        (db/update! Card (u/get-id card) :collection_id (u/get-id collection))))
-    (f)))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [collection]
+      (grant-collection-perms-fn! (group/all-users) collection)
+      ;; Go ahead and put all the Cards for all of the Alerts in the temp Collection
+      (when (seq alerts-or-ids)
+        (doseq [alert (db/select Pulse :id [:in (map u/get-id alerts-or-ids)])
+                :let  [card (#'metabase.models.pulse/alert->card alert)]]
+          (db/update! Card (u/get-id card) :collection_id (u/get-id collection))))
+      (f))))
 
 (defmacro ^:private with-alerts-in-readable-collection [alerts-or-ids & body]
   `(do-with-alerts-in-a-collection perms/grant-collection-read-permissions! ~alerts-or-ids (fn [] ~@body)))
@@ -128,6 +126,31 @@
 (expect (get middleware/response-unauthentic :body) (http/client :put 401 "alert/13"))
 
 
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                                 GET /api/alert                                                 |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+;; by default, archived Alerts should be excluded
+(expect
+  #{"Not Archived"}
+  (with-alert-in-collection [_ _ not-archived-alert]
+    (with-alert-in-collection [_ _ archived-alert]
+      (db/update! Pulse (u/get-id not-archived-alert) :name "Not Archived")
+      (db/update! Pulse (u/get-id archived-alert)     :name "Archived", :archived true)
+      (with-alerts-in-readable-collection [not-archived-alert archived-alert]
+        (set (map :name ((user->client :rasta) :get 200 "alert")))))))
+
+;; can we fetch archived Alerts?
+(expect
+  #{"Archived"}
+  (with-alert-in-collection [_ _ not-archived-alert]
+    (with-alert-in-collection [_ _ archived-alert]
+      (db/update! Pulse (u/get-id not-archived-alert) :name "Not Archived")
+      (db/update! Pulse (u/get-id archived-alert)     :name "Archived", :archived true)
+      (with-alerts-in-readable-collection [not-archived-alert archived-alert]
+        (set (map :name ((user->client :rasta) :get 200 "alert?archived=true")))))))
+
+
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                                POST /api/alert                                                 |
 ;;; +----------------------------------------------------------------------------------------------------------------+
@@ -223,26 +246,27 @@
    :recipients    []})
 
 ;; Check creation of a new rows alert with email notification
-(tt/expect-with-temp [Collection [collection]
-                      Card       [card {:name          "My question"
-                                        :collection_id (u/get-id collection)}]]
+(tt/expect-with-temp [Card [card {:name "My question"}]]
   [(-> (default-alert card)
        (assoc-in [:card :include_csv] true)
        (assoc-in [:card :collection_id] true)
        (update-in [:channels 0] merge {:schedule_hour 12, :schedule_type "daily", :recipients []}))
    (rasta-new-alert-email {"has any results" true})]
-  (with-alert-setup
-    (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
-    [(et/with-expected-messages 1
-       ((alert-client :rasta) :post 200 "alert"
-        {:card             {:id (u/get-id card), :include_csv false, :include_xls false}
-         :collection_id    (u/get-id collection)
-         :alert_condition  "rows"
-         :alert_first_only false
-         :channels         [daily-email-channel]}))
-     (et/regex-email-bodies #"https://metabase.com/testmb"
-                            #"has any results"
-                            #"My question")]))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [collection]
+      (db/update! Card (u/get-id card) :collection_id (u/get-id collection))
+      (with-alert-setup
+        (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
+        [(et/with-expected-messages 1
+           ((alert-client :rasta) :post 200 "alert"
+            {:card             {:id (u/get-id card), :include_csv false, :include_xls false}
+             :collection_id    (u/get-id collection)
+             :alert_condition  "rows"
+             :alert_first_only false
+             :channels         [daily-email-channel]}))
+         (et/regex-email-bodies #"https://metabase.com/testmb"
+                                #"has any results"
+                                #"My question")]))))
 
 (defn- setify-recipient-emails [results]
   (update results :channels (fn [channels]
@@ -250,77 +274,79 @@
 
 ;; An admin created alert should notify others they've been subscribed
 (tt/expect-with-temp [Card [card {:name "My question"}]]
-  {1 (-> (default-alert card)
-         (assoc :creator (user-details :crowberto))
-         (assoc-in [:card :include_csv] true)
-         (update-in [:channels 0] merge {:schedule_hour 12
-                                         :schedule_type "daily"
-                                         :recipients    (set (map recipient-details [:rasta :crowberto]))}))
-   2 (merge (et/email-to :crowberto {:subject "You set up an alert"
-                                     :body    {"https://metabase.com/testmb"  true
-                                               "My question"                  true
-                                               "now getting alerts"           false
-                                               "confirmation that your alert" true}})
-            (rasta-added-to-alert-email {"My question"                  true
-                                         "now getting alerts"           true
-                                         "confirmation that your alert" false}))}
+  {:response (-> (default-alert card)
+                 (assoc :creator (user-details :crowberto))
+                 (assoc-in [:card :include_csv] true)
+                 (update-in [:channels 0] merge {:schedule_hour 12
+                                                 :schedule_type "daily"
+                                                 :recipients    (set (map recipient-details [:rasta :crowberto]))}))
+   :emails (merge (et/email-to :crowberto {:subject "You set up an alert"
+                                           :body    {"https://metabase.com/testmb"  true
+                                                     "My question"                  true
+                                                     "now getting alerts"           false
+                                                     "confirmation that your alert" true}})
+                  (rasta-added-to-alert-email {"My question"                  true
+                                               "now getting alerts"           true
+                                               "confirmation that your alert" false}))}
 
   (with-alert-setup
     (array-map
-     1 (et/with-expected-messages 2
-         (-> ((alert-client :crowberto) :post 200 "alert"
-              {:card             {:id (u/get-id card), :include_csv false, :include_xls false}
-               :alert_condition  "rows"
-               :alert_first_only false
-               :channels         [(assoc daily-email-channel
-                                    :details       {:emails nil}
-                                    :recipients    (mapv fetch-user [:crowberto :rasta]))]})
-             setify-recipient-emails))
-     2 (et/regex-email-bodies #"https://metabase.com/testmb"
-                              #"now getting alerts"
-                              #"confirmation that your alert"
-                              #"My question"))))
+     :response (et/with-expected-messages 2
+                 (-> ((alert-client :crowberto) :post 200 "alert"
+                      {:card             {:id (u/get-id card), :include_csv false, :include_xls false}
+                       :alert_condition  "rows"
+                       :alert_first_only false
+                       :channels         [(assoc daily-email-channel
+                                            :details       {:emails nil}
+                                            :recipients    (mapv fetch-user [:crowberto :rasta]))]})
+                     setify-recipient-emails))
+     :emails (et/regex-email-bodies #"https://metabase.com/testmb"
+                                    #"now getting alerts"
+                                    #"confirmation that your alert"
+                                    #"My question"))))
 
 ;; Check creation of a below goal alert
 (expect
   (rasta-new-alert-email {"goes below its goal" true})
-  (tt/with-temp* [Collection [collection]
-                  Card       [card {:name          "My question"
-                                    :display       "line"
-                                    :collection_id (u/get-id collection)}]]
-    (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
-    (with-alert-setup
-      (et/with-expected-messages 1
-        ((user->client :rasta) :post 200 "alert"
-         {:card             {:id (u/get-id card), :include_csv false, :include_xls false}
-          :alert_condition  "goal"
-          :alert_above_goal false
-          :alert_first_only false
-          :channels         [daily-email-channel]}))
-      (et/regex-email-bodies #"https://metabase.com/testmb"
-                             #"goes below its goal"
-                             #"My question"))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [collection]
+                    Card       [card {:name          "My question"
+                                      :display       "line"
+                                      :collection_id (u/get-id collection)}]]
+      (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
+      (with-alert-setup
+        (et/with-expected-messages 1
+          ((user->client :rasta) :post 200 "alert"
+           {:card             {:id (u/get-id card), :include_csv false, :include_xls false}
+            :alert_condition  "goal"
+            :alert_above_goal false
+            :alert_first_only false
+            :channels         [daily-email-channel]}))
+        (et/regex-email-bodies #"https://metabase.com/testmb"
+                               #"goes below its goal"
+                               #"My question")))))
 
 ;; Check creation of a above goal alert
 (expect
   (rasta-new-alert-email {"meets its goal" true})
-  (tt/with-temp* [Collection [collection]
-                  Card       [card {:name          "My question"
-                                    :display       "bar"
-                                    :collection_id (u/get-id collection)}]]
-    (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
-    (with-alert-setup
-      (et/with-expected-messages 1
-        ((user->client :rasta) :post 200 "alert"
-         {:card             {:id (u/get-id card), :include_csv false, :include_xls false}
-          :collection_id    (u/get-id collection)
-          :alert_condition  "goal"
-          :alert_above_goal true
-          :alert_first_only false
-          :channels         [daily-email-channel]}))
-      (et/regex-email-bodies #"https://metabase.com/testmb"
-                             #"meets its goal"
-                             #"My question"))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [collection]
+                    Card       [card {:name          "My question"
+                                      :display       "bar"
+                                      :collection_id (u/get-id collection)}]]
+      (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
+      (with-alert-setup
+        (et/with-expected-messages 1
+          ((user->client :rasta) :post 200 "alert"
+           {:card             {:id (u/get-id card), :include_csv false, :include_xls false}
+            :collection_id    (u/get-id collection)
+            :alert_condition  "goal"
+            :alert_above_goal true
+            :alert_first_only false
+            :channels         [daily-email-channel]}))
+        (et/regex-email-bodies #"https://metabase.com/testmb"
+                               #"meets its goal"
+                               #"My question")))))
 
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
@@ -482,21 +508,41 @@
                   PulseCard             [_     (pulse-card alert card)]
                   PulseChannel          [pc    (pulse-channel alert)]
                   PulseChannelRecipient [_     (recipient pc :rasta)]]
-    (with-alert-setup
-      ((alert-client :rasta) :put 403 (alert-url alert)
-       (default-alert-req card pc)))))
+    (tu/with-non-admin-groups-no-root-collection-perms
+      (with-alert-setup
+        ((alert-client :rasta) :put 403 (alert-url alert)
+         (default-alert-req card pc))))))
 
 ;; Non-admin users can't edit alerts if they're not in the recipient list
 (expect
   "You don't have permissions to do that."
-  (tt/with-temp* [Pulse                 [alert (basic-alert)]
-                  Card                  [card]
-                  PulseCard             [_     (pulse-card alert card)]
-                  PulseChannel          [pc    (pulse-channel alert)]
-                  PulseChannelRecipient [_     (recipient pc :crowberto)]]
-    (with-alert-setup
-      ((alert-client :rasta) :put 403 (alert-url alert)
-       (default-alert-req card pc)))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Pulse                 [alert (basic-alert)]
+                    Card                  [card]
+                    PulseCard             [_     (pulse-card alert card)]
+                    PulseChannel          [pc    (pulse-channel alert)]
+                    PulseChannelRecipient [_     (recipient pc :crowberto)]]
+      (with-alert-setup
+        ((alert-client :rasta) :put 403 (alert-url alert)
+         (default-alert-req card pc))))))
+
+;; Can we archive an Alert?
+(expect
+  (with-alert-in-collection [_ collection alert]
+    (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
+    ((user->client :rasta) :put 200 (str "alert/" (u/get-id alert))
+     {:archived true})
+    (db/select-one-field :archived Pulse :id (u/get-id alert))))
+
+;; Can we unarchive an Alert?
+(expect
+  false
+  (with-alert-in-collection [_ collection alert]
+    (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
+    (db/update! Pulse (u/get-id alert) :archived true)
+    ((user->client :rasta) :put 200 (str "alert/" (u/get-id alert))
+     {:archived false})
+    (db/select-one-field :archived Pulse :id (u/get-id alert))))
 
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
@@ -507,7 +553,7 @@
   {:name          "Foo"
    :dataset_query {:database (data/id)
                    :type     :query
-                   :query    {:source_table (data/id :checkins)
+                   :query    {:source-table (data/id :checkins)
                               :aggregation  [["count"]]
                               :breakout     [["datetime-field" (data/id :checkins :date) "hour"]]}}})
 
@@ -527,13 +573,13 @@
                       PulseChannel          [pc   (pulse-channel alert)]
                       PulseChannelRecipient [_    (recipient pc :rasta)]]
   [(-> (default-alert card)
-       ;; The read_only flag is used by the UI to determine what the user is allowed to update
-       (assoc :read_only true)
+       (assoc :can_write false)
        (update-in [:channels 0] merge {:schedule_hour 15, :schedule_type "daily"})
        (assoc-in [:card :collection_id] true))]
-  (with-alert-setup
-    (with-alerts-in-readable-collection [alert]
-      ((alert-client :rasta) :get 200 (alert-question-url card)))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (with-alert-setup
+      (with-alerts-in-readable-collection [alert]
+        ((alert-client :rasta) :get 200 (alert-question-url card))))))
 
 ;; Non-admin users shouldn't see alerts they created if they're no longer recipients
 (expect
@@ -750,37 +796,39 @@
 (expect
   {:count    1
    :response "You don't have permissions to do that."}
-  (tt/with-temp* [Card                  [card  (basic-alert-query)]
-                  Pulse                 [alert (basic-alert)]
-                  PulseCard             [_     (pulse-card alert card)]
-                  PulseChannel          [pc    (pulse-channel alert)]
-                  PulseChannelRecipient [_     (recipient pc :rasta)]]
-    (with-alerts-in-readable-collection [alert]
-      (with-alert-setup
-        (array-map
-         :count    (api:alert-question-count :rasta card)
-         :response (api:delete! :rasta 403 alert))))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Card                  [card  (basic-alert-query)]
+                    Pulse                 [alert (basic-alert)]
+                    PulseCard             [_     (pulse-card alert card)]
+                    PulseChannel          [pc    (pulse-channel alert)]
+                    PulseChannelRecipient [_     (recipient pc :rasta)]]
+      (with-alerts-in-readable-collection [alert]
+        (with-alert-setup
+          (array-map
+           :count    (api:alert-question-count :rasta card)
+           :response (api:delete! :rasta 403 alert)))))))
 
 ;; Testing a user can't delete an admin's alert
 (expect
   {:count-1  1
    :response nil
    :count-2  0}
-  (tt/with-temp* [Card                  [card  (basic-alert-query)]
-                  Pulse                 [alert (basic-alert)]
-                  PulseCard             [_     (pulse-card alert card)]
-                  PulseChannel          [pc    (pulse-channel alert)]
-                  PulseChannelRecipient [_     (recipient pc :rasta)]]
-    (with-alert-setup
-      (let [original-alert-response ((user->client :crowberto) :get 200 (alert-question-url card))]
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Card                  [card  (basic-alert-query)]
+                    Pulse                 [alert (basic-alert)]
+                    PulseCard             [_     (pulse-card alert card)]
+                    PulseChannel          [pc    (pulse-channel alert)]
+                    PulseChannelRecipient [_     (recipient pc :rasta)]]
+      (with-alert-setup
+        (let [original-alert-response ((user->client :crowberto) :get 200 (alert-question-url card))]
 
-        ;; A user can't delete an admin's alert
-        (api:delete! :rasta 403 alert)
+          ;; A user can't delete an admin's alert
+          (api:delete! :rasta 403 alert)
 
-        (array-map
-         :count-1  (count original-alert-response)
-         :response (api:delete! :crowberto 204 alert)
-         :count-2  (api:alert-question-count :rasta card))))))
+          (array-map
+           :count-1  (count original-alert-response)
+           :response (api:delete! :crowberto 204 alert)
+           :count-2  (api:alert-question-count :rasta card)))))))
 
 ;; An admin can delete a user's alert
 (expect
diff --git a/test/metabase/api/automagic_dashboards_test.clj b/test/metabase/api/automagic_dashboards_test.clj
index deb31aeb7e1a714dbe87f082be35e69c0b217512..7af0566573630e5447278bd5a3d41980c2acadab 100644
--- a/test/metabase/api/automagic_dashboards_test.clj
+++ b/test/metabase/api/automagic_dashboards_test.clj
@@ -1,7 +1,5 @@
 (ns metabase.api.automagic-dashboards-test
   (:require [expectations :refer :all]
-            [metabase.api.common :as api]
-            [metabase.api.card-test :refer [with-cards-in-readable-collection]]
             [metabase.automagic-dashboards.core :as magic]
             [metabase.models
              [card :refer [Card]]
@@ -9,28 +7,14 @@
              [metric :refer [Metric]]
              [permissions :as perms]
              [permissions-group :as perms-group]
-             [segment :refer [Segment]]
-             [user :as user]]
-            [metabase.test.data :as data]
+             [segment :refer [Segment]]]
+            [metabase.test
+             [automagic-dashboards :refer :all]
+             [data :as data]
+             [util :as tu]]
             [metabase.test.data.users :as test-users]
-            [metabase.test.util :as tu]
             [toucan.util.test :as tt]))
 
-(defmacro with-rasta
-  "Execute body with rasta as the current user."
-  [& body]
-  `(binding [api/*current-user-id*              (test-users/user->id :rasta)
-             api/*current-user-permissions-set* (-> :rasta
-                                                    test-users/user->id
-                                                    user/permissions-set
-                                                    atom)]
-     ~@body))
-
-(defmacro ^:private with-dashboard-cleanup
-  [& body]
-  `(tu/with-model-cleanup ['~'Card '~'Dashboard '~'Collection '~'DashboardCard]
-     ~@body))
-
 (defn- api-call
   ([template args] (api-call template args (constantly true)))
   ([template args revoke-fn]
@@ -40,13 +24,16 @@
          (and (some? ((test-users/user->client :rasta) :get 200 api-endpoint))
               (try
                 (do
-                  (perms/delete-related-permissions! (perms-group/all-users) (perms/object-path (data/id)))
+                  (perms/revoke-permissions! (perms-group/all-users) (data/id))
                   (revoke-fn)
                   (= ((test-users/user->client :rasta) :get 403 api-endpoint)
                      "You don't have permissions to do that."))
                 (finally
                   (perms/grant-permissions! (perms-group/all-users) (perms/object-path (data/id)))))))))))
 
+
+;;; ------------------- X-ray  -------------------
+
 (expect (api-call "table/%s" [(data/id :venues)]))
 (expect (api-call "table/%s/rule/example/indepth" [(data/id :venues)]))
 
@@ -59,7 +46,7 @@
 
 (expect
   (tt/with-temp* [Segment [{segment-id :id} {:table_id (data/id :venues)
-                                             :definition {:filter [:> [:field-id-id (data/id :venues :price)] 10]}}]]
+                                             :definition {:filter [:> [:field-id (data/id :venues :price)] 10]}}]]
     (api-call "segment/%s" [segment-id])))
 
 (expect
@@ -75,53 +62,56 @@
   (perms/revoke-collection-permissions! (perms-group/all-users) collection-id))
 
 (expect
-  (tt/with-temp* [Collection [{collection-id :id}]
-                  Card [{card-id :id} {:table_id      (data/id :venues)
-                                       :collection_id collection-id
-                                       :dataset_query {:query {:filter [:> [:field-id (data/id :venues :price)] 10]
-                                                               :source_table (data/id :venues)}
-                                                       :type :query
-                                                       :database (data/id)}}]]
-    (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection-id)
-    (api-call "question/%s" [card-id] #(revoke-collection-permissions! collection-id))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [{collection-id :id}]
+                    Card [{card-id :id} {:table_id      (data/id :venues)
+                                         :collection_id collection-id
+                                         :dataset_query {:query    {:filter       [:> [:field-id (data/id :venues :price)] 10]
+                                                                    :source-table (data/id :venues)}
+                                                         :type     :query
+                                                         :database (data/id)}}]]
+      (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection-id)
+      (api-call "question/%s" [card-id] #(revoke-collection-permissions! collection-id)))))
 
 (expect
-  (tt/with-temp* [Collection [{collection-id :id}]
-                  Card [{card-id :id} {:table_id      (data/id :venues)
-                                       :collection_id collection-id
-                                       :dataset_query {:query {:filter [:> [:field-id (data/id :venues :price)] 10]
-                                                               :source_table (data/id :venues)}
-                                                       :type :query
-                                                       :database (data/id)}}]]
-    (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection-id)
-    (api-call "question/%s/cell/%s" [card-id (->> [:> [:field-id (data/id :venues :price)] 5]
-                                                  (#'magic/encode-base64-json))]
-              #(revoke-collection-permissions! collection-id))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [{collection-id :id}]
+                    Card [{card-id :id} {:table_id      (data/id :venues)
+                                         :collection_id collection-id
+                                         :dataset_query {:query    {:filter       [:> [:field-id (data/id :venues :price)] 10]
+                                                                    :source-table (data/id :venues)}
+                                                         :type     :query
+                                                         :database (data/id)}}]]
+      (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection-id)
+      (api-call "question/%s/cell/%s" [card-id (->> [:> [:field-id (data/id :venues :price)] 5]
+                                                    (#'magic/encode-base64-json))]
+                #(revoke-collection-permissions! collection-id)))))
 
 (expect
-  (tt/with-temp* [Collection [{collection-id :id}]
-                  Card [{card-id :id} {:table_id      (data/id :venues)
-                                       :collection_id collection-id
-                                       :dataset_query {:query {:filter [:> [:field-id (data/id :venues :price)] 10]
-                                                               :source_table (data/id :venues)}
-                                                       :type :query
-                                                       :database (data/id)}}]]
-    (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection-id)
-    (api-call "question/%s/cell/%s/rule/example/indepth"
-              [card-id (->> [:> [:field-id (data/id :venues :price)] 5]
-                            (#'magic/encode-base64-json))]
-              #(revoke-collection-permissions! collection-id))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [{collection-id :id}]
+                    Card [{card-id :id} {:table_id      (data/id :venues)
+                                         :collection_id collection-id
+                                         :dataset_query {:query    {:filter       [:> [:field-id (data/id :venues :price)] 10]
+                                                                    :source-table (data/id :venues)}
+                                                         :type     :query
+                                                         :database (data/id)}}]]
+      (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection-id)
+      (api-call "question/%s/cell/%s/rule/example/indepth"
+                [card-id (->> [:> [:field-id (data/id :venues :price)] 5]
+                              (#'magic/encode-base64-json))]
+                #(revoke-collection-permissions! collection-id)))))
 
 
 (expect (api-call "adhoc/%s" [(->> {:query {:filter [:> [:field-id (data/id :venues :price)] 10]
-                                            :source_table (data/id :venues)}
+                                            :source-table (data/id :venues)}
                                     :type :query
                                     :database (data/id)}
                                    (#'magic/encode-base64-json))]))
 
 (expect (api-call "adhoc/%s/cell/%s"
                   [(->> {:query {:filter [:> [:field-id (data/id :venues :price)] 10]
-                                 :source_table (data/id :venues)}
+                                 :source-table (data/id :venues)}
                          :type :query
                          :database (data/id)}
                         (#'magic/encode-base64-json))
@@ -130,9 +120,37 @@
 
 (expect (api-call "adhoc/%s/cell/%s/rule/example/indepth"
                   [(->> {:query {:filter [:> [:field-id (data/id :venues :price)] 10]
-                                 :source_table (data/id :venues)}
+                                 :source-table (data/id :venues)}
                          :type :query
                          :database (data/id)}
                         (#'magic/encode-base64-json))
                    (->> [:> [:field-id (data/id :venues :price)] 5]
                         (#'magic/encode-base64-json))]))
+
+
+;;; ------------------- Comparisons -------------------
+
+(def ^:private segment {:table_id (data/id :venues)
+                        :definition {:filter [:> [:field-id (data/id :venues :price)] 10]}})
+
+(expect
+  (tt/with-temp* [Segment [{segment-id :id} segment]]
+    (api-call "table/%s/compare/segment/%s"
+              [(data/id :venues) segment-id])))
+
+(expect
+  (tt/with-temp* [Segment [{segment-id :id} segment]]
+    (api-call "table/%s/rule/example/indepth/compare/segment/%s"
+              [(data/id :venues) segment-id])))
+
+(expect
+  (tt/with-temp* [Segment [{segment-id :id} segment]]
+    (api-call "adhoc/%s/cell/%s/compare/segment/%s"
+              [(->> {:query {:filter [:> [:field-id (data/id :venues :price)] 10]
+                             :source-table (data/id :venues)}
+                     :type :query
+                     :database (data/id)}
+                    (#'magic/encode-base64-json))
+               (->> [:= [:field-id (data/id :venues :price)] 15]
+                    (#'magic/encode-base64-json))
+               segment-id])))
diff --git a/test/metabase/api/card_test.clj b/test/metabase/api/card_test.clj
index 3c8d18d77ddfc8cfdbecf6ab41be8dbb3aa2af63..408f811150e0f245757be98ee87d695435328feb 100644
--- a/test/metabase/api/card_test.clj
+++ b/test/metabase/api/card_test.clj
@@ -57,8 +57,8 @@
    (mbql-count-query (data/id) (data/id :venues)))
   ([db-or-id table-or-id]
    {:database (u/get-id db-or-id)
-    :type     "query"
-    :query    {:source-table (u/get-id table-or-id), :aggregation {:aggregation-type "count"}}}))
+    :type     :query
+    :query    {:source-table (u/get-id table-or-id), :aggregation [[:count]]}}))
 
 (defn- card-with-name-and-query
   ([]
@@ -90,16 +90,17 @@
 
 
 (defn do-with-cards-in-a-collection [card-or-cards-or-ids grant-perms-fn! f]
-  (tt/with-temp Collection [collection]
-    ;; put all the Card(s) in our temp `collection`
-    (doseq [card-or-id (if (sequential? card-or-cards-or-ids)
-                         card-or-cards-or-ids
-                         [card-or-cards-or-ids])]
-      (db/update! Card (u/get-id card-or-id) {:collection_id (u/get-id collection)}))
-    ;; now use `grant-perms-fn!` to grant appropriate perms
-    (grant-perms-fn! (perms-group/all-users) collection)
-    ;; call (f)
-    (f)))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [collection]
+      ;; put all the Card(s) in our temp `collection`
+      (doseq [card-or-id (if (sequential? card-or-cards-or-ids)
+                           card-or-cards-or-ids
+                           [card-or-cards-or-ids])]
+        (db/update! Card (u/get-id card-or-id) {:collection_id (u/get-id collection)}))
+      ;; now use `grant-perms-fn!` to grant appropriate perms
+      (grant-perms-fn! (perms-group/all-users) collection)
+      ;; call (f)
+      (f))))
 
 (defmacro with-cards-in-readable-collection
   "Execute `body` with `card-or-cards-or-ids` added to a temporary Collection that All Users have read permissions for."
@@ -123,7 +124,7 @@
                         {:database (u/get-id db)
                          :type     :native
                          :native   {:query         "SELECT COUNT(*) FROM VENUES WHERE CATEGORY_ID = {{category}};"
-                                    :template_tags {:category {:id           "a9001580-3bcc-b827-ce26-1dbc82429163"
+                                    :template-tags {:category {:id           "a9001580-3bcc-b827-ce26-1dbc82429163"
                                                                :name         "category"
                                                                :display_name "Category"
                                                                :type         "number"
@@ -260,18 +261,16 @@
 
 ;; Test that we can make a card
 (let [card-name (random-name)]
-  (tt/expect-with-temp [Database   [db]
-                        Table      [table {:db_id (u/get-id db)}]
-                        Collection [collection]]
+  (expect
     (merge card-defaults
            {:name                   card-name
-            :collection_id          (u/get-id collection)
-            :collection             collection
+            :collection_id          true
+            :collection             true
             :creator_id             (user->id :rasta)
-            :dataset_query          (mbql-count-query (u/get-id db) (u/get-id table))
+            :dataset_query          true
             :visualization_settings {:global {:title nil}}
-            :database_id            (u/get-id db) ; these should be inferred automatically
-            :table_id               (u/get-id table)
+            :database_id            true
+            :table_id               true
             :can_write              true
             :dashboard_count        0
             :read_permissions       nil
@@ -285,12 +284,21 @@
                                        :date_joined  $
                                        :email        "rasta@metabase.com"
                                        :id           $})})
-    (tu/with-model-cleanup [Card]
-      (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
-      (-> ((user->client :rasta) :post 200 "card"
-           (assoc (card-with-name-and-query card-name (mbql-count-query (u/get-id db) (u/get-id table)))
-             :collection_id (u/get-id collection)))
-          (dissoc :created_at :updated_at :id)))))
+    (tu/with-non-admin-groups-no-root-collection-perms
+      (tt/with-temp* [Database   [db]
+                      Table      [table {:db_id (u/get-id db)}]
+                      Collection [collection]]
+        (tu/with-model-cleanup [Card]
+          (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
+          (-> ((user->client :rasta) :post 200 "card"
+               (assoc (card-with-name-and-query card-name (mbql-count-query (u/get-id db) (u/get-id table)))
+                 :collection_id (u/get-id collection)))
+              (dissoc :created_at :updated_at :id)
+              (update :table_id integer?)
+              (update :database_id integer?)
+              (update :collection_id integer?)
+              (update :dataset_query map?)
+              (update :collection map?)))))))
 
 ;; Make sure when saving a Card the query metadata is saved (if correct)
 (expect
@@ -298,68 +306,74 @@
     :display_name "Count Chocula"
     :name         "count_chocula"
     :special_type "type/Number"}]
-  (let [metadata [{:base_type    :type/Integer
-                   :display_name "Count Chocula"
-                   :name         "count_chocula"
-                   :special_type :type/Number}]
-        card-name (tu/random-name)]
-    (tt/with-temp Collection [collection]
-      (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
-      (tu/with-model-cleanup [Card]
-        ;; create a card with the metadata
-        ((user->client :rasta) :post 200 "card"
-         (assoc (card-with-name-and-query card-name)
-           :collection_id      (u/get-id collection)
-           :result_metadata    metadata
-           :metadata_checksum  (#'results-metadata/metadata-checksum metadata)))
-        ;; now check the metadata that was saved in the DB
-        (db/select-one-field :result_metadata Card :name card-name)))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (let [metadata  [{:base_type    :type/Integer
+                      :display_name "Count Chocula"
+                      :name         "count_chocula"
+                      :special_type :type/Number}]
+          card-name (tu/random-name)]
+      (tt/with-temp Collection [collection]
+        (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
+        (tu/with-model-cleanup [Card]
+          ;; create a card with the metadata
+          ((user->client :rasta) :post 200 "card"
+           (assoc (card-with-name-and-query card-name)
+             :collection_id      (u/get-id collection)
+             :result_metadata    metadata
+             :metadata_checksum  (#'results-metadata/metadata-checksum metadata)))
+          ;; now check the metadata that was saved in the DB
+          (db/select-one-field :result_metadata Card :name card-name))))))
 
 ;; make sure when saving a Card the correct query metadata is fetched (if incorrect)
 (expect
   [{:base_type    "type/Integer"
     :display_name "count"
     :name         "count"
-    :special_type "type/Number"}]
-  (let [metadata [{:base_type    :type/Integer
-                   :display_name "Count Chocula"
-                   :name         "count_chocula"
-                   :special_type :type/Number}]
-        card-name (tu/random-name)]
-    (tt/with-temp Collection [collection]
-      (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
-      (tu/with-model-cleanup [Card]
-        ;; create a card with the metadata
-        ((user->client :rasta) :post 200 "card"
-         (assoc (card-with-name-and-query card-name)
-           :collection_id      (u/get-id collection)
-           :result_metadata    metadata
-           :metadata_checksum  "ABCDEF")) ; bad checksum
-        ;; now check the correct metadata was fetched and was saved in the DB
-        (db/select-one-field :result_metadata Card :name card-name)))))
+    :special_type "type/Quantity"
+    :fingerprint  {:global {:distinct-count 1},
+                   :type   {:type/Number {:min 100.0, :max 100.0, :avg 100.0}}}}]
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (let [metadata  [{:base_type    :type/Integer
+                      :display_name "Count Chocula"
+                      :name         "count_chocula"
+                      :special_type :type/Quantity}]
+          card-name (tu/random-name)]
+      (tt/with-temp Collection [collection]
+        (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
+        (tu/with-model-cleanup [Card]
+          ;; create a card with the metadata
+          ((user->client :rasta) :post 200 "card"
+           (assoc (card-with-name-and-query card-name)
+             :collection_id      (u/get-id collection)
+             :result_metadata    metadata
+             :metadata_checksum  "ABCDEF")) ; bad checksum
+          ;; now check the correct metadata was fetched and was saved in the DB
+          (db/select-one-field :result_metadata Card :name card-name))))))
 
 ;; Make sure we can create a Card with a Collection position
 (expect
   #metabase.models.card.CardInstance{:collection_id true, :collection_position 1}
-  (tu/with-model-cleanup [Card]
-    (let [card-name (tu/random-name)]
-      (tt/with-temp Collection [collection]
-        (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
-        ((user->client :rasta) :post 200 "card" (assoc (card-with-name-and-query card-name)
-                                                  :collection_id (u/get-id collection), :collection_position 1))
-        (some-> (db/select-one [Card :collection_id :collection_position] :name card-name)
-                (update :collection_id (partial = (u/get-id collection))))))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tu/with-model-cleanup [Card]
+      (let [card-name (tu/random-name)]
+        (tt/with-temp Collection [collection]
+          (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
+          ((user->client :rasta) :post 200 "card" (assoc (card-with-name-and-query card-name)
+                                                    :collection_id (u/get-id collection), :collection_position 1))
+          (some-> (db/select-one [Card :collection_id :collection_position] :name card-name)
+                  (update :collection_id (partial = (u/get-id collection)))))))))
 
 ;; ...but not if we don't have permissions for the Collection
 (expect
   nil
-  (tu/with-model-cleanup [Card]
-    (let [card-name (tu/random-name)]
-      (tt/with-temp Collection [collection]
-        ((user->client :rasta) :post 403 "card" (assoc (card-with-name-and-query card-name)
-                                                  :collection_id (u/get-id collection), :collection_position 1))
-        (some-> (db/select-one [Card :collection_id :collection_position] :name card-name)
-                (update :collection_id (partial = (u/get-id collection))))))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tu/with-model-cleanup [Card]
+      (let [card-name (tu/random-name)]
+        (tt/with-temp Collection [collection]
+          ((user->client :rasta) :post 403 "card" (assoc (card-with-name-and-query card-name)
+                                                    :collection_id (u/get-id collection), :collection_position 1))
+          (some-> (db/select-one [Card :collection_id :collection_position] :name card-name)
+                  (update :collection_id (partial = (u/get-id collection)))))))))
 
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
@@ -388,12 +402,12 @@
                                        :email        "rasta@metabase.com"
                                        :id           $})
             :updated_at             $
-            :dataset_query          $
+            :dataset_query          (tu/obj->json->obj (:dataset_query card))
             :read_permissions       nil
             :id                     $
             :display                "table"
             :visualization_settings {}
-            :can_write              false
+            :can_write              true
             :created_at             $
             :database_id            (u/get-id db) ; these should be inferred from the dataset_query
             :table_id               (u/get-id table)
@@ -406,13 +420,14 @@
 ;; Check that a user without permissions isn't allowed to fetch the card
 (expect
   "You don't have permissions to do that."
-  (tt/with-temp* [Database [db]
-                  Table    [table    {:db_id (u/get-id db)}]
-                  Card     [card              {:dataset_query (mbql-count-query (u/get-id db) (u/get-id table))}]]
-    ;; revoke permissions for default group to this database
-    (perms/delete-related-permissions! (perms-group/all-users) (perms/object-path (u/get-id db)))
-    ;; now a non-admin user shouldn't be able to fetch this card
-    ((user->client :rasta) :get 403 (str "card/" (u/get-id card)))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Database [db]
+                    Table    [table {:db_id (u/get-id db)}]
+                    Card     [card  {:dataset_query (mbql-count-query (u/get-id db) (u/get-id table))}]]
+      ;; revoke permissions for default group to this database
+      (perms/revoke-permissions! (perms-group/all-users) (u/get-id db))
+      ;; now a non-admin user shouldn't be able to fetch this card
+      ((user->client :rasta) :get 403 (str "card/" (u/get-id card))))))
 
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
@@ -453,10 +468,11 @@
 ;; we shouldn't be able to update archived status if we don't have collection *write* perms
 (expect
   "You don't have permissions to do that."
-  (tt/with-temp* [Collection [collection]
-                  Card       [card {:collection_id (u/get-id collection)}]]
-    (perms/grant-collection-read-permissions! (perms-group/all-users) collection)
-    ((user->client :rasta) :put 403 (str "card/" (u/get-id card)) {:archived true})))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [collection]
+                    Card       [card {:collection_id (u/get-id collection)}]]
+      (perms/grant-collection-read-permissions! (perms-group/all-users) collection)
+      ((user->client :rasta) :put 403 (str "card/" (u/get-id card)) {:archived true}))))
 
 ;; Can we clear the description of a Card? (#4738)
 (expect
@@ -521,11 +537,13 @@
   [{:base_type    "type/Integer"
     :display_name "count"
     :name         "count"
-    :special_type "type/Number"}]
+    :special_type "type/Quantity"
+    :fingerprint  {:global {:distinct-count 1},
+                   :type   {:type/Number {:min 100.0, :max 100.0, :avg 100.0}}}}]
   (let [metadata [{:base_type    :type/Integer
                    :display_name "Count Chocula"
                    :name         "count_chocula"
-                   :special_type :type/Number}]]
+                   :special_type :type/Quantity}]]
     (tt/with-temp Card [card]
       (with-cards-in-writeable-collection card
         ;; update the Card's query
@@ -557,19 +575,22 @@
 ;; ...we shouldn't be able to if we don't have permissions for the Collection
 (expect
   nil
-  (tt/with-temp* [Collection [collection]
-                  Card       [card {:collection_id (u/get-id collection)}]]
-    ((user->client :rasta) :put 403 (str "card/" (u/get-id card))
-     {:collection_position 1})
-    (db/select-one-field :collection_position Card :id (u/get-id card))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [collection]
+                    Card       [card {:collection_id (u/get-id collection)}]]
+      ((user->client :rasta) :put 403 (str "card/" (u/get-id card))
+       {:collection_position 1})
+      (db/select-one-field :collection_position Card :id (u/get-id card)))))
 
 (expect
   1
-  (tt/with-temp* [Collection [collection]
-                  Card       [card {:collection_id (u/get-id collection), :collection_position 1}]]
-    ((user->client :rasta) :put 403 (str "card/" (u/get-id card))
-     {:collection_position nil})
-    (db/select-one-field :collection_position Card :id (u/get-id card))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [collection]
+                    Card       [card {:collection_id (u/get-id collection), :collection_position 1}]]
+      ((user->client :rasta) :put 403 (str "card/" (u/get-id card))
+       {:collection_position nil})
+      (db/select-one-field :collection_position Card :id (u/get-id card)))))
+
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                      UPDATING THE POSITION OF A CARDS                                          |
@@ -602,15 +623,16 @@
    "a" 2
    "b" 3
    "d" 4}
-  (tt/with-temp Collection [collection]
-    (with-ordered-items collection [Card a
-                                    Card b
-                                    Card c
-                                    Card d]
-      (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
-      ((user->client :rasta) :put 200 (str "card/" (u/get-id c))
-       {:collection_position 1})
-      (get-name->collection-position :rasta collection))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [collection]
+      (with-ordered-items collection [Card a
+                                      Card b
+                                      Card c
+                                      Card d]
+        (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
+        ((user->client :rasta) :put 200 (str "card/" (u/get-id c))
+         {:collection_position 1})
+        (get-name->collection-position :rasta collection)))))
 
 ;; Change the position of the 4th card to 1st, all other cards should inc their position
 (expect
@@ -618,15 +640,16 @@
    "a" 2
    "b" 3
    "c" 4}
-  (tt/with-temp Collection [collection]
-    (with-ordered-items collection [Dashboard a
-                                    Dashboard b
-                                    Pulse     c
-                                    Card      d]
-      (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
-      ((user->client :rasta) :put 200 (str "card/" (u/get-id d))
-       {:collection_position 1})
-      (get-name->collection-position :rasta collection))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [collection]
+      (with-ordered-items collection [Dashboard a
+                                      Dashboard b
+                                      Pulse     c
+                                      Card      d]
+        (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
+        ((user->client :rasta) :put 200 (str "card/" (u/get-id d))
+         {:collection_position 1})
+        (get-name->collection-position :rasta collection)))))
 
 ;; Change the position of the 1st card to the 4th, all of the other items dec
 (expect
@@ -634,15 +657,16 @@
    "c" 2
    "d" 3
    "a" 4}
-  (tt/with-temp Collection [collection]
-    (with-ordered-items collection [Card      a
-                                    Dashboard b
-                                    Pulse     c
-                                    Dashboard d]
-      (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
-      ((user->client :rasta) :put 200 (str "card/" (u/get-id a))
-       {:collection_position 4})
-      (get-name->collection-position :rasta collection))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [collection]
+      (with-ordered-items collection [Card      a
+                                      Dashboard b
+                                      Pulse     c
+                                      Dashboard d]
+        (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
+        ((user->client :rasta) :put 200 (str "card/" (u/get-id a))
+         {:collection_position 4})
+        (get-name->collection-position :rasta collection)))))
 
 ;; Change the position of a card from nil to 2nd, should adjust the existing items
 (expect
@@ -650,16 +674,17 @@
    "b" 2
    "c" 3
    "d" 4}
-  (tt/with-temp* [Collection [{coll-id :id :as collection}]
-                  Card       [_ {:name "a", :collection_id coll-id, :collection_position 1}]
-                  ;; Card b does not start with a collection_position
-                  Card       [b {:name "b", :collection_id coll-id}]
-                  Dashboard  [_ {:name "c", :collection_id coll-id, :collection_position 2}]
-                  Card       [_ {:name "d", :collection_id coll-id, :collection_position 3}]]
-    (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
-    ((user->client :rasta) :put 200 (str "card/" (u/get-id b))
-     {:collection_position 2})
-    (get-name->collection-position :rasta coll-id)))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [{coll-id :id :as collection}]
+                    Card       [_ {:name "a", :collection_id coll-id, :collection_position 1}]
+                    ;; Card b does not start with a collection_position
+                    Card       [b {:name "b", :collection_id coll-id}]
+                    Dashboard  [_ {:name "c", :collection_id coll-id, :collection_position 2}]
+                    Card       [_ {:name "d", :collection_id coll-id, :collection_position 3}]]
+      (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
+      ((user->client :rasta) :put 200 (str "card/" (u/get-id b))
+       {:collection_position 2})
+      (get-name->collection-position :rasta coll-id))))
 
 ;; Update an existing card to no longer have a position, should dec items after it's position
 (expect
@@ -667,15 +692,16 @@
    "b" nil
    "c" 2
    "d" 3}
-  (tt/with-temp Collection [collection]
-    (with-ordered-items collection [Card      a
-                                    Card      b
-                                    Dashboard c
-                                    Pulse     d]
-      (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
-      ((user->client :rasta) :put 200 (str "card/" (u/get-id b))
-       {:collection_position nil})
-      (get-name->collection-position :rasta collection))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [collection]
+      (with-ordered-items collection [Card      a
+                                      Card      b
+                                      Dashboard c
+                                      Pulse     d]
+        (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
+        ((user->client :rasta) :put 200 (str "card/" (u/get-id b))
+         {:collection_position nil})
+        (get-name->collection-position :rasta collection)))))
 
 ;; Change the collection the card is in, leave the position, should cause old and new collection to have their
 ;; positions updated
@@ -688,22 +714,23 @@
    {"e" 1
     "g" 2
     "h" 3}]
-  (tt/with-temp* [Collection [collection-1]
-                  Collection [collection-2]]
-    (with-ordered-items collection-1 [Dashboard a
-                                      Card      b
-                                      Pulse     c
-                                      Dashboard d]
-      (with-ordered-items collection-2 [Pulse     e
-                                        Card      f
-                                        Card      g
-                                        Dashboard h]
-        (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection-1)
-        (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection-2)
-        ((user->client :rasta) :put 200 (str "card/" (u/get-id f))
-         {:collection_id (u/get-id collection-1)})
-        [(get-name->collection-position :rasta collection-1)
-         (get-name->collection-position :rasta collection-2)]))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [collection-1]
+                    Collection [collection-2]]
+      (with-ordered-items collection-1 [Dashboard a
+                                        Card      b
+                                        Pulse     c
+                                        Dashboard d]
+        (with-ordered-items collection-2 [Pulse     e
+                                          Card      f
+                                          Card      g
+                                          Dashboard h]
+          (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection-1)
+          (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection-2)
+          ((user->client :rasta) :put 200 (str "card/" (u/get-id f))
+           {:collection_id (u/get-id collection-1)})
+          [(get-name->collection-position :rasta collection-1)
+           (get-name->collection-position :rasta collection-2)])))))
 
 ;; Change the collection and the position, causing both collections and the updated card to have their order changed
 (expect
@@ -715,22 +742,23 @@
    {"e" 1
     "f" 2
     "g" 3}]
-  (tt/with-temp* [Collection [collection-1]
-                  Collection [collection-2]]
-    (with-ordered-items collection-1 [Pulse     a
-                                      Pulse     b
-                                      Dashboard c
-                                      Dashboard d]
-      (with-ordered-items collection-2 [Dashboard e
-                                        Dashboard f
-                                        Pulse     g
-                                        Card      h]
-        (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection-1)
-        (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection-2)
-        ((user->client :rasta) :put 200 (str "card/" (u/get-id h))
-         {:collection_position 1, :collection_id (u/get-id collection-1)})
-        [(get-name->collection-position :rasta collection-1)
-         (get-name->collection-position :rasta collection-2)]))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [collection-1]
+                    Collection [collection-2]]
+      (with-ordered-items collection-1 [Pulse     a
+                                        Pulse     b
+                                        Dashboard c
+                                        Dashboard d]
+        (with-ordered-items collection-2 [Dashboard e
+                                          Dashboard f
+                                          Pulse     g
+                                          Card      h]
+          (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection-1)
+          (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection-2)
+          ((user->client :rasta) :put 200 (str "card/" (u/get-id h))
+           {:collection_position 1, :collection_id (u/get-id collection-1)})
+          [(get-name->collection-position :rasta collection-1)
+           (get-name->collection-position :rasta collection-2)])))))
 
 ;; Add a new card to an existing collection at position 1, will cause all existing positions to increment by 1
 (expect
@@ -743,19 +771,20 @@
     "b" 2
     "c" 3
     "d" 4}]
-  (tt/with-temp Collection [collection]
-    (tu/with-model-cleanup [Card]
-      (with-ordered-items collection [Dashboard b
-                                      Pulse     c
-                                      Card      d]
-        (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
-        [(get-name->collection-position :rasta collection)
-         (do
-           ((user->client :rasta) :post 200 "card"
-            (merge (card-with-name-and-query "a")
-                   {:collection_id       (u/get-id collection)
-                    :collection_position 1}))
-           (get-name->collection-position :rasta collection))]))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [collection]
+      (tu/with-model-cleanup [Card]
+        (with-ordered-items collection [Dashboard b
+                                        Pulse     c
+                                        Card      d]
+          (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
+          [(get-name->collection-position :rasta collection)
+           (do
+             ((user->client :rasta) :post 200 "card"
+              (merge (card-with-name-and-query "a")
+                     {:collection_id       (u/get-id collection)
+                      :collection_position 1}))
+             (get-name->collection-position :rasta collection))])))))
 
 ;; Add a new card to the end of an existing collection
 (expect
@@ -768,19 +797,20 @@
     "b" 2
     "c" 3
     "d" 4}]
-  (tt/with-temp Collection [collection]
-    (tu/with-model-cleanup [Card]
-      (with-ordered-items collection [Card      a
-                                      Dashboard b
-                                      Pulse     c]
-        (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
-        [(get-name->collection-position :rasta collection)
-         (do
-           ((user->client :rasta) :post 200 "card"
-            (merge (card-with-name-and-query "d")
-                   {:collection_id (u/get-id collection)
-                    :collection_position 4}))
-           (get-name->collection-position :rasta collection))]))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [collection]
+      (tu/with-model-cleanup [Card]
+        (with-ordered-items collection [Card      a
+                                        Dashboard b
+                                        Pulse     c]
+          (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
+          [(get-name->collection-position :rasta collection)
+           (do
+             ((user->client :rasta) :post 200 "card"
+              (merge (card-with-name-and-query "d")
+                     {:collection_id       (u/get-id collection)
+                      :collection_position 4}))
+             (get-name->collection-position :rasta collection))])))))
 
 ;; When adding a new card to a collection that does not have a position, it should not change existing positions
 (expect
@@ -793,19 +823,20 @@
     "b" 2
     "c" 3
     "d" nil}]
-  (tt/with-temp Collection [collection]
-    (tu/with-model-cleanup [Card]
-      (with-ordered-items collection [Pulse     a
-                                      Card      b
-                                      Dashboard c]
-        (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
-        [(get-name->collection-position :rasta collection)
-         (do
-           ((user->client :rasta) :post 200 "card"
-            (merge (card-with-name-and-query "d")
-                   {:collection_id       (u/get-id collection)
-                    :collection_position nil}))
-           (get-name->collection-position :rasta collection))]))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [collection]
+      (tu/with-model-cleanup [Card]
+        (with-ordered-items collection [Pulse     a
+                                        Card      b
+                                        Dashboard c]
+          (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
+          [(get-name->collection-position :rasta collection)
+           (do
+             ((user->client :rasta) :post 200 "card"
+              (merge (card-with-name-and-query "d")
+                     {:collection_id       (u/get-id collection)
+                      :collection_position nil}))
+             (get-name->collection-position :rasta collection))])))))
 
 (expect
   {"d" 1
@@ -814,17 +845,19 @@
    "c" 4
    "e" 5
    "f" 6}
-  (tt/with-temp Collection [collection]
-    (with-ordered-items collection [Dashboard a
-                                    Dashboard b
-                                    Card      c
-                                    Card      d
-                                    Pulse     e
-                                    Pulse     f]
-      (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
-      ((user->client :rasta) :put 200 (str "card/" (u/get-id d))
-       {:collection_position 1, :collection_id (u/get-id collection)})
-      (name->position ((user->client :rasta) :get 200 (format "collection/%s/items" (u/get-id collection)))))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [collection]
+      (with-ordered-items collection [Dashboard a
+                                      Dashboard b
+                                      Card      c
+                                      Card      d
+                                      Pulse     e
+                                      Pulse     f]
+        (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
+        ((user->client :rasta) :put 200 (str "card/" (u/get-id d))
+         {:collection_position 1, :collection_id (u/get-id collection)})
+        (name->position ((user->client :rasta) :get 200 (format "collection/%s/items" (u/get-id collection))))))))
+
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                        Card updates that impact alerts                                         |
@@ -1137,23 +1170,25 @@
 
 ;; Make sure we can create a card and specify its `collection_id` at the same time
 (expect
-  (tt/with-temp Collection [collection]
-    (tu/with-model-cleanup [Card]
-      (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
-      (let [card ((user->client :rasta) :post 200 "card"
-                  (assoc (card-with-name-and-query)
-                    :collection_id (u/get-id collection)))]
-        (= (db/select-one-field :collection_id Card :id (u/get-id card))
-           (u/get-id collection))))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [collection]
+      (tu/with-model-cleanup [Card]
+        (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
+        (let [card ((user->client :rasta) :post 200 "card"
+                    (assoc (card-with-name-and-query)
+                      :collection_id (u/get-id collection)))]
+          (= (db/select-one-field :collection_id Card :id (u/get-id card))
+             (u/get-id collection)))))))
 
 ;; Make sure we card creation fails if we try to set a `collection_id` we don't have permissions for
 (expect
   "You don't have permissions to do that."
-  (tu/with-model-cleanup [Card]
-    (tt/with-temp Collection [collection]
-      ((user->client :rasta) :post 403 "card"
-       (assoc (card-with-name-and-query)
-         :collection_id (u/get-id collection))))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tu/with-model-cleanup [Card]
+      (tt/with-temp Collection [collection]
+        ((user->client :rasta) :post 403 "card"
+         (assoc (card-with-name-and-query)
+           :collection_id (u/get-id collection)))))))
 
 ;; Make sure we can change the `collection_id` of a Card if it's not in any collection
 (expect
@@ -1166,40 +1201,44 @@
 ;; Make sure we can still change *anything* for a Card if we don't have permissions for the Collection it belongs to
 (expect
   "You don't have permissions to do that."
-  (tt/with-temp* [Collection [collection]
-                  Card       [card       {:collection_id (u/get-id collection)}]]
-    ((user->client :rasta) :put 403 (str "card/" (u/get-id card)) {:name "Number of Blueberries Consumed Per Month"})))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [collection]
+                    Card       [card       {:collection_id (u/get-id collection)}]]
+      ((user->client :rasta) :put 403 (str "card/" (u/get-id card)) {:name "Number of Blueberries Consumed Per Month"}))))
 
 ;; Make sure that we can't change the `collection_id` of a Card if we don't have write permissions for the new
 ;; collection
 (expect
   "You don't have permissions to do that."
-  (tt/with-temp* [Collection [original-collection]
-                  Collection [new-collection]
-                  Card       [card                {:collection_id (u/get-id original-collection)}]]
-    (perms/grant-collection-readwrite-permissions! (perms-group/all-users) original-collection)
-    ((user->client :rasta) :put 403 (str "card/" (u/get-id card)) {:collection_id (u/get-id new-collection)})))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [original-collection]
+                    Collection [new-collection]
+                    Card       [card                {:collection_id (u/get-id original-collection)}]]
+      (perms/grant-collection-readwrite-permissions! (perms-group/all-users) original-collection)
+      ((user->client :rasta) :put 403 (str "card/" (u/get-id card)) {:collection_id (u/get-id new-collection)}))))
 
 ;; Make sure that we can't change the `collection_id` of a Card if we don't have write permissions for the current
 ;; collection
 (expect
   "You don't have permissions to do that."
-  (tt/with-temp* [Collection [original-collection]
-                  Collection [new-collection]
-                  Card       [card                {:collection_id (u/get-id original-collection)}]]
-    (perms/grant-collection-readwrite-permissions! (perms-group/all-users) new-collection)
-    ((user->client :rasta) :put 403 (str "card/" (u/get-id card)) {:collection_id (u/get-id new-collection)})))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [original-collection]
+                    Collection [new-collection]
+                    Card       [card                {:collection_id (u/get-id original-collection)}]]
+      (perms/grant-collection-readwrite-permissions! (perms-group/all-users) new-collection)
+      ((user->client :rasta) :put 403 (str "card/" (u/get-id card)) {:collection_id (u/get-id new-collection)}))))
 
 ;; But if we do have permissions for both, we should be able to change it.
 (expect
-  (tt/with-temp* [Collection [original-collection]
-                  Collection [new-collection]
-                  Card       [card                {:collection_id (u/get-id original-collection)}]]
-    (perms/grant-collection-readwrite-permissions! (perms-group/all-users) original-collection)
-    (perms/grant-collection-readwrite-permissions! (perms-group/all-users) new-collection)
-    ((user->client :rasta) :put 200 (str "card/" (u/get-id card)) {:collection_id (u/get-id new-collection)})
-    (= (db/select-one-field :collection_id Card :id (u/get-id card))
-       (u/get-id new-collection))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [original-collection]
+                    Collection [new-collection]
+                    Card       [card                {:collection_id (u/get-id original-collection)}]]
+      (perms/grant-collection-readwrite-permissions! (perms-group/all-users) original-collection)
+      (perms/grant-collection-readwrite-permissions! (perms-group/all-users) new-collection)
+      ((user->client :rasta) :put 200 (str "card/" (u/get-id card)) {:collection_id (u/get-id new-collection)})
+      (= (db/select-one-field :collection_id Card :id (u/get-id card))
+         (u/get-id new-collection)))))
 
 
 ;;; ------------------------------ Bulk Collections Update (POST /api/card/collections) ------------------------------
@@ -1263,32 +1302,35 @@
 (expect
   {:response    "You don't have permissions to do that."
    :collections [nil nil]}
-  (tt/with-temp* [Collection [collection]
-                  Card       [card-1]
-                  Card       [card-2]]
-    (POST-card-collections! :rasta 403 collection [card-1 card-2])))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [collection]
+                    Card       [card-1]
+                    Card       [card-2]]
+      (POST-card-collections! :rasta 403 collection [card-1 card-2]))))
 
 ;; Check that we aren't allowed to move Cards if we don't have permissions for source collection
 (expect
   {:response    "You don't have permissions to do that."
    :collections ["Horseshoe Collection" "Horseshoe Collection"]}
-  (tt/with-temp* [Collection [collection {:name "Horseshoe Collection"}]
-                  Card       [card-1     {:collection_id (u/get-id collection)}]
-                  Card       [card-2     {:collection_id (u/get-id collection)}]]
-    (POST-card-collections! :rasta 403 nil [card-1 card-2])))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [collection {:name "Horseshoe Collection"}]
+                    Card       [card-1     {:collection_id (u/get-id collection)}]
+                    Card       [card-2     {:collection_id (u/get-id collection)}]]
+      (POST-card-collections! :rasta 403 nil [card-1 card-2]))))
 
 ;; Check that we aren't allowed to move Cards if we don't have permissions for the Card
 (expect
   {:response    "You don't have permissions to do that."
    :collections [nil nil]}
-  (tt/with-temp* [Collection [collection]
-                  Database   [database]
-                  Table      [table      {:db_id (u/get-id database)}]
-                  Card       [card-1     {:dataset_query (mbql-count-query (u/get-id database) (u/get-id table))}]
-                  Card       [card-2     {:dataset_query (mbql-count-query (u/get-id database) (u/get-id table))}]]
-    (perms/revoke-permissions! (perms-group/all-users) (u/get-id database))
-    (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
-    (POST-card-collections! :rasta 403 collection [card-1 card-2])))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [collection]
+                    Database   [database]
+                    Table      [table      {:db_id (u/get-id database)}]
+                    Card       [card-1     {:dataset_query (mbql-count-query (u/get-id database) (u/get-id table))}]
+                    Card       [card-2     {:dataset_query (mbql-count-query (u/get-id database) (u/get-id table))}]]
+      (perms/revoke-permissions! (perms-group/all-users) (u/get-id database))
+      (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
+      (POST-card-collections! :rasta 403 collection [card-1 card-2]))))
 
 ;; Test that we can bulk move some Cards from one collection to another, while updating the collection position of the
 ;; old collection and the new collection
diff --git a/test/metabase/api/collection_test.clj b/test/metabase/api/collection_test.clj
index 8d352ebd3ec7dc110ba6b5db4f75fce34ed85456..59f31d776ee6de8f30e0bc78dab1fd5048411db0 100644
--- a/test/metabase/api/collection_test.clj
+++ b/test/metabase/api/collection_test.clj
@@ -1,6 +1,7 @@
 (ns metabase.api.collection-test
   "Tests for /api/collection endpoints."
-  (:require [expectations :refer :all]
+  (:require [clojure.string :as str]
+            [expectations :refer :all]
             [metabase
              [email-test :as et]
              [util :as u]]
@@ -28,14 +29,16 @@
 ;; check that we can get a basic list of collections
 ;; (for the purposes of test purposes remove the personal collections)
 (tt/expect-with-temp [Collection [collection]]
-  [(assoc (into {} collection) :can_write true)]
+  [{:parent_id nil, :effective_location nil, :effective_ancestors (), :can_write true, :name "Our analytics", :id "root"}
+   (assoc (into {} collection) :can_write true)]
   (for [collection ((user->client :crowberto) :get 200 "collection")
         :when (not (:personal_owner_id collection))]
     collection))
 
 ;; We should only see our own Personal Collections!
 (expect
-  ["Lucky Pigeon's Personal Collection"]
+  ["Our analytics"
+   "Lucky Pigeon's Personal Collection"]
   (do
     (collection-test/force-create-personal-collections!)
     ;; now fetch those Collections as the Lucky bird
@@ -43,7 +46,8 @@
 
 ;; ...unless we are *admins*
 (expect
-  ["Crowberto Corv's Personal Collection"
+  ["Our analytics"
+   "Crowberto Corv's Personal Collection"
    "Lucky Pigeon's Personal Collection"
    "Rasta Toucan's Personal Collection"
    "Trash Bird's Personal Collection"]
@@ -54,17 +58,20 @@
 
 ;; check that we don't see collections if we don't have permissions for them
 (expect
-  ["Collection 1"
+  ["Our analytics"
+   "Collection 1"
    "Rasta Toucan's Personal Collection"]
-  (tt/with-temp* [Collection [collection-1 {:name "Collection 1"}]
-                  Collection [collection-2 {:name "Collection 2"}]]
-    (perms/grant-collection-read-permissions! (group/all-users) collection-1)
-    (collection-test/force-create-personal-collections!)
-    (map :name ((user->client :rasta) :get 200 "collection"))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [collection-1 {:name "Collection 1"}]
+                    Collection [collection-2 {:name "Collection 2"}]]
+      (perms/grant-collection-read-permissions! (group/all-users) collection-1)
+      (collection-test/force-create-personal-collections!)
+      (map :name ((user->client :rasta) :get 200 "collection")))))
 
 ;; check that we don't see collections if they're archived
 (expect
-  ["Rasta Toucan's Personal Collection"
+  ["Our analytics"
+   "Rasta Toucan's Personal Collection"
    "Regular Collection"]
   (tt/with-temp* [Collection [collection-1 {:name "Archived Collection", :archived true}]
                   Collection [collection-2 {:name "Regular Collection"}]]
@@ -98,8 +105,9 @@
 ;; check that collections detail properly checks permissions
 (expect
   "You don't have permissions to do that."
-  (tt/with-temp Collection [collection]
-    ((user->client :rasta) :get 403 (str "collection/" (u/get-id collection)))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [collection]
+      ((user->client :rasta) :get 403 (str "collection/" (u/get-id collection))))))
 
 
 ;;; ----------------------------------------- Cards, Dashboards, and Pulses ------------------------------------------
@@ -110,8 +118,9 @@
   (tu/obj->json->obj
     [{:id                  (u/get-id card)
       :name                (:name card)
-      :description         nil
       :collection_position nil
+      :display            "table"
+      :description         nil
       :favorite            false
       :model               "card"}])
   (tu/obj->json->obj
@@ -119,12 +128,13 @@
 
 (defn- do-with-some-children-of-collection [collection-or-id-or-nil f]
   (collection-test/force-create-personal-collections!)
-  (let [collection-id-or-nil (when collection-or-id-or-nil
-                               (u/get-id collection-or-id-or-nil))]
-    (tt/with-temp* [Card       [_ {:name "Birthday Card",          :collection_id collection-id-or-nil}]
-                    Dashboard  [_ {:name "Dine & Dashboard",       :collection_id collection-id-or-nil}]
-                    Pulse      [_ {:name "Electro-Magnetic Pulse", :collection_id collection-id-or-nil}]]
-      (f))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (let [collection-id-or-nil (when collection-or-id-or-nil
+                                 (u/get-id collection-or-id-or-nil))]
+      (tt/with-temp* [Card       [_ {:name "Birthday Card", :collection_id collection-id-or-nil}]
+                      Dashboard  [_ {:name "Dine & Dashboard", :collection_id collection-id-or-nil}]
+                      Pulse      [_ {:name "Electro-Magnetic Pulse", :collection_id collection-id-or-nil}]]
+        (f)))))
 
 (defmacro ^:private with-some-children-of-collection {:style/indent 1} [collection-or-id-or-nil & body]
   `(do-with-some-children-of-collection ~collection-or-id-or-nil (fn [] ~@body)))
@@ -133,13 +143,16 @@
   (merge {:id true, :collection_position nil} item-map))
 
 (defn- collection-item [collection-name & {:as extra-keypairs}]
-  (merge {:id true, :description nil,
-          :model "collection", :name collection-name}
+  (merge {:id          true
+          :description nil
+          :can_write   (str/ends-with? collection-name "Personal Collection")
+          :model       "collection"
+          :name        collection-name}
          extra-keypairs))
 
 ;; check that you get to see the children as appropriate
 (expect
-  (map default-item [{:name "Birthday Card", :description nil, :favorite false, :model "card"}
+  (map default-item [{:name "Birthday Card", :description nil, :favorite false, :model "card", :display "table"}
                      {:name "Dine & Dashboard", :description nil, :model "dashboard"}
                      {:name "Electro-Magnetic Pulse", :model "pulse"}])
   (tt/with-temp Collection [collection {:name "Debt Collection"}]
@@ -177,8 +190,9 @@
    :can_write           true
    :name                "Lucky Pigeon's Personal Collection"
    :personal_owner_id   (user->id :lucky)
-   :effective_ancestors ()
+   :effective_ancestors [{:metabase.models.collection/is-root? true, :name "Our analytics", :id "root", :can_write true}]
    :effective_location  "/"
+   :parent_id           nil
    :id                  (u/get-id (collection/user->personal-collection (user->id :lucky)))
    :location            "/"})
 
@@ -208,7 +222,7 @@
   (api-get-lucky-personal-collection :rasta, :expected-status-code 403))
 
 (def ^:private lucky-personal-subcollection-item
-  [(collection-item "Lucky's Personal Sub-Collection")])
+  [(collection-item "Lucky's Personal Sub-Collection" :can_write true)])
 
 (defn- api-get-lucky-personal-collection-with-subcollection [user-kw]
   (tt/with-temp Collection [_ {:name     "Lucky's Personal Sub-Collection"
@@ -267,7 +281,7 @@
 
 ;; ok, does a second-level Collection have its parent and its children?
 (expect
-  [{:effective_ancestors [{:name "A", :id true}]
+  [{:effective_ancestors [{:name "A", :id true, :can_write false}]
     :effective_location  "/A/"}
    (map collection-item ["D" "G"])]
   (with-collection-hierarchy [a b c d g]
@@ -275,8 +289,8 @@
 
 ;; what about a third-level Collection?
 (expect
-  [{:effective_ancestors [{:name "A", :id true}
-                          {:name "C", :id true}]
+  [{:effective_ancestors [{:name "A", :id true, :can_write false}
+                          {:name "C", :id true, :can_write false}]
     :effective_location  "/A/C/"}
    []]
   (with-collection-hierarchy [a b c d g]
@@ -285,7 +299,7 @@
 ;; for D: if we remove perms for C we should only have A as an ancestor; effective_location should lie and say we are
 ;; a child of A
 (expect
-  [{:effective_ancestors [{:name "A", :id true}]
+  [{:effective_ancestors [{:name "A", :id true, :can_write false}]
     :effective_location  "/A/"}
    []]
   (with-collection-hierarchy [a b d g]
@@ -293,7 +307,7 @@
 
 ;; for D: If, on the other hand, we remove A, we should see C as the only ancestor and as a root-level Collection.
 (expect
-  [{:effective_ancestors [{:name "C", :id true}]
+  [{:effective_ancestors [{:name "C", :id true, :can_write false}]
     :effective_location  "/C/"}
    []]
   (with-collection-hierarchy [b c d g]
@@ -301,7 +315,7 @@
 
 ;; for C: if we remove D we should get E and F as effective children
 (expect
-  [{:effective_ancestors [{:name "A", :id true}]
+  [{:effective_ancestors [{:name "A", :id true, :can_write false}]
     :effective_location  "/A/"}
    (map collection-item ["E" "F"])]
   (with-collection-hierarchy [a b c e f g]
@@ -331,17 +345,18 @@
 
 ;; Check that we can see stuff that isn't in any Collection -- meaning they're in the so-called "Root" Collection
 (expect
-  {:name                "Saved items"
+  {:name                "Our analytics"
    :id                  "root"
    :can_write           true
    :effective_location  nil
-   :effective_ancestors []}
+   :effective_ancestors []
+   :parent_id           nil}
   (with-some-children-of-collection nil
     ((user->client :crowberto) :get 200 "collection/root")))
 
 ;; Make sure you can see everything for Users that can see everything
 (expect
-  [(default-item {:name "Birthday Card", :description nil, :favorite false, :model "card"})
+  [(default-item {:name "Birthday Card", :description nil, :favorite false, :model "card", :display "table"})
    (collection-item "Crowberto Corv's Personal Collection")
    (default-item {:name "Dine & Dashboard", :description nil, :model "dashboard"})
    (default-item {:name "Electro-Magnetic Pulse", :model "pulse"})]
@@ -357,7 +372,7 @@
 
 ;; ...but if they have read perms for the Root Collection they should get to see them
 (expect
-  [(default-item {:name "Birthday Card", :description nil, :favorite false, :model "card"})
+  [(default-item {:name "Birthday Card", :description nil, :favorite false, :model "card", :display "table"})
    (default-item {:name "Dine & Dashboard", :description nil, :model "dashboard"})
    (default-item {:name "Electro-Magnetic Pulse", :model "pulse"})
    (collection-item "Rasta Toucan's Personal Collection")]
@@ -372,7 +387,8 @@
   [{:name        "Rasta Toucan's Personal Collection"
     :id          (u/get-id (collection/user->personal-collection (user->id :rasta)))
     :description nil
-    :model       "collection"}]
+    :model       "collection"
+    :can_write   true}]
   (do
     (collection-test/force-create-personal-collections!)
     ((user->client :rasta) :get 200 "collection/root/items")))
@@ -382,7 +398,8 @@
   [{:name        "Crowberto Corv's Personal Collection"
     :id          (u/get-id (collection/user->personal-collection (user->id :crowberto)))
     :description nil
-    :model       "collection"}]
+    :model       "collection"
+    :can_write   true}]
   (do
     (collection-test/force-create-personal-collections!)
     ((user->client :crowberto) :get 200 "collection/root/items")))
@@ -392,7 +409,8 @@
   [{:name        "Crowberto Corv's Personal Collection"
     :id          (u/get-id (collection/user->personal-collection (user->id :crowberto)))
     :description nil
-    :model       "collection"}]
+    :model       "collection"
+    :can_write   true}]
   (do
     (collection-test/force-create-personal-collections!)
     (tt/with-temp Collection [_ {:name     "Lucky's Sub-Collection"
@@ -405,6 +423,7 @@
   [{:name                "Business Card"
     :description         nil
     :collection_position nil
+    :display             "table"
     :favorite            false
     :model               "card"}]
   (tt/with-temp Card [card {:name "Business Card", :archived true}]
@@ -514,9 +533,10 @@
 ;; check that users without write perms aren't allowed to update a Collection
 (expect
   "You don't have permissions to do that."
-  (tt/with-temp Collection [collection]
-    ((user->client :rasta) :put 403 (str "collection/" (u/get-id collection))
-     {:name "My Beautiful Collection", :color "#ABCDEF"})))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [collection]
+      ((user->client :rasta) :put 403 (str "collection/" (u/get-id collection))
+       {:name "My Beautiful Collection", :color "#ABCDEF"}))))
 
 ;; Archiving a collection should delete any alerts associated with questions in the collection
 (expect
@@ -551,9 +571,10 @@
 ;; I shouldn't be allowed to archive a Collection without proper perms
 (expect
   "You don't have permissions to do that."
-  (tt/with-temp Collection [collection]
-    ((user->client :rasta) :put 403 (str "collection/" (u/get-id collection))
-     {:archived true})))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [collection]
+      ((user->client :rasta) :put 403 (str "collection/" (u/get-id collection))
+       {:archived true}))))
 
 ;; Perms checking should be recursive as well...
 ;;
@@ -561,11 +582,12 @@
 ;; also need perms for B
 (expect
   "You don't have permissions to do that."
-  (tt/with-temp* [Collection [collection-a]
-                  Collection [collection-b {:location (collection/children-location collection-a)}]]
-    (perms/grant-collection-readwrite-permissions! (group/all-users) collection-a)
-    ((user->client :rasta) :put 403 (str "collection/" (u/get-id collection-a))
-     {:archived true})))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [collection-a]
+                    Collection [collection-b {:location (collection/children-location collection-a)}]]
+      (perms/grant-collection-readwrite-permissions! (group/all-users) collection-a)
+      ((user->client :rasta) :put 403 (str "collection/" (u/get-id collection-a))
+       {:archived true}))))
 
 ;; Can I *change* the `location` of a Collection? (i.e. move it into a different parent Colleciton)
 (expect
@@ -587,11 +609,12 @@
 ;; If I want to move A into B, I should need permissions for both A and B
 (expect
   "You don't have permissions to do that."
-  (tt/with-temp* [Collection [collection-a]
-                  Collection [collection-b]]
-    (perms/grant-collection-readwrite-permissions! (group/all-users) collection-a)
-    ((user->client :rasta) :put 403 (str "collection/" (u/get-id collection-a))
-     {:parent_id (u/get-id collection-b)})))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [collection-a]
+                    Collection [collection-b]]
+      (perms/grant-collection-readwrite-permissions! (group/all-users) collection-a)
+      ((user->client :rasta) :put 403 (str "collection/" (u/get-id collection-a))
+       {:parent_id (u/get-id collection-b)}))))
 
 ;; Perms checking should be recursive as well...
 ;;
@@ -603,13 +626,14 @@
 ;; C
 (expect
   "You don't have permissions to do that."
-  (tt/with-temp* [Collection [collection-a]
-                  Collection [collection-b {:location (collection/children-location collection-a)}]
-                  Collection [collection-c]]
-    (doseq [collection [collection-a collection-b]]
-      (perms/grant-collection-readwrite-permissions! (group/all-users) collection))
-    ((user->client :rasta) :put 403 (str "collection/" (u/get-id collection-a))
-     {:parent_id (u/get-id collection-c)})))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [collection-a]
+                    Collection [collection-b {:location (collection/children-location collection-a)}]
+                    Collection [collection-c]]
+      (doseq [collection [collection-a collection-b]]
+        (perms/grant-collection-readwrite-permissions! (group/all-users) collection))
+      ((user->client :rasta) :put 403 (str "collection/" (u/get-id collection-a))
+       {:parent_id (u/get-id collection-c)}))))
 
 
 ;; Create A, B, and C; B is a child of A. Grant perms for A and C. Moving A into C should fail because we need perms
@@ -620,13 +644,14 @@
 ;; C*
 (expect
   "You don't have permissions to do that."
-  (tt/with-temp* [Collection [collection-a]
-                  Collection [collection-b {:location (collection/children-location collection-a)}]
-                  Collection [collection-c]]
-    (doseq [collection [collection-a collection-c]]
-      (perms/grant-collection-readwrite-permissions! (group/all-users) collection))
-    ((user->client :rasta) :put 403 (str "collection/" (u/get-id collection-a))
-     {:parent_id (u/get-id collection-c)})))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [collection-a]
+                    Collection [collection-b {:location (collection/children-location collection-a)}]
+                    Collection [collection-c]]
+      (doseq [collection [collection-a collection-c]]
+        (perms/grant-collection-readwrite-permissions! (group/all-users) collection))
+      ((user->client :rasta) :put 403 (str "collection/" (u/get-id collection-a))
+       {:parent_id (u/get-id collection-c)}))))
 
 ;; Create A, B, and C; B is a child of A. Grant perms for B and C. Moving A into C should fail because we need perms
 ;; for A:
@@ -636,10 +661,11 @@
 ;; C*
 (expect
   "You don't have permissions to do that."
-  (tt/with-temp* [Collection [collection-a]
-                  Collection [collection-b {:location (collection/children-location collection-a)}]
-                  Collection [collection-c]]
-    (doseq [collection [collection-b collection-c]]
-      (perms/grant-collection-readwrite-permissions! (group/all-users) collection))
-    ((user->client :rasta) :put 403 (str "collection/" (u/get-id collection-a))
-     {:parent_id (u/get-id collection-c)})))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [collection-a]
+                    Collection [collection-b {:location (collection/children-location collection-a)}]
+                    Collection [collection-c]]
+      (doseq [collection [collection-b collection-c]]
+        (perms/grant-collection-readwrite-permissions! (group/all-users) collection))
+      ((user->client :rasta) :put 403 (str "collection/" (u/get-id collection-a))
+       {:parent_id (u/get-id collection-c)}))))
diff --git a/test/metabase/api/common_test.clj b/test/metabase/api/common_test.clj
index 335b67793f857951a197c552f07c1f2d6127e21c..fe310c39195d0ca949b0146eac842c818bd16fc8 100644
--- a/test/metabase/api/common_test.clj
+++ b/test/metabase/api/common_test.clj
@@ -11,15 +11,25 @@
 
 (def ^:private four-oh-four
   "The expected format of a 404 response."
-  {:status 404
-   :body "Not found."})
+  {:status  404
+   :body    "Not found."
+   :headers {"Cache-Control"                     "max-age=0, no-cache, must-revalidate, proxy-revalidate"
+             "Content-Security-Policy"           (-> @#'mb-middleware/content-security-policy-header vals first)
+             "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?
+             "Strict-Transport-Security"         "max-age=31536000"
+             "X-Content-Type-Options"            "nosniff"
+             "X-Frame-Options"                   "DENY"
+             "X-Permitted-Cross-Domain-Policies" "none"
+             "X-XSS-Protection"                  "1; mode=block"}})
 
 (defn ^:private my-mock-api-fn []
   ((mb-middleware/catch-api-exceptions
     (fn [_]
       (check-404 @*current-user*)
       {:status 200
-       :body @*current-user*}))
+       :body   @*current-user*}))
    nil))
 
 ; check that `check-404` doesn't throw an exception if TEST is true
@@ -29,17 +39,20 @@
     (my-mock-api-fn)))
 
 ; check that 404 is returned otherwise
-(expect four-oh-four
-  (my-mock-api-fn))
+(expect
+  four-oh-four
+  (-> (my-mock-api-fn)
+      (update-in [:headers "Last-Modified"] string?)))
 
 ;;let-404 should return nil if test fails
 (expect
   four-oh-four
-  ((mb-middleware/catch-api-exceptions
-    (fn [_]
-      (let-404 [user nil]
-        {:user user})))
-   nil))
+  (-> ((mb-middleware/catch-api-exceptions
+        (fn [_]
+          (let-404 [user nil]
+            {:user user})))
+       nil)
+      (update-in [:headers "Last-Modified"] string?)))
 
 ;; otherwise let-404 should bind as expected
 (expect
diff --git a/test/metabase/api/dashboard_test.clj b/test/metabase/api/dashboard_test.clj
index 107bd4defbe6d5c8a6358b5c9dc769607db0ed79..a2626777817ec1bd90f45f5a1a3f915079ef10ec 100644
--- a/test/metabase/api/dashboard_test.clj
+++ b/test/metabase/api/dashboard_test.clj
@@ -62,7 +62,7 @@
       (assoc :created_at (boolean created_at)
              :updated_at (boolean updated_at)
              :card       (-> (into {} card)
-                             (dissoc :id :database_id :table_id :created_at :updated_at)
+                             (dissoc :id :database_id :table_id :created_at :updated_at :query_average_duration)
                              (update :collection_id boolean)))))
 
 (defn- dashboard-response [{:keys [creator ordered_cards created_at updated_at] :as dashboard}]
@@ -76,11 +76,12 @@
       ordered_cards (update :ordered_cards #(mapv dashcard-response %)))))
 
 (defn- do-with-dashboards-in-a-collection [grant-collection-perms-fn! dashboards-or-ids f]
-  (tt/with-temp Collection [collection]
-    (grant-collection-perms-fn! (group/all-users) collection)
-    (doseq [dashboard-or-id dashboards-or-ids]
-      (db/update! Dashboard (u/get-id dashboard-or-id) :collection_id (u/get-id collection)))
-    (f)))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [collection]
+      (grant-collection-perms-fn! (group/all-users) collection)
+      (doseq [dashboard-or-id dashboards-or-ids]
+        (db/update! Dashboard (u/get-id dashboard-or-id) :collection_id (u/get-id collection)))
+      (f))))
 
 (defmacro ^:private with-dashboards-in-readable-collection [dashboards-or-ids & body]
   `(do-with-dashboards-in-a-collection perms/grant-collection-read-permissions! ~dashboards-or-ids (fn [] ~@body)))
@@ -138,38 +139,41 @@
           :updated_at    true
           :created_at    true
           :collection_id true})
-  (tu/with-model-cleanup [Dashboard]
-    (tt/with-temp Collection [collection]
-      (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
-      (-> ((user->client :rasta) :post 200 "dashboard" {:name          "Test Create Dashboard"
-                                                        :parameters    [{:hash "abc123", :name "test", :type "date"}]
-                                                        :collection_id (u/get-id collection)})
-          dashboard-response))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tu/with-model-cleanup [Dashboard]
+      (tt/with-temp Collection [collection]
+        (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
+        (-> ((user->client :rasta) :post 200 "dashboard" {:name          "Test Create Dashboard"
+                                                          :parameters    [{:hash "abc123", :name "test", :type "date"}]
+                                                          :collection_id (u/get-id collection)})
+            dashboard-response)))))
 
 ;; Make sure we can create a Dashboard with a Collection position
 (expect
   #metabase.models.dashboard.DashboardInstance{:collection_id true, :collection_position 1000}
-  (tu/with-model-cleanup [Dashboard]
-    (let [dashboard-name (tu/random-name)]
-      (tt/with-temp Collection [collection]
-        (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
-        ((user->client :rasta) :post 200 "dashboard" {:name                dashboard-name
-                                                      :collection_id       (u/get-id collection)
-                                                      :collection_position 1000})
-        (some-> (db/select-one [Dashboard :collection_id :collection_position] :name dashboard-name)
-                (update :collection_id (partial = (u/get-id collection))))))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tu/with-model-cleanup [Dashboard]
+      (let [dashboard-name (tu/random-name)]
+        (tt/with-temp Collection [collection]
+          (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
+          ((user->client :rasta) :post 200 "dashboard" {:name                dashboard-name
+                                                        :collection_id       (u/get-id collection)
+                                                        :collection_position 1000})
+          (some-> (db/select-one [Dashboard :collection_id :collection_position] :name dashboard-name)
+                  (update :collection_id (partial = (u/get-id collection)))))))))
 
 ;; ...but not if we don't have permissions for the Collection
 (expect
   nil
-  (tu/with-model-cleanup [Dashboard]
-    (let [dashboard-name (tu/random-name)]
-      (tt/with-temp Collection [collection]
-        ((user->client :rasta) :post 403 "dashboard" {:name                dashboard-name
-                                                      :collection_id       (u/get-id collection)
-                                                      :collection_position 1000})
-        (some-> (db/select-one [Dashboard :collection_id :collection_position] :name dashboard-name)
-                (update :collection_id (partial = (u/get-id collection))))))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tu/with-model-cleanup [Dashboard]
+      (let [dashboard-name (tu/random-name)]
+        (tt/with-temp Collection [collection]
+          ((user->client :rasta) :post 403 "dashboard" {:name                dashboard-name
+                                                        :collection_id       (u/get-id collection)
+                                                        :collection_position 1000})
+          (some-> (db/select-one [Dashboard :collection_id :collection_position] :name dashboard-name)
+                  (update :collection_id (partial = (u/get-id collection)))))))))
 
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
@@ -181,6 +185,7 @@
          {:name          "Test Dashboard"
           :creator_id    (user->id :rasta)
           :collection_id true
+          :can_write     false
           :ordered_cards [{:sizeX                  2
                            :sizeY                  2
                            :col                    0
@@ -198,7 +203,6 @@
                                                            :dataset_query          {}
                                                            :read_permissions       nil
                                                            :visualization_settings {}
-                                                           :query_average_duration nil
                                                            :result_metadata        nil})
                            :series                 []}]})
   ;; fetch a dashboard WITH a dashboard card on it
@@ -212,16 +216,17 @@
 ;; ## GET /api/dashboard/:id with a series, should fail if the user doesn't have access to the collection
 (expect
   "You don't have permissions to do that."
-  (tt/with-temp* [Collection          [{coll-id :id}      {:name "Collection 1"}]
-                  Dashboard           [{dashboard-id :id} {:name       "Test Dashboard"
-                                                           :creator_id (user->id :crowberto)}]
-                  Card                [{card-id :id}      {:name          "Dashboard Test Card"
-                                                           :collection_id coll-id}]
-                  Card                [{card-id2 :id}     {:name          "Dashboard Test Card 2"
-                                                           :collection_id coll-id}]
-                  DashboardCard       [{dbc_id :id}       {:dashboard_id dashboard-id, :card_id card-id}]
-                  DashboardCardSeries [_                  {:dashboardcard_id dbc_id, :card_id card-id2, :position 0}]]
-    ((user->client :rasta) :get 403 (format "dashboard/%d" dashboard-id))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection          [{coll-id :id}      {:name "Collection 1"}]
+                    Dashboard           [{dashboard-id :id} {:name       "Test Dashboard"
+                                                             :creator_id (user->id :crowberto)}]
+                    Card                [{card-id :id}      {:name          "Dashboard Test Card"
+                                                             :collection_id coll-id}]
+                    Card                [{card-id2 :id}     {:name          "Dashboard Test Card 2"
+                                                             :collection_id coll-id}]
+                    DashboardCard       [{dbc_id :id}       {:dashboard_id dashboard-id, :card_id card-id}]
+                    DashboardCardSeries [_                  {:dashboardcard_id dbc_id, :card_id card-id2, :position 0}]]
+      ((user->client :rasta) :get 403 (format "dashboard/%d" dashboard-id)))))
 
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
@@ -298,59 +303,65 @@
 ;; ...but if we don't have the Permissions for the old collection, we should get an Exception
 (expect
   "You don't have permissions to do that."
-  (dashboard-test/with-dash-in-collection [db collection dash]
-    (tt/with-temp Collection [new-collection]
-      ;; grant Permissions for only the *new* collection
-      (perms/grant-collection-readwrite-permissions! (group/all-users) new-collection)
-      ;; now make an API call to move collections. Should fail
-      ((user->client :rasta) :put 403 (str "dashboard/" (u/get-id dash)) {:collection_id (u/get-id new-collection)}))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (dashboard-test/with-dash-in-collection [db collection dash]
+      (tt/with-temp Collection [new-collection]
+        ;; grant Permissions for only the *new* collection
+        (perms/grant-collection-readwrite-permissions! (group/all-users) new-collection)
+        ;; now make an API call to move collections. Should fail
+        ((user->client :rasta) :put 403 (str "dashboard/" (u/get-id dash)) {:collection_id (u/get-id new-collection)})))))
 
 ;; ...and if we don't have the Permissions for the new collection, we should get an Exception
 (expect
   "You don't have permissions to do that."
-  (dashboard-test/with-dash-in-collection [db collection dash]
-    (tt/with-temp Collection [new-collection]
-      ;; grant Permissions for only the *old* collection
-      (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
-      ;; now make an API call to move collections. Should fail
-      ((user->client :rasta) :put 403 (str "dashboard/" (u/get-id dash)) {:collection_id (u/get-id new-collection)}))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (dashboard-test/with-dash-in-collection [db collection dash]
+      (tt/with-temp Collection [new-collection]
+        ;; grant Permissions for only the *old* collection
+        (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
+        ;; now make an API call to move collections. Should fail
+        ((user->client :rasta) :put 403 (str "dashboard/" (u/get-id dash)) {:collection_id (u/get-id new-collection)})))))
 
 ;; Can we change the Collection position of a Dashboard?
 (expect
   1
-  (tt/with-temp* [Collection [collection]
-                  Dashboard  [dashboard {:collection_id (u/get-id collection)}]]
-    (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
-    ((user->client :rasta) :put 200 (str "dashboard/" (u/get-id dashboard))
-     {:collection_position 1})
-    (db/select-one-field :collection_position Dashboard :id (u/get-id dashboard))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [collection]
+                    Dashboard  [dashboard {:collection_id (u/get-id collection)}]]
+      (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
+      ((user->client :rasta) :put 200 (str "dashboard/" (u/get-id dashboard))
+       {:collection_position 1})
+      (db/select-one-field :collection_position Dashboard :id (u/get-id dashboard)))))
 
 ;; ...and unset (unpin) it as well?
 (expect
   nil
-  (tt/with-temp* [Collection [collection]
-                  Dashboard  [dashboard {:collection_id (u/get-id collection), :collection_position 1}]]
-    (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
-    ((user->client :rasta) :put 200 (str "dashboard/" (u/get-id dashboard))
-     {:collection_position nil})
-    (db/select-one-field :collection_position Dashboard :id (u/get-id dashboard))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [collection]
+                    Dashboard  [dashboard {:collection_id (u/get-id collection), :collection_position 1}]]
+      (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
+      ((user->client :rasta) :put 200 (str "dashboard/" (u/get-id dashboard))
+       {:collection_position nil})
+      (db/select-one-field :collection_position Dashboard :id (u/get-id dashboard)))))
 
 ;; ...we shouldn't be able to if we don't have permissions for the Collection
 (expect
   nil
-  (tt/with-temp* [Collection [collection]
-                  Dashboard  [dashboard {:collection_id (u/get-id collection)}]]
-    ((user->client :rasta) :put 403 (str "dashboard/" (u/get-id dashboard))
-     {:collection_position 1})
-    (db/select-one-field :collection_position Dashboard :id (u/get-id dashboard))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [collection]
+                    Dashboard  [dashboard {:collection_id (u/get-id collection)}]]
+      ((user->client :rasta) :put 403 (str "dashboard/" (u/get-id dashboard))
+       {:collection_position 1})
+      (db/select-one-field :collection_position Dashboard :id (u/get-id dashboard)))))
 
 (expect
   1
-  (tt/with-temp* [Collection [collection]
-                  Dashboard  [dashboard {:collection_id (u/get-id collection), :collection_position 1}]]
-    ((user->client :rasta) :put 403 (str "dashboard/" (u/get-id dashboard))
-     {:collection_position nil})
-    (db/select-one-field :collection_position Dashboard :id (u/get-id dashboard))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [collection]
+                    Dashboard  [dashboard {:collection_id (u/get-id collection), :collection_position 1}]]
+      ((user->client :rasta) :put 403 (str "dashboard/" (u/get-id dashboard))
+       {:collection_position nil})
+      (db/select-one-field :collection_position Dashboard :id (u/get-id dashboard)))))
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                              UPDATING DASHBOARD COLLECTION POSITIONS                                           |
@@ -362,15 +373,16 @@
    "c" 2
    "d" 3
    "b" 4}
-  (tt/with-temp Collection [collection]
-    (card-api-test/with-ordered-items collection [Dashboard a
-                                                  Dashboard b
-                                                  Dashboard c
-                                                  Dashboard d]
-      (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
-      ((user->client :rasta) :put 200 (str "dashboard/" (u/get-id b))
-       {:collection_position 4})
-      (card-api-test/get-name->collection-position :rasta collection))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [collection]
+      (card-api-test/with-ordered-items collection [Dashboard a
+                                                    Dashboard b
+                                                    Dashboard c
+                                                    Dashboard d]
+        (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
+        ((user->client :rasta) :put 200 (str "dashboard/" (u/get-id b))
+         {:collection_position 4})
+        (card-api-test/get-name->collection-position :rasta collection)))))
 
 ;; Check that updating a dashboard at position 3 to position 1 will increment the positions before 3, not after
 (expect
@@ -378,15 +390,16 @@
    "a" 2
    "b" 3
    "d" 4}
-  (tt/with-temp Collection [collection]
-    (card-api-test/with-ordered-items collection [Card      a
-                                                  Pulse     b
-                                                  Dashboard c
-                                                  Dashboard d]
-      (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
-      ((user->client :rasta) :put 200 (str "dashboard/" (u/get-id c))
-       {:collection_position 1})
-      (card-api-test/get-name->collection-position :rasta collection))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [collection]
+      (card-api-test/with-ordered-items collection [Card      a
+                                                    Pulse     b
+                                                    Dashboard c
+                                                    Dashboard d]
+        (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
+        ((user->client :rasta) :put 200 (str "dashboard/" (u/get-id c))
+         {:collection_position 1})
+        (card-api-test/get-name->collection-position :rasta collection)))))
 
 ;; Check that updating position 1 to 3 will cause b and c to be decremented
 (expect
@@ -394,15 +407,16 @@
    "c" 2
    "a" 3
    "d" 4}
-  (tt/with-temp Collection [collection]
-    (card-api-test/with-ordered-items collection [Dashboard a
-                                                  Card      b
-                                                  Pulse     c
-                                                  Dashboard d]
-      (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
-      ((user->client :rasta) :put 200 (str "dashboard/" (u/get-id a))
-       {:collection_position 3})
-      (card-api-test/get-name->collection-position :rasta collection))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [collection]
+      (card-api-test/with-ordered-items collection [Dashboard a
+                                                    Card      b
+                                                    Pulse     c
+                                                    Dashboard d]
+        (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
+        ((user->client :rasta) :put 200 (str "dashboard/" (u/get-id a))
+         {:collection_position 3})
+        (card-api-test/get-name->collection-position :rasta collection)))))
 
 ;; Check that updating position 1 to 4 will cause a through c to be decremented
 (expect
@@ -410,15 +424,16 @@
    "c" 2
    "d" 3
    "a" 4}
-  (tt/with-temp Collection [collection]
-    (card-api-test/with-ordered-items collection [Dashboard a
-                                                  Card      b
-                                                  Pulse     c
-                                                  Pulse     d]
-      (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
-      ((user->client :rasta) :put 200 (str "dashboard/" (u/get-id a))
-       {:collection_position 4})
-      (card-api-test/get-name->collection-position :rasta collection))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [collection]
+      (card-api-test/with-ordered-items collection [Dashboard a
+                                                    Card      b
+                                                    Pulse     c
+                                                    Pulse     d]
+        (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
+        ((user->client :rasta) :put 200 (str "dashboard/" (u/get-id a))
+         {:collection_position 4})
+        (card-api-test/get-name->collection-position :rasta collection)))))
 
 ;; Check that updating position 4 to 1 will cause a through c to be incremented
 (expect
@@ -426,15 +441,16 @@
    "a" 2
    "b" 3
    "c" 4}
-  (tt/with-temp Collection [collection]
-    (card-api-test/with-ordered-items collection [Card      a
-                                                  Pulse     b
-                                                  Card      c
-                                                  Dashboard d]
-      (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
-      ((user->client :rasta) :put 200 (str "dashboard/" (u/get-id d))
-       {:collection_position 1})
-      (card-api-test/get-name->collection-position :rasta collection))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [collection]
+      (card-api-test/with-ordered-items collection [Card      a
+                                                    Pulse     b
+                                                    Card      c
+                                                    Dashboard d]
+        (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
+        ((user->client :rasta) :put 200 (str "dashboard/" (u/get-id d))
+         {:collection_position 1})
+        (card-api-test/get-name->collection-position :rasta collection)))))
 
 ;; Check that moving a dashboard to another collection will fixup both collections
 (expect
@@ -446,25 +462,26 @@
     "f" 3
     "g" 4
     "h" 5}]
-  (tt/with-temp* [Collection [collection-1]
-                  Collection [collection-2]]
-    (card-api-test/with-ordered-items collection-1 [Dashboard a
-                                                    Card      b
-                                                    Card      c
-                                                    Pulse     d]
-      (card-api-test/with-ordered-items collection-2 [Pulse     e
-                                                      Pulse     f
-                                                      Dashboard g
-                                                      Card      h]
-        (perms/grant-collection-readwrite-permissions! (group/all-users) collection-1)
-        (perms/grant-collection-readwrite-permissions! (group/all-users) collection-2)
-        ;; Move the first dashboard in collection-1 to collection-1
-        ((user->client :rasta) :put 200 (str "dashboard/" (u/get-id a))
-         {:collection_position 1, :collection_id (u/get-id collection-2)})
-        ;; "a" should now be gone from collection-1 and all the existing dashboards bumped down in position
-        [(card-api-test/get-name->collection-position :rasta collection-1)
-         ;; "a" is now first, all other dashboards in collection-2 bumped down 1
-         (card-api-test/get-name->collection-position :rasta collection-2)]))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [collection-1]
+                    Collection [collection-2]]
+      (card-api-test/with-ordered-items collection-1 [Dashboard a
+                                                      Card      b
+                                                      Card      c
+                                                      Pulse     d]
+        (card-api-test/with-ordered-items collection-2 [Pulse     e
+                                                        Pulse     f
+                                                        Dashboard g
+                                                        Card      h]
+          (perms/grant-collection-readwrite-permissions! (group/all-users) collection-1)
+          (perms/grant-collection-readwrite-permissions! (group/all-users) collection-2)
+          ;; Move the first dashboard in collection-1 to collection-1
+          ((user->client :rasta) :put 200 (str "dashboard/" (u/get-id a))
+           {:collection_position 1, :collection_id (u/get-id collection-2)})
+          ;; "a" should now be gone from collection-1 and all the existing dashboards bumped down in position
+          [(card-api-test/get-name->collection-position :rasta collection-1)
+           ;; "a" is now first, all other dashboards in collection-2 bumped down 1
+           (card-api-test/get-name->collection-position :rasta collection-2)])))))
 
 ;; Check that adding a new card at position 3 will cause the existing card at 3 to be incremented
 (expect
@@ -475,19 +492,20 @@
     "b" 2
     "c" 3
     "d" 4}]
-  (tt/with-temp Collection [collection]
-    (tu/with-model-cleanup [Dashboard]
-      (card-api-test/with-ordered-items collection [Card  a
-                                                    Pulse b
-                                                    Card  d]
-        (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
-        [(card-api-test/get-name->collection-position :rasta collection)
-         (do
-           ((user->client :rasta) :post 200 "dashboard" {:name "c"
-                                                         :parameters          [{}]
-                                                         :collection_id       (u/get-id collection)
-                                                         :collection_position 3})
-           (card-api-test/get-name->collection-position :rasta collection))]))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [collection]
+      (tu/with-model-cleanup [Dashboard]
+        (card-api-test/with-ordered-items collection [Card  a
+                                                      Pulse b
+                                                      Card  d]
+          (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
+          [(card-api-test/get-name->collection-position :rasta collection)
+           (do
+             ((user->client :rasta) :post 200 "dashboard" {:name                "c"
+                                                           :parameters          [{}]
+                                                           :collection_id       (u/get-id collection)
+                                                           :collection_position 3})
+             (card-api-test/get-name->collection-position :rasta collection))])))))
 
 ;; Check that adding a new card without a position, leaves the existing positions unchanged
 (expect
@@ -498,18 +516,19 @@
     "b" 2
     "c" nil
     "d" 3}]
-  (tt/with-temp Collection [collection]
-    (tu/with-model-cleanup [Dashboard]
-      (card-api-test/with-ordered-items collection [Dashboard a
-                                                    Card      b
-                                                    Pulse     d]
-        (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
-        [(card-api-test/get-name->collection-position :rasta collection)
-         (do
-           ((user->client :rasta) :post 200 "dashboard" {:name "c"
-                                                         :parameters          [{}]
-                                                         :collection_id       (u/get-id collection)})
-           (card-api-test/get-name->collection-position :rasta collection))]))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [collection]
+      (tu/with-model-cleanup [Dashboard]
+        (card-api-test/with-ordered-items collection [Dashboard a
+                                                      Card      b
+                                                      Pulse     d]
+          (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
+          [(card-api-test/get-name->collection-position :rasta collection)
+           (do
+             ((user->client :rasta) :post 200 "dashboard" {:name          "c"
+                                                           :parameters    [{}]
+                                                           :collection_id (u/get-id collection)})
+             (card-api-test/get-name->collection-position :rasta collection))])))))
 
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
diff --git a/test/metabase/api/database_test.clj b/test/metabase/api/database_test.clj
index 310a3c50f8efabe091fcd74caf70e0e1431095e7..57e8339db6d022a8acbc9a0cc9268a7292374703 100644
--- a/test/metabase/api/database_test.clj
+++ b/test/metabase/api/database_test.clj
@@ -1,4 +1,5 @@
 (ns metabase.api.database-test
+  "Tests for /api/database endpoints."
   (:require [expectations :refer :all]
             [metabase
              [driver :as driver]
@@ -50,14 +51,16 @@
         (db/delete! Database :id (:id db))))))
 
 (defmacro ^:private expect-with-temp-db-created-via-api {:style/indent 1} [[binding & [options]] expected actual]
-  ;; use `gensym` instead of auto gensym here so we can be sure it's a unique symbol every time. Otherwise since expectations hashes its body
-  ;; to generate function names it will treat every usage this as the same test and only a single one will end up being ran
+  ;; use `gensym` instead of auto gensym here so we can be sure it's a unique symbol every time. Otherwise since
+  ;; expectations hashes its body to generate function names it will treat every usage this as the same test and only
+  ;; a single one will end up being ran
   (let [result (gensym "result-")]
     `(let [~result (delay (do-with-temp-db-created-via-api ~options (fn [~binding]
                                                                       [~expected
                                                                        ~actual])))]
        (expect
-         (u/ignore-exceptions (first @~result)) ; in case @result# barfs we don't want the test to succeed (Exception == Exception for expectations)
+         ;; in case @result# barfs we don't want the test to succeed (Exception == Exception for expectations)
+         (u/ignore-exceptions (first @~result))
          (second @~result)))))
 
 (def ^:private default-db-details
@@ -174,14 +177,13 @@
                 :active          $
                 :id              $
                 :db_id           $
-                :raw_table_id    $
                 :created_at      $
                 :fields_hash     $}))
       (update :entity_type (comp (partial str "entity/") name))))
 
 
-;; TODO - this is a test code smell, each test should clean up after itself and this step shouldn't be neccessary. One day we should be able to remove this!
-;; If you're writing a NEW test that needs this, fix your brain and your test!
+;; TODO - this is a test code smell, each test should clean up after itself and this step shouldn't be neccessary. One
+;; day we should be able to remove this! If you're writing a NEW test that needs this, fix your brain and your test!
 ;; To reïterate, this is BAD BAD BAD BAD BAD BAD! It will break tests if you use it! Don't use it!
 (defn- ^:deprecated delete-randomly-created-databases!
   "Delete all the randomly created Databases we've made so far. Optionally specify one or more IDs to SKIP."
@@ -193,9 +195,9 @@
                                 :when  id]
                             id))]
     (when-let [dbs (seq (db/select [Database :name :engine :id] :id [:not-in ids-to-skip]))]
-      (println (u/format-color 'red (str "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
+      (println (u/format-color 'red (str "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
                                          "WARNING: deleting randomly created databases:\n%s"
-                                         "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n")
+                                         "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n")
                  (u/pprint-to-str (for [db dbs]
                                     (dissoc db :features))))))
     (db/delete! Database :id [:not-in ids-to-skip])))
@@ -281,7 +283,6 @@
    (match-$ field
      {:updated_at          $
       :id                  $
-      :raw_column_id       $
       :created_at          $
       :last_analyzed       $
       :fingerprint         $
@@ -327,7 +328,6 @@
                                    :rows         nil
                                    :updated_at   $
                                    :id           (data/id :categories)
-                                   :raw_table_id $
                                    :db_id        (data/id)
                                    :created_at   $
                                    :fields_hash  $}))]}))
@@ -653,52 +653,121 @@
     ((user->client :crowberto) :post 200 "database/validate"
      {:details {:engine :h2, :details {:db "ABC"}}})))
 
-;; Tests for GET /api/database/:id/schemas
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                      GET /api/database/:id/schemas & GET /api/database/:id/schema/:schema                      |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+;; Tests for GET /api/database/:id/schemas: should work if user has full DB perms...
+(expect
+  ["schema1"]
+  (tt/with-temp* [Database [{db-id :id}]
+                  Table    [_           {:db_id db-id, :schema "schema1"}]
+                  Table    [_           {:db_id db-id, :schema "schema1"}]]
+    ((user->client :rasta) :get 200 (format "database/%d/schemas" db-id))))
+
+;; ...or full schema perms...
+(expect
+  ["schema1"]
+  (tt/with-temp* [Database [{db-id :id}]
+                  Table    [_           {:db_id db-id, :schema "schema1"}]
+                  Table    [_           {:db_id db-id, :schema "schema1"}]]
+    (perms/revoke-permissions! (perms-group/all-users) db-id)
+    (perms/grant-permissions!  (perms-group/all-users) db-id "schema1")
+    ((user->client :rasta) :get 200 (format "database/%d/schemas" db-id))))
+
+;; ...or just table read perms...
 (expect
   ["schema1"]
   (tt/with-temp* [Database [{db-id :id}]
                   Table    [t1          {:db_id db-id, :schema "schema1"}]
                   Table    [t2          {:db_id db-id, :schema "schema1"}]]
-    ((user->client :crowberto) :get 200 (format "database/%d/schemas" db-id))))
+    (perms/revoke-permissions! (perms-group/all-users) db-id)
+    (perms/grant-permissions!  (perms-group/all-users) db-id "schema1" t1)
+    (perms/grant-permissions!  (perms-group/all-users) db-id "schema1" t2)
+    ((user->client :rasta) :get 200 (format "database/%d/schemas" db-id))))
 
 ;; Multiple schemas are ordered by name
 (expect
   ["schema1" "schema2" "schema3"]
   (tt/with-temp* [Database [{db-id :id}]
-                  Table    [t1          {:db_id db-id, :schema "schema3"}]
-                  Table    [t2          {:db_id db-id, :schema "schema2"}]
-                  Table    [t3          {:db_id db-id, :schema "schema1"}]]
-    ((user->client :crowberto) :get 200 (format "database/%d/schemas" db-id))))
+                  Table    [_ {:db_id db-id, :schema "schema3"}]
+                  Table    [_ {:db_id db-id, :schema "schema2"}]
+                  Table    [_ {:db_id db-id, :schema "schema1"}]]
+    ((user->client :rasta) :get 200 (format "database/%d/schemas" db-id))))
 
-;; Multiple schemas are ordered by name
+;; Can we fetch the Tables in a Schema? (If we have full DB perms)
 (expect
   ["t1" "t3"]
   (tt/with-temp* [Database [{db-id :id}]
-                  Table    [{t1-id :id} {:db_id db-id, :schema "schema1", :name "t1"}]
-                  Table    [t2          {:db_id db-id, :schema "schema2"}]
-                  Table    [{t3-id :id} {:db_id db-id, :schema "schema1", :name "t3"}]]
-    (map :name ((user->client :crowberto) :get 200 (format "database/%d/schema/%s" db-id "schema1")))))
+                  Table    [_ {:db_id db-id, :schema "schema1", :name "t1"}]
+                  Table    [_ {:db_id db-id, :schema "schema2"}]
+                  Table    [_ {:db_id db-id, :schema "schema1", :name "t3"}]]
+    (map :name ((user->client :rasta) :get 200 (format "database/%d/schema/%s" db-id "schema1")))))
+
+;; Can we fetch the Tables in a Schema? (If we have full schema perms)
+(expect
+  ["t1" "t3"]
+  (tt/with-temp* [Database [{db-id :id}]
+                  Table    [_ {:db_id db-id, :schema "schema1", :name "t1"}]
+                  Table    [_ {:db_id db-id, :schema "schema2"}]
+                  Table    [_ {:db_id db-id, :schema "schema1", :name "t3"}]]
+    (perms/revoke-permissions! (perms-group/all-users) db-id)
+    (perms/grant-permissions!  (perms-group/all-users) db-id "schema1")
+    (map :name ((user->client :rasta) :get 200 (format "database/%d/schema/%s" db-id "schema1")))))
+
+;; Can we fetch the Tables in a Schema? (If we have full Table perms)
+(expect
+  ["t1" "t3"]
+  (tt/with-temp* [Database [{db-id :id}]
+                  Table    [t1 {:db_id db-id, :schema "schema1", :name "t1"}]
+                  Table    [_  {:db_id db-id, :schema "schema2"}]
+                  Table    [t3 {:db_id db-id, :schema "schema1", :name "t3"}]]
+        (perms/revoke-permissions! (perms-group/all-users) db-id)
+    (perms/grant-permissions!  (perms-group/all-users) db-id "schema1" t1)
+    (perms/grant-permissions!  (perms-group/all-users) db-id "schema1" t3)
+    (map :name ((user->client :rasta) :get 200 (format "database/%d/schema/%s" db-id "schema1")))))
 
 ;; GET /api/database/:id/schemas should return a 403 for a user that doesn't have read permissions
 (expect
   "You don't have permissions to do that."
   (tt/with-temp* [Database [{database-id :id}]
-                  Table    [{table-id :id} {:db_id database-id, :schema "test"}]]
-    (perms/delete-related-permissions! (perms-group/all-users) (perms/object-path database-id))
+                  Table    [_ {:db_id database-id, :schema "test"}]]
+    (perms/revoke-permissions! (perms-group/all-users) database-id)
     ((user->client :rasta) :get 403 (format "database/%s/schemas" database-id))))
 
-;; GET /api/database/:id/schemas should return a 403 for a user that doesn't have read permissions
+;; GET /api/database/:id/schemas should exclude schemas for which the user has no perms
+(expect
+  ["schema-with-perms"]
+  (tt/with-temp* [Database [{database-id :id}]
+                  Table    [_ {:db_id database-id, :schema "schema-with-perms"}]
+                  Table    [_ {:db_id database-id, :schema "schema-without-perms"}]]
+    (perms/revoke-permissions! (perms-group/all-users) database-id)
+    (perms/grant-permissions!  (perms-group/all-users) database-id "schema-with-perms")
+    ((user->client :rasta) :get 200 (format "database/%s/schemas" database-id))))
+
+;; GET /api/database/:id/schema/:schema should return a 403 for a user that doesn't have read permissions FOR THE DB...
 (expect
   "You don't have permissions to do that."
   (tt/with-temp* [Database [{database-id :id}]
                   Table    [{table-id :id} {:db_id database-id, :schema "test"}]]
-    (perms/delete-related-permissions! (perms-group/all-users) (perms/object-path database-id))
+    (perms/revoke-permissions! (perms-group/all-users) database-id)
     ((user->client :rasta) :get 403 (format "database/%s/schema/%s" database-id "test"))))
 
+;; ... or for the SCHEMA
+(expect
+  "You don't have permissions to do that."
+  (tt/with-temp* [Database [{database-id :id}]
+                  Table    [_ {:db_id database-id, :schema "schema-with-perms"}]
+                  Table    [_ {:db_id database-id, :schema "schema-without-perms"}]]
+    (perms/revoke-permissions! (perms-group/all-users) database-id)
+    (perms/grant-permissions!  (perms-group/all-users) database-id "schema-with-perms")
+    ((user->client :rasta) :get 403 (format "database/%s/schema/%s" database-id "schema-without-perms"))))
+
 ;; Looking for a database that doesn't exist should return a 404
 (expect
   "Not found."
-  ((user->client :rasta) :get 404 (format "database/%s/schemas" Integer/MAX_VALUE)))
+  ((user->client :crowberto) :get 404 (format "database/%s/schemas" Integer/MAX_VALUE)))
 
 ;; Check that a 404 returns if the schema isn't found
 (expect
@@ -706,3 +775,14 @@
   (tt/with-temp* [Database [{db-id :id}]
                   Table    [{t1-id :id} {:db_id db-id, :schema "schema1"}]]
     ((user->client :crowberto) :get 404 (format "database/%d/schema/%s" db-id "not schema1"))))
+
+
+;; GET /api/database/:id/schema/:schema should exclude Tables for which the user has no perms
+(expect
+  ["table-with-perms"]
+  (tt/with-temp* [Database [{database-id :id}]
+                  Table    [table-with-perms {:db_id database-id, :schema "public", :name "table-with-perms"}]
+                  Table    [_                {:db_id database-id, :schema "public", :name "table-without-perms"}]]
+    (perms/revoke-permissions! (perms-group/all-users) database-id)
+    (perms/grant-permissions!  (perms-group/all-users) database-id "public" table-with-perms)
+    (map :name ((user->client :rasta) :get 200 (format "database/%s/schema/%s" database-id "public")))))
diff --git a/test/metabase/api/dataset_test.clj b/test/metabase/api/dataset_test.clj
index 2854fd268258bd2ad81d6a0d654bc79f512e177e..9815ea9113bc5cc9b923f475d8b5da18b52ad387 100644
--- a/test/metabase/api/dataset_test.clj
+++ b/test/metabase/api/dataset_test.clj
@@ -7,17 +7,22 @@
             [dk.ative.docjure.spreadsheet :as spreadsheet]
             [expectations :refer :all]
             [medley.core :as m]
-            [metabase.models.query-execution :refer [QueryExecution]]
-            [metabase.query-processor :as qp]
-            [metabase.query-processor.middleware.expand :as ql]
+            [metabase
+             [query-processor :as qp]
+             [util :as u]]
+            [metabase.models
+             [card :refer [Card]]
+             [database :as database]
+             [query-execution :refer [QueryExecution]]]
             [metabase.test
-             [data :refer :all]
+             [data :as data :refer :all]
              [util :as tu]]
             [metabase.test.data
              [dataset-definitions :as defs]
              [datasets :refer [expect-with-engine]]
              [users :refer :all]]
-            [toucan.db :as db]))
+            [toucan.db :as db]
+            [toucan.util.test :as tt]))
 
 (defn user-details [user]
   (tu/match-$ user
@@ -44,7 +49,7 @@
 ;;; ## POST /api/meta/dataset
 ;; Just a basic sanity check to make sure Query Processor endpoint is still working correctly.
 (expect
-  [;; API call response
+  [ ;; API call response
    {:data                   {:rows    [[1000]]
                              :columns ["count"]
                              :cols    [{:base_type "type/Integer", :special_type "type/Number", :name "count", :display_name "count", :id nil, :table_id nil,
@@ -53,11 +58,10 @@
     :row_count              1
     :status                 "completed"
     :context                "ad-hoc"
-    :json_query             (-> (wrap-inner-query
-                                  (query checkins
-                                    (ql/aggregation (ql/count))))
+    :json_query             (-> (data/mbql-query checkins
+                                  {:aggregation [[:count]]})
                                 (assoc :type "query")
-                                (assoc-in [:query :aggregation] [{:aggregation-type "count", :custom-name nil}])
+                                (assoc-in [:query :aggregation] [["count"]])
                                 (assoc :constraints qp/default-query-constraints))
     :started_at             true
     :running_time           true
@@ -76,9 +80,8 @@
     :id           true
     :started_at   true
     :running_time true}]
-  (let [result ((user->client :rasta) :post 200 "dataset" (wrap-inner-query
-                                                            (query checkins
-                                                              (ql/aggregation (ql/count)))))]
+  (let [result ((user->client :rasta) :post 200 "dataset" (data/mbql-query checkins
+                                                            {:aggregation [[:count]]}))]
     [(format-response result)
      (format-response (most-recent-query-execution))]))
 
@@ -162,8 +165,7 @@
    ["4" "2014-03-11" "5" "4"]
    ["5" "2013-05-05" "3" "49"]]
   (let [result ((user->client :rasta) :post 200 "dataset/csv" :query
-                (json/generate-string (wrap-inner-query
-                                        (query checkins))))]
+                (json/generate-string (data/mbql-query checkins)))]
     (take 5 (parse-and-sort-csv result))))
 
 ;; Check an empty date column
@@ -175,8 +177,7 @@
    ["5" "2013-05-05" "" "3" "49"]]
   (with-db (get-or-create-database! defs/test-data-with-null-date-checkins)
     (let [result ((user->client :rasta) :post 200 "dataset/csv" :query
-                  (json/generate-string (wrap-inner-query
-                                          (query checkins))))]
+                  (json/generate-string (data/mbql-query checkins)))]
       (take 5 (parse-and-sort-csv result)))))
 
 ;; SQLite doesn't return proper date objects but strings, they just pass through the qp untouched
@@ -187,18 +188,29 @@
    ["4" "2014-03-11" "5" "4"]
    ["5" "2013-05-05" "3" "49"]]
   (let [result ((user->client :rasta) :post 200 "dataset/csv" :query
-                (json/generate-string (wrap-inner-query
-                                        (query checkins))))]
+                (json/generate-string (data/mbql-query checkins)))]
     (take 5 (parse-and-sort-csv result))))
 
 ;; DateTime fields are untouched when exported
 (expect
-  [["1" "Plato Yeshua" "2014-04-01T08:30:00.000Z"]
+  [["1" "Plato Yeshua"        "2014-04-01T08:30:00.000Z"]
    ["2" "Felipinho Asklepios" "2014-12-05T15:15:00.000Z"]
    ["3" "Kaneonuskatew Eiran" "2014-11-06T16:15:00.000Z"]
-   ["4" "Simcha Yan" "2014-01-01T08:30:00.000Z"]
-   ["5" "Quentin Sören" "2014-10-03T17:30:00.000Z"]]
+   ["4" "Simcha Yan"          "2014-01-01T08:30:00.000Z"]
+   ["5" "Quentin Sören"       "2014-10-03T17:30:00.000Z"]]
   (let [result ((user->client :rasta) :post 200 "dataset/csv" :query
-                (json/generate-string (wrap-inner-query
-                                        (query users))))]
+                (json/generate-string (data/mbql-query users)))]
     (take 5 (parse-and-sort-csv result))))
+
+;; Check that we can export the results of a nested query
+(expect
+  16
+  (tt/with-temp Card [card {:dataset_query {:database (id)
+                                            :type     :native
+                                            :native   {:query "SELECT * FROM USERS;"}}}]
+    (let [result ((user->client :rasta) :post 200 "dataset/csv"
+                  :query (json/generate-string
+                          {:database database/virtual-id
+                           :type     :query
+                           :query    {:source-table (str "card__" (u/get-id card))}}))]
+      (count (csv/read-csv result)))))
diff --git a/test/metabase/api/embed_test.clj b/test/metabase/api/embed_test.clj
index 57dc48532ad914a333b3566dbd4e45584158dcbc..8c4331c1f7035eaa3248fabaf0f625d736cdf15d 100644
--- a/test/metabase/api/embed_test.clj
+++ b/test/metabase/api/embed_test.clj
@@ -143,7 +143,7 @@
     (with-temp-card [card {:enable_embedding true
                            :dataset_query    {:database (data/id)
                                               :type     :native
-                                              :native   {:template_tags {:a {:type "date", :name "a", :display_name "a"}
+                                              :native   {:template-tags {:a {:type "date", :name "a", :display_name "a"}
                                                                          :b {:type "date", :name "b", :display_name "b"}
                                                                          :c {:type "date", :name "c", :display_name "c"}
                                                                          :d {:type "date", :name "d", :display_name "d"}}}}
@@ -284,11 +284,11 @@
   {:dataset_query    {:database (data/id)
                       :type     :native
                       :native   {:query         "SELECT COUNT(*) AS \"count\" FROM CHECKINS WHERE {{date}}"
-                                 :template_tags {:date {:name         "date"
-                                                        :display_name "Date"
+                                 :template-tags {:date {:name         "date"
+                                                        :display-name "Date"
                                                         :type         "dimension"
                                                         :dimension    [:field-id (data/id :checkins :date)]
-                                                        :widget_type  "date/quarter-year"}}}}
+                                                        :widget-type  "date/quarter-year"}}}}
    :enable_embedding true
    :embedding_params {:date :enabled}})
 
diff --git a/test/metabase/api/field_test.clj b/test/metabase/api/field_test.clj
index 5217edf2ce441d4334cae5d565a53f32da93bf65..1f478c65d11d3a572f2231bcc6e94cafa56940f5 100644
--- a/test/metabase/api/field_test.clj
+++ b/test/metabase/api/field_test.clj
@@ -46,7 +46,6 @@
   (tu/match-$ (Field (data/id :users :name))
     {:description         nil
      :table_id            (data/id :users)
-     :raw_column_id       $
      :fingerprint         $
      :fingerprint_version $
      :table               (tu/match-$ (Table (data/id :users))
@@ -66,7 +65,6 @@
                              :caveats                 nil
                              :points_of_interest      nil
                              :show_in_getting_started false
-                             :raw_table_id            $
                              :created_at              $
                              :fields_hash             $})
      :special_type        "type/Name"
diff --git a/test/metabase/api/getting_started_guide_test.clj b/test/metabase/api/getting_started_guide_test.clj
deleted file mode 100644
index 6cca9720f8ebd03f67565f42713af693b9e18ee7..0000000000000000000000000000000000000000
--- a/test/metabase/api/getting_started_guide_test.clj
+++ /dev/null
@@ -1,8 +0,0 @@
-(ns metabase.api.getting-started-guide-test
-  (:require [expectations :refer :all]
-            [metabase.test.data.users :refer [user->client]]))
-
-;; just make sure the getting started guide endpoint works and returns the correct keys
-(expect
-  #{:metric_important_fields :most_important_dashboard :things_to_know :important_metrics :important_segments :important_tables :contact}
-  (set (keys ((user->client :rasta) :get 200 "getting_started"))))
diff --git a/test/metabase/api/getting_started_test.clj b/test/metabase/api/getting_started_test.clj
deleted file mode 100644
index c7052dad7db73284180df5caa79a85079f0b8cf8..0000000000000000000000000000000000000000
--- a/test/metabase/api/getting_started_test.clj
+++ /dev/null
@@ -1,40 +0,0 @@
-(ns metabase.api.getting-started-test
-  (:require [expectations :refer [expect]]
-            [metabase.models
-             [collection :refer [Collection]]
-             [dashboard :refer [Dashboard]]
-             [permissions :as perms]
-             [permissions-group :as group]]
-            [metabase.test.data.users :as test-users]
-            [metabase.util :as u]
-            [toucan.util.test :as tt]))
-
-;; make sure that we can fetch the GSG stuff with a 'show in getting started' Dashboard
-;; ...but you shouldn't know about it if you don't have read perms for it
-(expect
-  {:things_to_know           nil
-   :contact                  {:name nil, :email nil}
-   :most_important_dashboard false
-   :important_metrics        []
-   :important_tables         []
-   :important_segments       []
-   :metric_important_fields  {}}
-  (tt/with-temp Dashboard [_ {:show_in_getting_started true}]
-    (-> ((test-users/user->client :rasta) :get 200 "getting_started")
-        (update :most_important_dashboard integer?))))
-
-;; ...but if you do have read perms, then you should get to see it!
-(expect
-  {:things_to_know           nil
-   :contact                  {:name nil, :email nil}
-   :most_important_dashboard true
-   :important_metrics        []
-   :important_tables         []
-   :important_segments       []
-   :metric_important_fields  {}}
-  (tt/with-temp* [Collection [collection]
-                  Dashboard  [_ {:collection_id           (u/get-id collection)
-                                 :show_in_getting_started true}]]
-    (perms/grant-collection-read-permissions! (group/all-users) collection)
-    (-> ((test-users/user->client :rasta) :get 200 "getting_started")
-        (update :most_important_dashboard integer?))))
diff --git a/test/metabase/api/metric_test.clj b/test/metabase/api/metric_test.clj
index 34e80399aee9188d21da2eccbfdcf2ce463b2cd5..ba4e7c5e42aecf83968a28ed0541b14eb4193bd8 100644
--- a/test/metabase/api/metric_test.clj
+++ b/test/metabase/api/metric_test.clj
@@ -27,7 +27,7 @@
    :created_at              true
    :updated_at              true
    :archived                false
-   :definition              {}})
+   :definition              nil})
 
 (defn- user-details [user]
   (tu/match-$ user
@@ -232,7 +232,7 @@
     :user         (-> (user-details (fetch-user :rasta))
                       (dissoc :email :date_joined :last_login :is_superuser :is_qbnewb))
     :diff         {:name       {:after "b"}
-                   :definition {:after {:filter ["AND" [">" 1 25]]}}}
+                   :definition {:after {:filter [">" ["field-id" 1] 25]}}}
     :description  nil}]
   (tt/with-temp* [Database [{database-id :id}]
                   Table    [{table-id :id} {:db_id database-id}]
@@ -245,17 +245,17 @@
                                             :points_of_interest      nil
                                             :how_is_this_calculated  nil
                                             :definition              {:database 123
-                                                                      :query    {:filter ["In the Land of Metabase where the Datas lie"]}}}]
+                                                                      :query    {:filter [:= [:field-id 10] 20]}}}]
                   Revision [_              {:model       "Metric"
                                             :model_id    id
                                             :object      {:name "b"
-                                                          :definition {:filter ["AND" [">" 1 25]]}}
+                                                          :definition {:filter [:and [:> 1 25]]}}
                                             :is_creation true}]
                   Revision [_              {:model    "Metric"
                                             :model_id id
                                             :user_id  (user->id :crowberto)
                                             :object   {:name "c"
-                                                       :definition {:filter ["AND" [">" 1 25]]}}
+                                                       :definition {:filter [:and [:> 1 25]]}}
                                             :message  "updated"}]]
     (doall (for [revision ((user->client :crowberto) :get 200 (format "metric/%d/revisions" id))]
              (dissoc revision :timestamp :id)))))
@@ -306,7 +306,7 @@
      :diff         {:name        {:after "One Metric to rule them all, one metric to define them"}
                     :description {:after "One metric to bring them all, and in the DataModel bind them"}
                     :definition  {:after {:database 123
-                                          :query    {:filter ["In the Land of Metabase where the Datas lie"]}}}}
+                                          :query    {:filter ["=" ["field-id" 10] 20]}}}}
      :description  nil}]]
   (tt/with-temp* [Database [{database-id :id}]
                   Table    [{table-id :id}    {:db_id database-id}]
@@ -327,7 +327,7 @@
                                                                          :points_of_interest      nil
                                                                          :how_is_this_calculated  nil
                                                                          :definition              {:database 123
-                                                                                                   :query    {:filter ["In the Land of Metabase where the Datas lie"]}}}}]
+                                                                                                   :query    {:filter [:= [:field-id 10] 20]}}}}]
                   Revision [{revision-id :id} {:model       "Metric"
                                                :model_id    id
                                                :object      {:creator_id              (user->id :crowberto)
@@ -339,7 +339,7 @@
                                                              :points_of_interest      nil
                                                              :how_is_this_calculated  nil
                                                              :definition              {:database 123
-                                                                                       :query    {:filter ["In the Land of Metabase where the Datas lie"]}}}
+                                                                                       :query    {:filter [:= [:field-id 10] 20]}}}
                                                :is_creation true}]
                   Revision [_                 {:model    "Metric"
                                                :model_id id
@@ -353,7 +353,7 @@
                                                           :points_of_interest      nil
                                                           :how_is_this_calculated  nil
                                                           :definition              {:database 123
-                                                                                    :query    {:filter ["In the Land of Metabase where the Datas lie"]}}}
+                                                                                    :query    {:filter [:= [:field-id 10] 20]}}}
                                                :message  "updated"}]]
     [(dissoc ((user->client :crowberto) :post 200 (format "metric/%d/revert" id) {:revision_id revision-id}) :id :timestamp)
      (doall (for [revision ((user->client :crowberto) :get 200 (format "metric/%d/revisions" id))]
diff --git a/test/metabase/api/preview_embed_test.clj b/test/metabase/api/preview_embed_test.clj
index 7113c0816a9cfdc8d95cb2b7cb7dfd6aec38e04a..8185cb837f5e3fc5b04397b90f30dc663dc03aa2 100644
--- a/test/metabase/api/preview_embed_test.clj
+++ b/test/metabase/api/preview_embed_test.clj
@@ -55,7 +55,7 @@
     (embed-test/with-temp-card [card {:dataset_query
                                       {:database (data/id)
                                        :type     :native
-                                       :native   {:template_tags {:a {:type "date", :name "a", :display_name "a"}
+                                       :native   {:template-tags {:a {:type "date", :name "a", :display_name "a"}
                                                                   :b {:type "date", :name "b", :display_name "b"}
                                                                   :c {:type "date", :name "c", :display_name "c"}
                                                                   :d {:type "date", :name "d", :display_name "d"}}}}}]
@@ -64,7 +64,7 @@
                                                                                              :c "enabled"
                                                                                              :d "enabled"}
                                                                          :params            {:c 100}}))
-          :parameters ))))
+          :parameters))))
 
 
 ;;; ------------------------------------ GET /api/preview_embed/card/:token/query ------------------------------------
@@ -358,7 +358,7 @@
     (tt/with-temp Card [card {:dataset_query {:database (data/id)
                                               :type     :native
                                               :native   {:query         "SELECT {{num}} AS num"
-                                                         :template_tags {:num {:name         "num"
+                                                         :template-tags {:num {:name         "num"
                                                                                :display_name "Num"
                                                                                :type         "number"
                                                                                :required     true
diff --git a/test/metabase/api/public_test.clj b/test/metabase/api/public_test.clj
index e1dd4619b888739ff4804061f950e3d3b8a7f2e8..224b55116ae0e5c861622901e00809183dbaccaa 100644
--- a/test/metabase/api/public_test.clj
+++ b/test/metabase/api/public_test.clj
@@ -31,7 +31,7 @@
 (defn count-of-venues-card []
   {:dataset_query {:database (data/id)
                    :type     :query
-                   :query    {:source_table (data/id :venues)
+                   :query    {:source-table (data/id :venues)
                               :aggregation  [:count]}}})
 
 (defn- shared-obj []
@@ -118,11 +118,11 @@
                                                             "LEFT JOIN categories ON venues.category_id = categories.id "
                                                             "WHERE {{category}}")
                                         :collection    "CATEGORIES"
-                                        :template_tags {:category {:name         "category"
-                                                                   :display_name "Category"
+                                        :template-tags {:category {:name         "category"
+                                                                   :display-name "Category"
                                                                    :type         "dimension"
                                                                    :dimension    ["field-id" (data/id :categories :name)]
-                                                                   :widget_type  "category"
+                                                                   :widget-type  "category"
                                                                    :required     true}}}}}]
     (-> (:param_values (#'public-api/public-card :id (u/get-id card)))
         (update-in [(data/id :categories :name) :values] count))))
@@ -198,11 +198,11 @@
     :dataset_query {:database (data/id)
                     :type     :native
                     :native   {:query         "SELECT COUNT(*) AS \"count\" FROM CHECKINS WHERE {{date}}"
-                               :template_tags {:date {:name         "date"
-                                                      :display_name "Date"
+                               :template-tags {:date {:name         "date"
+                                                      :display-name "Date"
                                                       :type         "dimension"
                                                       :dimension    [:field-id (data/id :checkins :date)]
-                                                      :widget_type  "date/quarter-year"}}}}))
+                                                      :widget-type  "date/quarter-year"}}}}))
 
 (expect
   "count\n107\n"
@@ -373,8 +373,8 @@
     (tt/with-temp Card [card {:dataset_query {:database (data/id)
                                               :type     :native
                                               :native   {:query         "SELECT {{num}} AS num"
-                                                         :template_tags {:num {:name         "num"
-                                                                               :display_name "Num"
+                                                         :template-tags {:num {:name         "num"
+                                                                               :display-name "Num"
                                                                                :type         "number"
                                                                                :required     true
                                                                                :default      "1"}}}}}]
@@ -462,9 +462,9 @@
    (tt/with-temp Card [card {:dataset_query {:database (data/id)
                                              :type     :native
                                              :native   {:query         "SELECT {{msg}} AS message"
-                                                        :template_tags {:msg {:id           "181da7c5"
+                                                        :template-tags {:msg {:id           "181da7c5"
                                                                               :name         "msg"
-                                                                              :display_name "Message"
+                                                                              :display-name "Message"
                                                                               :type         "text"
                                                                               :required     true
                                                                               :default      "Wow"}}}}}]
@@ -518,8 +518,8 @@
     (db/update! Card (u/get-id card)
       :dataset_query {:database (data/id)
                       :type     :native
-                      :native   {:template_tags {:price {:name         "price"
-                                                         :display_name "Price"
+                      :native   {:template-tags {:price {:name         "price"
+                                                         :display-name "Price"
                                                          :type         "dimension"
                                                          :dimension    ["field-id" (data/id :venues :price)]}}}})
     (add-price-param-to-dashboard! dash)
@@ -567,8 +567,8 @@
    {:database (data/id)
     :type     :native
     :native   {:query         "SELECT COUNT(*) FROM VENUES WHERE {{x}}"
-               :template_tags {:x {:name         :x
-                                   :display_name "X"
+               :template-tags {:x {:name         :x
+                                   :display-name "X"
                                    :type         :dimension
                                    :dimension    [:field-id (data/id :venues :name)]}}}}})
 
diff --git a/test/metabase/api/pulse_test.clj b/test/metabase/api/pulse_test.clj
index 87475c899b2f84d9c337389e489bd40ef441a7c8..bbc76a33db91eb26d021163a61f05693887dd755 100644
--- a/test/metabase/api/pulse_test.clj
+++ b/test/metabase/api/pulse_test.clj
@@ -72,14 +72,15 @@
                         (update card :collection_id boolean)))))
 
 (defn- do-with-pulses-in-a-collection [grant-collection-perms-fn! pulses-or-ids f]
-  (tt/with-temp Collection [collection]
-    (grant-collection-perms-fn! (perms-group/all-users) collection)
-    ;; use db/execute! instead of db/update! so the updated_at field doesn't get automatically updated!
-    (when (seq pulses-or-ids)
-      (db/execute! {:update Pulse
-                    :set    [[:collection_id (u/get-id collection)]]
-                    :where  [:in :id (set (map u/get-id pulses-or-ids))]}))
-    (f)))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [collection]
+      (grant-collection-perms-fn! (perms-group/all-users) collection)
+      ;; use db/execute! instead of db/update! so the updated_at field doesn't get automatically updated!
+      (when (seq pulses-or-ids)
+        (db/execute! {:update Pulse
+                      :set    [[:collection_id (u/get-id collection)]]
+                      :where  [:in :id (set (map u/get-id pulses-or-ids))]}))
+      (f))))
 
 (defmacro ^:private with-pulses-in-readable-collection [pulses-or-ids & body]
   `(do-with-pulses-in-a-collection perms/grant-collection-read-permissions! ~pulses-or-ids (fn [] ~@body)))
@@ -253,33 +254,54 @@
                             :schedule_hour 12
                             :recipients    []})]
     :collection_id true})
-  (tt/with-temp Collection [collection]
-    (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
-    (tu/with-model-cleanup [Pulse]
-      (card-api-test/with-cards-in-readable-collection [card-1 card-2]
-        (-> ((user->client :rasta) :post 200 "pulse" {:name          "A Pulse"
-                                                      :collection_id (u/get-id collection)
-                                                      :cards         [{:id          (u/get-id card-1)
-                                                                       :include_csv true
-                                                                       :include_xls true}
-                                                                      {:id          (u/get-id card-2)
-                                                                       :include_csv false
-                                                                       :include_xls false}]
-                                                      :channels      [daily-email-channel]
-                                                      :skip_if_empty false})
-            pulse-response
-            (update :channels remove-extra-channels-fields))))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [collection]
+      (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
+      (tu/with-model-cleanup [Pulse]
+        (card-api-test/with-cards-in-readable-collection [card-1 card-2]
+          (-> ((user->client :rasta) :post 200 "pulse" {:name          "A Pulse"
+                                                        :collection_id (u/get-id collection)
+                                                        :cards         [{:id          (u/get-id card-1)
+                                                                         :include_csv true
+                                                                         :include_xls true}
+                                                                        {:id          (u/get-id card-2)
+                                                                         :include_csv false
+                                                                         :include_xls false}]
+                                                        :channels      [daily-email-channel]
+                                                        :skip_if_empty false})
+              pulse-response
+              (update :channels remove-extra-channels-fields)))))))
 
 ;; Make sure we can create a Pulse with a Collection position
 (expect
   #metabase.models.pulse.PulseInstance{:collection_id true, :collection_position 1}
-  (tu/with-model-cleanup [Pulse]
-    (let [pulse-name (tu/random-name)]
-      (tt/with-temp* [Card       [card]
-                      Collection [collection]]
-        (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
-        (card-api-test/with-cards-in-readable-collection [card]
-          ((user->client :rasta) :post 200 "pulse" {:name                pulse-name
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tu/with-model-cleanup [Pulse]
+      (let [pulse-name (tu/random-name)]
+        (tt/with-temp* [Card       [card]
+                        Collection [collection]]
+          (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
+          (card-api-test/with-cards-in-readable-collection [card]
+            ((user->client :rasta) :post 200 "pulse" {:name                pulse-name
+                                                      :cards               [{:id          (u/get-id card)
+                                                                             :include_csv false
+                                                                             :include_xls false}]
+                                                      :channels            [daily-email-channel]
+                                                      :skip_if_empty       false
+                                                      :collection_id       (u/get-id collection)
+                                                      :collection_position 1})
+            (some-> (db/select-one [Pulse :collection_id :collection_position] :name pulse-name)
+                    (update :collection_id (partial = (u/get-id collection))))))))))
+
+;; ...but not if we don't have permissions for the Collection
+(expect
+  nil
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tu/with-model-cleanup [Pulse]
+      (let [pulse-name (tu/random-name)]
+        (tt/with-temp* [Card       [card]
+                        Collection [collection]]
+          ((user->client :rasta) :post 403 "pulse" {:name                pulse-name
                                                     :cards               [{:id          (u/get-id card)
                                                                            :include_csv false
                                                                            :include_xls false}]
@@ -290,24 +312,6 @@
           (some-> (db/select-one [Pulse :collection_id :collection_position] :name pulse-name)
                   (update :collection_id (partial = (u/get-id collection)))))))))
 
-;; ...but not if we don't have permissions for the Collection
-(expect
-  nil
-  (tu/with-model-cleanup [Pulse]
-    (let [pulse-name (tu/random-name)]
-      (tt/with-temp* [Card       [card]
-                      Collection [collection]]
-        ((user->client :rasta) :post 403 "pulse" {:name                pulse-name
-                                                  :cards               [{:id          (u/get-id card)
-                                                                         :include_csv false
-                                                                         :include_xls false}]
-                                                  :channels            [daily-email-channel]
-                                                  :skip_if_empty       false
-                                                  :collection_id       (u/get-id collection)
-                                                  :collection_position 1})
-        (some-> (db/select-one [Pulse :collection_id :collection_position] :name pulse-name)
-                (update :collection_id (partial = (u/get-id collection))))))))
-
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                               PUT /api/pulse/:id                                               |
@@ -493,6 +497,41 @@
      {:collection_position nil})
     (db/select-one-field :collection_position Pulse :id (u/get-id pulse))))
 
+;; Can we archive a Pulse?
+(expect
+  (pulse-test/with-pulse-in-collection [_ collection pulse]
+    (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
+    ((user->client :rasta) :put 200 (str "pulse/" (u/get-id pulse))
+     {:archived true})
+    (db/select-one-field :archived Pulse :id (u/get-id pulse))))
+
+;; Can we unarchive a Pulse?
+(expect
+  false
+  (pulse-test/with-pulse-in-collection [_ collection pulse]
+    (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
+    (db/update! Pulse (u/get-id pulse) :archived true)
+    ((user->client :rasta) :put 200 (str "pulse/" (u/get-id pulse))
+     {:archived false})
+    (db/select-one-field :archived Pulse :id (u/get-id pulse))))
+
+;; Does unarchiving a Pulse affect its Cards & Recipients? It shouldn't. This should behave as a PATCH-style endpoint!
+(expect
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection            [collection]
+                    Pulse                 [pulse {:collection_id (u/get-id collection)}]
+                    PulseChannel          [pc    {:pulse_id (u/get-id pulse)}]
+                    PulseChannelRecipient [pcr   {:pulse_channel_id (u/get-id pc), :user_id (user->id :rasta)}]
+                    Card                  [card]]
+      (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
+      ((user->client :rasta) :put 200 (str "pulse/" (u/get-id pulse))
+       {:archived true})
+      ((user->client :rasta) :put 200 (str "pulse/" (u/get-id pulse))
+       {:archived false})
+      (and (db/exists? PulseChannel :id (u/get-id pc))
+           (db/exists? PulseChannelRecipient :id (u/get-id pcr))))))
+
+
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                   UPDATING PULSE COLLECTION POSITIONS                                          |
 ;;; +----------------------------------------------------------------------------------------------------------------+
@@ -503,15 +542,16 @@
    "a" 2
    "b" 3
    "c" 4}
-  (tt/with-temp Collection [{coll-id :id :as collection}]
-    (card-api-test/with-ordered-items collection [Pulse a
-                                                  Pulse b
-                                                  Pulse c
-                                                  Pulse d]
-      (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
-      ((user->client :rasta) :put 200 (str "pulse/" (u/get-id d))
-       {:collection_position 1})
-      (card-api-test/get-name->collection-position :rasta coll-id))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [{coll-id :id :as collection}]
+      (card-api-test/with-ordered-items collection [Pulse a
+                                                    Pulse b
+                                                    Pulse c
+                                                    Pulse d]
+        (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
+        ((user->client :rasta) :put 200 (str "pulse/" (u/get-id d))
+         {:collection_position 1})
+        (card-api-test/get-name->collection-position :rasta coll-id)))))
 
 ;; Change the position of b to 4, will dec c and d
 (expect
@@ -519,15 +559,16 @@
    "c" 2
    "d" 3
    "b" 4}
-  (tt/with-temp Collection [{coll-id :id :as collection}]
-    (card-api-test/with-ordered-items collection [Card      a
-                                                  Pulse     b
-                                                  Card      c
-                                                  Dashboard d]
-      (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
-      ((user->client :rasta) :put 200 (str "pulse/" (u/get-id b))
-       {:collection_position 4})
-      (card-api-test/get-name->collection-position :rasta coll-id))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [{coll-id :id :as collection}]
+      (card-api-test/with-ordered-items collection [Card      a
+                                                    Pulse     b
+                                                    Card      c
+                                                    Dashboard d]
+        (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
+        ((user->client :rasta) :put 200 (str "pulse/" (u/get-id b))
+         {:collection_position 4})
+        (card-api-test/get-name->collection-position :rasta coll-id)))))
 
 ;; Change the position of d to the 2, should inc b and c
 (expect
@@ -535,15 +576,16 @@
    "d" 2
    "b" 3
    "c" 4}
-  (tt/with-temp Collection [{coll-id :id :as collection}]
-    (card-api-test/with-ordered-items collection [Card      a
-                                                  Card      b
-                                                  Dashboard c
-                                                  Pulse     d]
-      (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
-      ((user->client :rasta) :put 200 (str "pulse/" (u/get-id d))
-       {:collection_position 2})
-      (card-api-test/get-name->collection-position :rasta coll-id))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [{coll-id :id :as collection}]
+      (card-api-test/with-ordered-items collection [Card      a
+                                                    Card      b
+                                                    Dashboard c
+                                                    Pulse     d]
+        (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
+        ((user->client :rasta) :put 200 (str "pulse/" (u/get-id d))
+         {:collection_position 2})
+        (card-api-test/get-name->collection-position :rasta coll-id)))))
 
 ;; Change the position of a to the 4th, will decrement all existing items
 (expect
@@ -551,15 +593,16 @@
    "c" 2
    "d" 3
    "a" 4}
-  (tt/with-temp Collection [{coll-id :id :as collection}]
-    (card-api-test/with-ordered-items collection [Pulse     a
-                                                  Dashboard b
-                                                  Card      c
-                                                  Pulse     d]
-      (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
-      ((user->client :rasta) :put 200 (str "pulse/" (u/get-id a))
-       {:collection_position 4})
-      (card-api-test/get-name->collection-position :rasta coll-id))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [{coll-id :id :as collection}]
+      (card-api-test/with-ordered-items collection [Pulse     a
+                                                    Dashboard b
+                                                    Card      c
+                                                    Pulse     d]
+        (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
+        ((user->client :rasta) :put 200 (str "pulse/" (u/get-id a))
+         {:collection_position 4})
+        (card-api-test/get-name->collection-position :rasta coll-id)))))
 
 ;; Change the position of the d to the 1st, will increment all existing items
 (expect
@@ -567,15 +610,16 @@
    "a" 2
    "b" 3
    "c" 4}
-  (tt/with-temp Collection [{coll-id :id :as collection}]
-    (card-api-test/with-ordered-items collection [Dashboard a
-                                                  Dashboard b
-                                                  Card      c
-                                                  Pulse     d]
-      (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
-      ((user->client :rasta) :put 200 (str "pulse/" (u/get-id d))
-       {:collection_position 1})
-      (card-api-test/get-name->collection-position :rasta coll-id))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [{coll-id :id :as collection}]
+      (card-api-test/with-ordered-items collection [Dashboard a
+                                                    Dashboard b
+                                                    Card      c
+                                                    Pulse     d]
+        (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
+        ((user->client :rasta) :put 200 (str "pulse/" (u/get-id d))
+         {:collection_position 1})
+        (card-api-test/get-name->collection-position :rasta coll-id)))))
 
 ;; Check that no position change, but changing collections still triggers a fixup of both collections
 ;; Moving `c` from collection-1 to collection-2, `c` is now at position 3 in collection 2
@@ -588,22 +632,23 @@
     "c" 3
     "g" 4
     "h" 5}]
-  (tt/with-temp* [Collection [collection-1]
-                  Collection [collection-2]]
-    (card-api-test/with-ordered-items collection-1 [Pulse     a
-                                                    Card      b
-                                                    Pulse     c
-                                                    Dashboard d]
-      (card-api-test/with-ordered-items collection-2 [Card      e
-                                                      Card      f
-                                                      Dashboard g
-                                                      Dashboard h]
-        (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection-1)
-        (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection-2)
-        ((user->client :rasta) :put 200 (str "pulse/" (u/get-id c))
-         {:collection_id (u/get-id collection-2)})
-        [(card-api-test/get-name->collection-position :rasta (u/get-id collection-1))
-         (card-api-test/get-name->collection-position :rasta (u/get-id collection-2))]))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [collection-1]
+                    Collection [collection-2]]
+      (card-api-test/with-ordered-items collection-1 [Pulse     a
+                                                      Card      b
+                                                      Pulse     c
+                                                      Dashboard d]
+        (card-api-test/with-ordered-items collection-2 [Card      e
+                                                        Card      f
+                                                        Dashboard g
+                                                        Dashboard h]
+          (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection-1)
+          (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection-2)
+          ((user->client :rasta) :put 200 (str "pulse/" (u/get-id c))
+           {:collection_id (u/get-id collection-2)})
+          [(card-api-test/get-name->collection-position :rasta (u/get-id collection-1))
+           (card-api-test/get-name->collection-position :rasta (u/get-id collection-2))])))))
 
 ;; Check that moving a pulse to another collection, with a changed position will fixup both collections
 ;; Moving `b` to collection 2, giving it a position of 1
@@ -616,22 +661,23 @@
     "f" 3
     "g" 4
     "h" 5}]
-  (tt/with-temp* [Collection [collection-1]
-                  Collection [collection-2]]
-    (card-api-test/with-ordered-items collection-1 [Pulse     a
-                                                    Pulse     b
-                                                    Dashboard c
-                                                    Card      d]
-      (card-api-test/with-ordered-items collection-2 [Card      e
-                                                      Card      f
-                                                      Pulse     g
-                                                      Dashboard h]
-        (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection-1)
-        (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection-2)
-        ((user->client :rasta) :put 200 (str "pulse/" (u/get-id b))
-         {:collection_id (u/get-id collection-2), :collection_position 1})
-        [(card-api-test/get-name->collection-position :rasta (u/get-id collection-1))
-         (card-api-test/get-name->collection-position :rasta (u/get-id collection-2))]))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [collection-1]
+                    Collection [collection-2]]
+      (card-api-test/with-ordered-items collection-1 [Pulse     a
+                                                      Pulse     b
+                                                      Dashboard c
+                                                      Card      d]
+        (card-api-test/with-ordered-items collection-2 [Card      e
+                                                        Card      f
+                                                        Pulse     g
+                                                        Dashboard h]
+          (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection-1)
+          (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection-2)
+          ((user->client :rasta) :put 200 (str "pulse/" (u/get-id b))
+           {:collection_id (u/get-id collection-2), :collection_position 1})
+          [(card-api-test/get-name->collection-position :rasta (u/get-id collection-1))
+           (card-api-test/get-name->collection-position :rasta (u/get-id collection-2))])))))
 
 ;; Add a new pulse at position 2, causing existing pulses to be incremented
 (expect
@@ -642,24 +688,25 @@
     "b" 2
     "c" 3
     "d" 4}]
-  (tt/with-temp* [Collection [{coll-id :id :as collection}]
-                  Card       [card-1]]
-    (card-api-test/with-cards-in-readable-collection [card-1]
-      (card-api-test/with-ordered-items  collection [Card      a
-                                                     Dashboard c
-                                                     Pulse     d]
-        (tu/with-model-cleanup [Pulse]
-          (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
-          [(card-api-test/get-name->collection-position :rasta coll-id)
-           (do ((user->client :rasta) :post 200 "pulse" {:name                "b"
-                                                         :collection_id       (u/get-id collection)
-                                                         :cards               [{:id          (u/get-id card-1)
-                                                                                :include_csv false
-                                                                                :include_xls false}]
-                                                         :channels            [daily-email-channel]
-                                                         :skip_if_empty       false
-                                                         :collection_position 2})
-               (card-api-test/get-name->collection-position :rasta coll-id))])))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [{coll-id :id :as collection}]
+                    Card       [card-1]]
+      (card-api-test/with-cards-in-readable-collection [card-1]
+        (card-api-test/with-ordered-items  collection [Card      a
+                                                       Dashboard c
+                                                       Pulse     d]
+          (tu/with-model-cleanup [Pulse]
+            (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
+            [(card-api-test/get-name->collection-position :rasta coll-id)
+             (do ((user->client :rasta) :post 200 "pulse" {:name                "b"
+                                                           :collection_id       (u/get-id collection)
+                                                           :cards               [{:id          (u/get-id card-1)
+                                                                                  :include_csv false
+                                                                                  :include_xls false}]
+                                                           :channels            [daily-email-channel]
+                                                           :skip_if_empty       false
+                                                           :collection_position 2})
+                 (card-api-test/get-name->collection-position :rasta coll-id))]))))))
 
 ;; Add a new pulse without a position, should leave existing positions unchanged
 (expect
@@ -670,23 +717,25 @@
     "b" nil
     "c" 2
     "d" 3}]
-  (tt/with-temp* [Collection [{coll-id :id :as collection}]
-                  Card       [card-1]]
-    (card-api-test/with-cards-in-readable-collection [card-1]
-      (card-api-test/with-ordered-items collection [Pulse     a
-                                                    Card      c
-                                                    Dashboard d]
-        (tu/with-model-cleanup [Pulse]
-          (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
-          [(card-api-test/get-name->collection-position :rasta coll-id)
-           (do ((user->client :rasta) :post 200 "pulse" {:name                "b"
-                                                         :collection_id       (u/get-id collection)
-                                                         :cards               [{:id          (u/get-id card-1)
-                                                                                :include_csv false
-                                                                                :include_xls false}]
-                                                         :channels            [daily-email-channel]
-                                                         :skip_if_empty       false})
-               (card-api-test/get-name->collection-position :rasta coll-id))])))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [{coll-id :id :as collection}]
+                    Card       [card-1]]
+      (card-api-test/with-cards-in-readable-collection [card-1]
+        (card-api-test/with-ordered-items collection [Pulse     a
+                                                      Card      c
+                                                      Dashboard d]
+          (tu/with-model-cleanup [Pulse]
+            (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
+            [(card-api-test/get-name->collection-position :rasta coll-id)
+             (do ((user->client :rasta) :post 200 "pulse" {:name          "b"
+                                                           :collection_id (u/get-id collection)
+                                                           :cards         [{:id          (u/get-id card-1)
+                                                                            :include_csv false
+                                                                            :include_xls false}]
+                                                           :channels      [daily-email-channel]
+                                                           :skip_if_empty false})
+                 (card-api-test/get-name->collection-position :rasta coll-id))]))))))
+
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                             DELETE /api/pulse/:id                                              |
@@ -710,12 +759,12 @@
                   Card      [card  {:dataset_query {:database (u/get-id db)
                                                     :type     "query"
                                                     :query    {:source-table (u/get-id table)
-                                                               :aggregation  {:aggregation-type "count"}}}}]
+                                                               :aggregation  [[:count]]}}}]
                   Pulse     [pulse {:name "Daily Sad Toucans"}]
                   PulseCard [_     {:pulse_id (u/get-id pulse), :card_id (u/get-id card)}]]
     (with-pulses-in-readable-collection [pulse]
       ;; revoke permissions for default group to this database
-      (perms/delete-related-permissions! (perms-group/all-users) (perms/object-path (u/get-id db)))
+      (perms/revoke-permissions! (perms-group/all-users) (u/get-id db))
       ;; now a user without permissions to the Card in question should *not* be allowed to delete the pulse
       ((user->client :rasta) :delete 403 (format "pulse/%d" (u/get-id pulse))))))
 
@@ -727,8 +776,8 @@
 ;; should come back in alphabetical order
 (tt/expect-with-temp [Pulse [pulse-1 {:name "ABCDEF"}]
                       Pulse [pulse-2 {:name "GHIJKL"}]]
-  [(assoc (pulse-details pulse-1) :read_only true, :collection_id true)
-   (assoc (pulse-details pulse-2) :read_only true, :collection_id true)]
+  [(assoc (pulse-details pulse-1) :can_write false, :collection_id true)
+   (assoc (pulse-details pulse-2) :can_write false, :collection_id true)]
   (with-pulses-in-readable-collection [pulse-1 pulse-2]
     ;; delete anything else in DB just to be sure; this step may not be neccesary any more
     (db/delete! Pulse :id [:not-in #{(u/get-id pulse-1)
@@ -736,11 +785,11 @@
     (for [pulse ((user->client :rasta) :get 200 "pulse")]
       (update pulse :collection_id boolean))))
 
-;; `read_only` property should get updated correctly based on whether current user can write
+;; `can_write` property should get updated correctly based on whether current user can write
 (tt/expect-with-temp [Pulse [pulse-1 {:name "ABCDEF"}]
                       Pulse [pulse-2 {:name "GHIJKL"}]]
-  [(assoc (pulse-details pulse-1) :read_only false)
-   (assoc (pulse-details pulse-2) :read_only false)]
+  [(assoc (pulse-details pulse-1) :can_write true)
+   (assoc (pulse-details pulse-2) :can_write true)]
   (do
     ;; delete anything else in DB just to be sure; this step may not be neccesary any more
     (db/delete! Pulse :id [:not-in #{(u/get-id pulse-1)
@@ -752,12 +801,28 @@
                       Pulse [pulse-2 {:name "GHIJKL"}]
                       Pulse [pulse-3 {:name            "AAAAAA"
                                       :alert_condition "rows"}]]
-  [(assoc (pulse-details pulse-1) :read_only true, :collection_id true)
-   (assoc (pulse-details pulse-2) :read_only true, :collection_id true)]
+  [(assoc (pulse-details pulse-1) :can_write false, :collection_id true)
+   (assoc (pulse-details pulse-2) :can_write false, :collection_id true)]
   (with-pulses-in-readable-collection [pulse-1 pulse-2 pulse-3]
     (for [pulse ((user->client :rasta) :get 200 "pulse")]
       (update pulse :collection_id boolean))))
 
+;; by default, archived Pulses should be excluded
+(expect
+  #{"Not Archived"}
+  (tt/with-temp* [Pulse [not-archived-pulse {:name "Not Archived"}]
+                  Pulse [archived-pulse     {:name "Archived", :archived true}]]
+    (with-pulses-in-readable-collection [not-archived-pulse archived-pulse]
+      (set (map :name ((user->client :rasta) :get 200 "pulse"))))))
+
+;; can we fetch archived Pulses?
+(expect
+  #{"Archived"}
+  (tt/with-temp* [Pulse [not-archived-pulse {:name "Not Archived"}]
+                  Pulse [archived-pulse     {:name "Archived", :archived true}]]
+    (with-pulses-in-readable-collection [not-archived-pulse archived-pulse]
+      (set (map :name ((user->client :rasta) :get 200 "pulse?archived=true"))))))
+
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                               GET /api/pulse/:id                                               |
@@ -765,6 +830,7 @@
 
 (tt/expect-with-temp [Pulse [pulse]]
   (assoc (pulse-details pulse)
+    :can_write     false
     :collection_id true)
   (with-pulses-in-readable-collection [pulse]
     (-> ((user->client :rasta) :get 200 (str "pulse/" (u/get-id pulse)))
@@ -786,32 +852,88 @@
   {:response {:ok true}
    :emails   (et/email-to :rasta {:subject "Pulse: Daily Sad Toucans"
                                   :body    {"Daily Sad Toucans" true}})}
-  (tu/with-model-cleanup [Pulse]
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tu/with-model-cleanup [Pulse]
+      (et/with-fake-inbox
+        (data/with-db (data/get-or-create-database! defs/sad-toucan-incidents)
+          (tt/with-temp* [Collection [collection]
+                          Database   [db]
+                          Table      [table {:db_id (u/get-id db)}]
+                          Card       [card  {:dataset_query {:database (u/get-id db)
+                                                             :type     "query"
+                                                             :query    {:source-table (u/get-id table),
+                                                                        :aggregation  [[:count]]}}}]]
+            (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
+            (card-api-test/with-cards-in-readable-collection [card]
+              (array-map
+               :response
+               ((user->client :rasta) :post 200 "pulse/test" {:name          "Daily Sad Toucans"
+                                                              :collection_id (u/get-id collection)
+                                                              :cards         [{:id          (u/get-id card)
+                                                                               :include_csv false
+                                                                               :include_xls false}]
+                                                              :channels      [{:enabled       true
+                                                                               :channel_type  "email"
+                                                                               :schedule_type "daily"
+                                                                               :schedule_hour 12
+                                                                               :schedule_day  nil
+                                                                               :recipients    [(fetch-user :rasta)]}]
+                                                              :skip_if_empty false})
+
+               :emails
+               (et/regex-email-bodies #"Daily Sad Toucans")))))))))
+
+;; This test follows a flow that the user/UI would follow by first creating a pulse, then making a small change to
+;; that pulse and testing it. The primary purpose of this test is to ensure tha the pulse/test endpoint accepts data
+;; of the same format that the pulse GET returns
+(tt/expect-with-temp [Card [card-1]
+                      Card [card-2]]
+  {:response {:ok true}
+   :emails   (et/email-to :rasta {:subject "Pulse: A Pulse"
+                                  :body    {"A Pulse" true}})}
+  (card-api-test/with-cards-in-readable-collection [card-1 card-2]
     (et/with-fake-inbox
-      (data/with-db (data/get-or-create-database! defs/sad-toucan-incidents)
-        (tt/with-temp* [Collection [collection]
-                        Database   [db]
-                        Table      [table {:db_id (u/get-id db)}]
-                        Card       [card  {:dataset_query {:database (u/get-id db)
-                                                           :type     "query"
-                                                           :query    {:source-table (u/get-id table),
-                                                                      :aggregation  {:aggregation-type "count"}}}}]]
+      (tu/with-model-cleanup [Pulse]
+        (tt/with-temp Collection [collection]
           (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
-          (card-api-test/with-cards-in-readable-collection [card]
-            (array-map
-             :response
-             ((user->client :rasta) :post 200 "pulse/test" {:name          "Daily Sad Toucans"
-                                                            :collection_id (u/get-id collection)
-                                                            :cards         [{:id          (u/get-id card)
-                                                                             :include_csv false
-                                                                             :include_xls false}]
-                                                            :channels      [{:enabled       true
-                                                                             :channel_type  "email"
-                                                                             :schedule_type "daily"
-                                                                             :schedule_hour 12
-                                                                             :schedule_day  nil
-                                                                             :recipients    [(fetch-user :rasta)]}]
-                                                            :skip_if_empty false})
-
-             :emails
-             (et/regex-email-bodies #"Daily Sad Toucans"))))))))
+          ;; First create the pulse
+          (let [{pulse-id :id} ((user->client :rasta) :post 200 "pulse"
+                                {:name          "A Pulse"
+                                 :collection_id (u/get-id collection)
+                                 :skip_if_empty false
+                                 :cards         [{:id          (u/get-id card-1)
+                                                  :include_csv false
+                                                  :include_xls false}
+                                                 {:id          (u/get-id card-2)
+                                                  :include_csv false
+                                                  :include_xls false}]
+
+                                 :channels [(assoc daily-email-channel :recipients [(fetch-user :rasta)
+                                                                                    (fetch-user :crowberto)])]})
+                ;; Retrieve the pulse via GET
+                result        ((user->client :rasta) :get 200 (str "pulse/" pulse-id))
+                ;; Change our fetched copy of the pulse to only have Rasta for the recipients
+                email-channel (assoc (-> result :channels first) :recipients [(fetch-user :rasta)])]
+            ;; Don't update the pulse, but test the pulse with the updated recipients
+            {:response ((user->client :rasta) :post 200 "pulse/test" (assoc result :channels [email-channel]))
+             :emails   (et/regex-email-bodies #"A Pulse")}))))))
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                         GET /api/pulse/form_input                                              |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+;; Check that Slack channels come back when configured
+(expect
+  [{:name "channel", :type "select", :displayName "Post to", :options ["#foo" "@bar"], :required true}]
+  (tu/with-temporary-setting-values [slack-token "something"]
+    (with-redefs [metabase.integrations.slack/channels-list (constantly [{:name "foo"}])
+                  metabase.integrations.slack/users-list (constantly [{:name "bar"}])]
+      (-> ((user->client :rasta) :get 200 "pulse/form_input")
+          (get-in [:channels :slack :fields])))))
+
+;; When slack is not configured, `form_input` returns just the #genreal slack channel
+(expect
+  [{:name "channel", :type "select", :displayName "Post to", :options ["#general"], :required true}]
+  (tu/with-temporary-setting-values [slack-token nil]
+    (-> ((user->client :rasta) :get 200 "pulse/form_input")
+        (get-in [:channels :slack :fields]))))
diff --git a/test/metabase/api/search_test.clj b/test/metabase/api/search_test.clj
index a3e4d4c167f82048aa90f50d97565117994af87f..722570ba45ede64674e90b690a2919970fb20b67 100644
--- a/test/metabase/api/search_test.clj
+++ b/test/metabase/api/search_test.clj
@@ -1,6 +1,7 @@
 (ns metabase.api.search-test
-  (:require [clojure.set :as set]
-            [clojure.string :as str]
+  (:require [clojure
+             [set :as set]
+             [string :as str]]
             [expectations :refer :all]
             [metabase.models
              [card :refer [Card]]
@@ -98,17 +99,19 @@
 ;; NOTE: Metrics and segments don't have collections, so they'll be returned
 (expect
   default-metric-segment-results
-  (with-search-items-in-root-collection "test"
-    (search-request :rasta :q "test")))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (with-search-items-in-root-collection "test"
+      (search-request :rasta :q "test"))))
 
 ;; Users that have root collection permissions should get root collection search results
 (expect
   (set (remove (comp #{"collection"} :model) default-search-results))
-  (with-search-items-in-root-collection "test"
-    (tt/with-temp* [PermissionsGroup           [group]
-                    PermissionsGroupMembership [_ {:user_id (user->id :rasta), :group_id (u/get-id group)}]]
-      (perms/grant-permissions! group (perms/collection-read-path {:metabase.models.collection/is-root? true}))
-      (search-request :rasta :q "test"))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (with-search-items-in-root-collection "test"
+      (tt/with-temp* [PermissionsGroup           [group]
+                      PermissionsGroupMembership [_ {:user_id (user->id :rasta), :group_id (u/get-id group)}]]
+        (perms/grant-permissions! group (perms/collection-read-path {:metabase.models.collection/is-root? true}))
+        (search-request :rasta :q "test")))))
 
 ;; Users without root collection permissions should still see other collections they have access to
 (expect
@@ -116,12 +119,13 @@
         (map #(merge default-search-row %)
              [{:name "metric test2 metric", :description "Lookin' for a blueberry", :model "metric"}
               {:name "segment test2 segment", :description "Lookin' for a blueberry", :model "segment"}]))
-  (with-search-items-in-collection {:keys [collection]} "test"
-    (with-search-items-in-root-collection "test2"
-      (tt/with-temp* [PermissionsGroup           [group]
-                      PermissionsGroupMembership [_ {:user_id (user->id :rasta), :group_id (u/get-id group)}]]
-        (perms/grant-collection-read-permissions! group (u/get-id collection))
-        (search-request :rasta :q "test")))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (with-search-items-in-collection {:keys [collection]} "test"
+      (with-search-items-in-root-collection "test2"
+        (tt/with-temp* [PermissionsGroup           [group]
+                        PermissionsGroupMembership [_ {:user_id (user->id :rasta), :group_id (u/get-id group)}]]
+          (perms/grant-collection-read-permissions! group (u/get-id collection))
+          (search-request :rasta :q "test"))))))
 
 ;; Users with root collection permissions should be able to search root collection data long with collections they
 ;; have access to
@@ -130,13 +134,14 @@
         (for [row default-search-results
               :when (not= "collection" (:model row))]
           (update row :name #(str/replace % "test" "test2"))))
-  (with-search-items-in-collection {:keys [collection]} "test"
-    (with-search-items-in-root-collection "test2"
-      (tt/with-temp* [PermissionsGroup           [group]
-                      PermissionsGroupMembership [_ {:user_id (user->id :rasta), :group_id (u/get-id group)}]]
-        (perms/grant-permissions! group (perms/collection-read-path {:metabase.models.collection/is-root? true}))
-        (perms/grant-collection-read-permissions! group collection)
-        (search-request :rasta :q "test")))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (with-search-items-in-collection {:keys [collection]} "test"
+      (with-search-items-in-root-collection "test2"
+        (tt/with-temp* [PermissionsGroup           [group]
+                        PermissionsGroupMembership [_ {:user_id (user->id :rasta), :group_id (u/get-id group)}]]
+          (perms/grant-permissions! group (perms/collection-read-path {:metabase.models.collection/is-root? true}))
+          (perms/grant-collection-read-permissions! group collection)
+          (search-request :rasta :q "test"))))))
 
 ;; Users with access to multiple collections should see results from all collections they have access to
 (expect
@@ -157,12 +162,13 @@
         (map #(merge default-search-row %)
              [{:name "metric test2 metric", :description "Lookin' for a blueberry", :model "metric"}
               {:name "segment test2 segment", :description "Lookin' for a blueberry", :model "segment"}]))
-  (with-search-items-in-collection {coll-1 :collection} "test"
-    (with-search-items-in-collection {coll-2 :collection} "test2"
-      (tt/with-temp* [PermissionsGroup           [group]
-                      PermissionsGroupMembership [_ {:user_id (user->id :rasta), :group_id (u/get-id group)}]]
-        (perms/grant-collection-read-permissions! group (u/get-id coll-1))
-        (search-request :rasta :q "test")))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (with-search-items-in-collection {coll-1 :collection} "test"
+      (with-search-items-in-collection {coll-2 :collection} "test2"
+        (tt/with-temp* [PermissionsGroup           [group]
+                        PermissionsGroupMembership [_ {:user_id (user->id :rasta), :group_id (u/get-id group)}]]
+          (perms/grant-collection-read-permissions! group (u/get-id coll-1))
+          (search-request :rasta :q "test"))))))
 
 ;; Favorites are per user, so other user's favorites don't cause search results to be favorited
 (expect
diff --git a/test/metabase/api/segment_test.clj b/test/metabase/api/segment_test.clj
index 272565673f0542715a35fd9466dffa279a5edf60..fc48f7ec63fa87e4c0bc202cf84addf717140fb8 100644
--- a/test/metabase/api/segment_test.clj
+++ b/test/metabase/api/segment_test.clj
@@ -86,18 +86,17 @@
    :created_at              true
    :updated_at              true
    :archived                false
-   :definition              {:database 21
-                             :query    {:filter ["abc"]}}}
+   :definition              {:filter ["=" ["field-id" 10] 20]}}
   (tt/with-temp* [Database [{database-id :id}]
                   Table    [{:keys [id]} {:db_id database-id}]]
-    (segment-response ((user->client :crowberto) :post 200 "segment" {:name                    "A Segment"
-                                                                      :description             "I did it!"
-                                                                      :show_in_getting_started false
-                                                                      :caveats                 nil
-                                                                      :points_of_interest      nil
-                                                                      :table_id                id
-                                                                      :definition              {:database 21
-                                                                                                :query    {:filter ["abc"]}}}))))
+    (segment-response ((user->client :crowberto) :post 200 "segment"
+                       {:name                    "A Segment"
+                        :description             "I did it!"
+                        :show_in_getting_started false
+                        :caveats                 nil
+                        :points_of_interest      nil
+                        :table_id                id
+                        :definition              {:filter [:= [:field-id 10] 20]}}))))
 
 
 ;; ## PUT /api/segment
@@ -139,8 +138,7 @@
    :created_at              true
    :updated_at              true
    :archived                false
-   :definition              {:database 2
-                             :query    {:filter ["not" "the toucans you're looking for"]}}}
+   :definition              {:filter ["!=" ["field-id" 2] "cans"]}}
   (tt/with-temp* [Database [{database-id :id}]
                   Table    [{table-id :id} {:db_id database-id}]
                   Segment  [{:keys [id]}   {:table_id table-id}]]
@@ -152,8 +150,7 @@
                                                                                     :points_of_interest      nil
                                                                                     :table_id                456
                                                                                     :revision_message        "I got me some revisions"
-                                                                                    :definition              {:database 2
-                                                                                                              :query    {:filter ["not" "the toucans you're looking for"]}}}))))
+                                                                                    :definition              {:filter [:!= [:field-id 2] "cans"]}}))))
 
 
 ;; ## DELETE /api/segment/:id
@@ -182,7 +179,7 @@
     :created_at              true
     :updated_at              true
     :archived                true
-    :definition              {}}]
+    :definition              nil}]
   (tt/with-temp* [Database [{database-id :id}]
                   Table    [{table-id :id} {:db_id database-id}]
                   Segment  [{:keys [id]} {:table_id table-id}]]
@@ -208,14 +205,12 @@
    :created_at              true
    :updated_at              true
    :archived                false
-   :definition              {:database 123
-                             :query    {:filter ["In the Land of Metabase where the Datas lie"]}}}
+   :definition              {:filter ["=" ["field-id" 2] "cans"]}}
   (tt/with-temp* [Database [{database-id :id}]
                   Table    [{table-id :id} {:db_id database-id}]
                   Segment  [{:keys [id]}   {:creator_id (user->id :crowberto)
                                             :table_id   table-id
-                                            :definition {:database 123
-                                                         :query    {:filter ["In the Land of Metabase where the Datas lie"]}}}]]
+                                            :definition {:filter [:= [:field-id 2] "cans"]}}]]
     (segment-response ((user->client :crowberto) :get 200 (format "segment/%d" id)))))
 
 
@@ -240,24 +235,24 @@
     :user         (-> (user-details (fetch-user :rasta))
                       (dissoc :email :date_joined :last_login :is_superuser :is_qbnewb))
     :diff         {:name       {:after "b"}
-                   :definition {:after {:filter ["AND" [">" 1 25]]}}}
+                   :definition {:after {:filter [">" ["field-id" 1] 25]}}}
     :description  nil}]
   (tt/with-temp* [Database [{database-id :id}]
                   Table    [{table-id :id} {:db_id database-id}]
                   Segment  [{:keys [id]} {:creator_id (user->id :crowberto)
                                           :table_id   table-id
                                           :definition {:database 123
-                                                       :query    {:filter ["In the Land of Metabase where the Datas lie"]}}}]
+                                                       :query    {:filter [:= [:field-id 2] "cans"]}}}]
                   Revision [_ {:model       "Segment"
                                :model_id    id
                                :object      {:name "b"
-                                             :definition {:filter ["AND" [">" 1 25]]}}
+                                             :definition {:filter [:and [:> 1 25]]}}
                                :is_creation true}]
                   Revision [_ {:model    "Segment"
                                :model_id id
                                :user_id  (user->id :crowberto)
                                :object   {:name "c"
-                                          :definition {:filter ["AND" [">" 1 25]]}}
+                                          :definition {:filter [:and [:> 1 25]]}}
                                :message  "updated"}]]
     (doall (for [revision ((user->client :crowberto) :get 200 (format "segment/%d/revisions" id))]
              (dissoc revision :timestamp :id)))))
@@ -311,8 +306,7 @@
                        (dissoc :email :date_joined :last_login :is_superuser :is_qbnewb))
      :diff         {:name        {:after "One Segment to rule them all, one segment to define them"}
                     :description {:after "One segment to bring them all, and in the DataModel bind them"}
-                    :definition  {:after {:database 123
-                                          :query    {:filter ["In the Land of Metabase where the Datas lie"]}}}}
+                    :definition  {:after {:filter ["=" ["field-id" 2] "cans"]}}}
      :description  nil}]]
   (tt/with-temp* [Database [{database-id :id}]
                   Table    [{table-id :id}    {:db_id database-id}]
@@ -323,15 +317,7 @@
                                                :show_in_getting_started false
                                                :caveats                 nil
                                                :points_of_interest      nil
-                                               :definition              {:creator_id              (user->id :crowberto)
-                                                                         :table_id                table-id
-                                                                         :name                    "Reverted Segment Name"
-                                                                         :description             nil
-                                                                         :show_in_getting_started false
-                                                                         :caveats                 nil
-                                                                         :points_of_interest      nil
-                                                                         :definition              {:database 123
-                                                                                                   :query    {:filter ["In the Land of Metabase where the Datas lie"]}}}}]
+                                               :definition              {:filter [:= [:field-id 2] "cans"]}}]
                   Revision [{revision-id :id} {:model       "Segment"
                                                :model_id    id
                                                :object      {:creator_id              (user->id :crowberto)
@@ -341,8 +327,7 @@
                                                              :show_in_getting_started false
                                                              :caveats                 nil
                                                              :points_of_interest      nil
-                                                             :definition              {:database 123
-                                                                                       :query    {:filter ["In the Land of Metabase where the Datas lie"]}}}
+                                                             :definition              {:filter [:= [:field-id 2] "cans"]}}
                                                :is_creation true}]
                   Revision [_                 {:model    "Segment"
                                                :model_id id
@@ -354,8 +339,7 @@
                                                           :show_in_getting_started false
                                                           :caveats                 nil
                                                           :points_of_interest      nil
-                                                          :definition              {:database 123
-                                                                                    :query    {:filter ["In the Land of Metabase where the Datas lie"]}}}
+                                                          :definition              {:filter [:= [:field-id 2] "cans"]}}
                                                :message  "updated"}]]
     [(dissoc ((user->client :crowberto) :post 200 (format "segment/%d/revert" id) {:revision_id revision-id}) :id :timestamp)
      (doall (for [revision ((user->client :crowberto) :get 200 (format "segment/%d/revisions" id))]
diff --git a/test/metabase/api/table_test.clj b/test/metabase/api/table_test.clj
index 63682bd2c8924b92c77206b32ec5d0cffc24a81f..9b24e35a4c63f6269f7544b976730f202361200a 100644
--- a/test/metabase/api/table_test.clj
+++ b/test/metabase/api/table_test.clj
@@ -18,6 +18,7 @@
              [permissions :as perms]
              [permissions-group :as perms-group]
              [table :as table :refer [Table]]]
+            [metabase.query-processor.util :as qputil]
             [metabase.test
              [data :as data]
              [util :as tu :refer [match-$]]]
@@ -25,6 +26,7 @@
              [dataset-definitions :as defs]
              [datasets :as datasets]
              [users :refer [user->client]]]
+            [metabase.test.mock.util :as mutil]
             [metabase.timeseries-query-processor-test.util :as tqpt]
             [toucan
              [db :as db]
@@ -98,7 +100,6 @@
       :id                  $
       :created_at          $
       :fk_target_field_id  $
-      :raw_column_id       $
       :last_analyzed       $
       :fingerprint         $
       :fingerprint_version $})))
@@ -149,7 +150,6 @@
             :pk_field     (#'table/pk-field-id $$)
             :id           (data/id :venues)
             :db_id        (data/id)
-            :raw_table_id $
             :created_at   $
             :fields_hash  $}))
   ((user->client :rasta) :get 200 (format "table/%d" (data/id :venues))))
@@ -159,12 +159,13 @@
                       Table    [{table-id :id}    {:db_id database-id}]]
   "You don't have permissions to do that."
   (do
-    (perms/delete-related-permissions! (perms-group/all-users) (perms/object-path database-id))
+    (perms/revoke-permissions! (perms-group/all-users) database-id)
     ((user->client :rasta) :get 403 (str "table/" table-id))))
 
 (defn- default-dimension-options []
   (->> #'table-api/dimension-options-for-response
        var-get
+       (m/map-vals #(update % :name str))
        walk/keywordize-keys))
 
 (defn- query-metadata-defaults []
@@ -199,7 +200,6 @@
             :rows         nil
             :updated_at   $
             :id           (data/id :categories)
-            :raw_table_id $
             :created_at   $
             :fields_hash  $}))
   ((user->client :rasta) :get 200 (format "table/%d/query_metadata" (data/id :categories))))
@@ -274,7 +274,6 @@
             :rows         nil
             :updated_at   $
             :id           (data/id :users)
-            :raw_table_id $
             :created_at   $
             :fields_hash  $}))
   ((user->client :rasta) :get 200 (format "table/%d/query_metadata?include_sensitive_fields=true" (data/id :users))))
@@ -316,7 +315,6 @@
             :rows         nil
             :updated_at   $
             :id           (data/id :users)
-            :raw_table_id $
             :created_at   $
             :fields_hash  $}))
   ((user->client :rasta) :get 200 (format "table/%d/query_metadata" (data/id :users))))
@@ -356,7 +354,6 @@
             :display_name    "Userz"
             :pk_field        (#'table/pk-field-id $$)
             :id              $
-            :raw_table_id    $
             :created_at      $
             :fields_hash     $}))
   (do ((user->client :crowberto) :put 200 (format "table/%d" (:id table)) {:display_name    "Userz"
@@ -412,7 +409,6 @@
                                                           :rows         nil
                                                           :updated_at   $
                                                           :id           $
-                                                          :raw_table_id $
                                                           :created_at   $
                                                           :fields_hash  $}))))
       :destination    (-> (fk-field-details users-id-field)
@@ -432,11 +428,29 @@
                                                           :rows         nil
                                                           :updated_at   $
                                                           :id           $
-                                                          :raw_table_id $
                                                           :created_at   $
                                                           :fields_hash  $}))))}])
   ((user->client :rasta) :get 200 (format "table/%d/fks" (data/id :users))))
 
+(defn- with-field-literal-id [{field-name :name, base-type :base_type :as field}]
+  (assoc field :id ["field-literal" field-name base-type]))
+
+(defn- default-card-field-for-venues [table-id]
+  {:table_id                 table-id
+   :special_type             nil
+   :default_dimension_option nil
+   :dimension_options        []})
+
+(defn- with-numeric-dimension-options [field]
+  (assoc field
+    :default_dimension_option (var-get #'table-api/numeric-default-index)
+    :dimension_options (var-get #'table-api/numeric-dimension-indexes)))
+
+(defn- with-coordinate-dimension-options [field]
+  (assoc field
+    :default_dimension_option (var-get #'table-api/coordinate-default-index)
+    :dimension_options (var-get #'table-api/coordinate-dimension-indexes)))
+
 ;; Make sure metadata for 'virtual' tables comes back as expected from GET /api/table/:id/query_metadata
 (tt/expect-with-temp [Card [card {:name          "Go Dubs!"
                                   :database_id   (data/id)
@@ -450,23 +464,35 @@
      :id                card-virtual-table-id
      :description       nil
      :dimension_options (default-dimension-options)
-     :fields            (for [[field-name display-name base-type] [["NAME"     "Name"     "type/Text"]
-                                                                   ["ID"       "ID"       "type/Integer"]
-                                                                   ["PRICE"    "Price"    "type/Integer"]
-                                                                   ["LATITUDE" "Latitude" "type/Float"]]]
-                          {:name                     field-name
-                           :display_name             display-name
-                           :base_type                base-type
-                           :table_id                 card-virtual-table-id
-                           :id                       ["field-literal" field-name base-type]
-                           :special_type             nil
-                           :default_dimension_option nil
-                           :dimension_options        []})})
+     :fields            (map (comp #(merge (default-card-field-for-venues card-virtual-table-id) %)
+                                   with-field-literal-id)
+                             [{:name         "NAME"
+                               :display_name "Name"
+                               :base_type    "type/Text"
+                               :special_type "type/Name"
+                               :fingerprint  (:name mutil/venue-fingerprints)}
+                              {:name         "ID"
+                               :display_name "ID"
+                               :base_type    "type/Integer"
+                               :special_type nil
+                               :fingerprint  (:id mutil/venue-fingerprints)}
+                              (with-numeric-dimension-options
+                                {:name         "PRICE"
+                                 :display_name "Price"
+                                 :base_type    "type/Integer"
+                                 :special_type nil
+                                 :fingerprint  (:price mutil/venue-fingerprints)})
+                              (with-coordinate-dimension-options
+                                {:name         "LATITUDE"
+                                 :display_name "Latitude"
+                                 :base_type    "type/Float"
+                                 :special_type "type/Latitude"
+                                 :fingerprint  (:latitude mutil/venue-fingerprints)})])})
   (do
     ;; run the Card which will populate its result_metadata column
     ((user->client :crowberto) :post 200 (format "card/%d/query" (u/get-id card)))
     ;; Now fetch the metadata for this "table"
-    ((user->client :crowberto) :get 200 (format "table/card__%d/query_metadata" (u/get-id card)))))
+    (tu/round-all-decimals 2 ((user->client :crowberto) :get 200 (format "table/card__%d/query_metadata" (u/get-id card))))))
 
 ;; Test date dimensions being included with a nested query
 (tt/expect-with-temp [Card [card {:name          "Users"
@@ -486,9 +512,12 @@
                           :base_type                "type/Text"
                           :table_id                 card-virtual-table-id
                           :id                       ["field-literal" "NAME" "type/Text"]
-                          :special_type             nil
+                          :special_type             "type/Name"
                           :default_dimension_option nil
-                          :dimension_options        []}
+                          :dimension_options        []
+                          :fingerprint              {:global {:distinct-count 15},
+                                                     :type   {:type/Text {:percent-json  0.0, :percent-url    0.0,
+                                                                          :percent-email 0.0, :average-length 13.27}}}}
                          {:name                     "LAST_LOGIN"
                           :display_name             "Last Login"
                           :base_type                "type/DateTime"
@@ -496,12 +525,15 @@
                           :id                       ["field-literal" "LAST_LOGIN" "type/DateTime"]
                           :special_type             nil
                           :default_dimension_option (var-get #'table-api/date-default-index)
-                          :dimension_options        (var-get #'table-api/datetime-dimension-indexes)}]})
+                          :dimension_options        (var-get #'table-api/datetime-dimension-indexes)
+                          :fingerprint              {:global {:distinct-count 15},
+                                                     :type   {:type/DateTime {:earliest "2014-01-01T08:30:00.000Z",
+                                                                              :latest   "2014-12-05T15:15:00.000Z"}}}}]})
   (do
     ;; run the Card which will populate its result_metadata column
     ((user->client :crowberto) :post 200 (format "card/%d/query" (u/get-id card)))
     ;; Now fetch the metadata for this "table"
-    ((user->client :crowberto) :get 200 (format "table/card__%d/query_metadata" (u/get-id card)))))
+    (tu/round-all-decimals 2 ((user->client :crowberto) :get 200 (format "table/card__%d/query_metadata" (u/get-id card))))))
 
 
 ;; make sure GET /api/table/:id/fks just returns nothing for 'virtual' tables
@@ -606,11 +638,10 @@
           :dimension_options))))
 
 (defn- dimension-options-for-field [response field-name]
-  (let [formatted-field-name (data/format-name field-name)]
-    (->> response
-         :fields
-         (m/find-first #(= formatted-field-name (:name %)))
-         :dimension_options)))
+  (->> response
+       :fields
+       (m/find-first #(.equalsIgnoreCase field-name (:name %)))
+       :dimension_options))
 
 (defn- extract-dimension-options
   "For the given `FIELD-NAME` find it's dimension_options following
@@ -667,3 +698,29 @@
 (expect
   #{:metrics :segments :linked-from :linking-to :tables}
   (-> ((user->client :crowberto) :get 200 (format "table/%s/related" (data/id :venues))) keys set))
+
+;; Nested queries with a fingerprint should have dimension options for binning
+(datasets/expect-with-engines (qpt/non-timeseries-engines-with-feature :binning :nested-queries)
+  (repeat 2 (var-get #'table-api/coordinate-dimension-indexes))
+  (tt/with-temp Card [card {:database_id   (data/id)
+                            :dataset_query {:database (data/id)
+                                            :type    :query
+                                            :query    {:source-query {:source-table (data/id :venues)}}}}]
+    ;; run the Card which will populate its result_metadata column
+    ((user->client :crowberto) :post 200 (format "card/%d/query" (u/get-id card)))
+    (let [response ((user->client :crowberto) :get 200 (format "table/card__%d/query_metadata" (u/get-id card)))]
+      (map #(dimension-options-for-field response %)
+           ["latitude" "longitude"]))))
+
+;; Nested queries missing a fingerprint should not show binning-options
+(datasets/expect-with-engines (qpt/non-timeseries-engines-with-feature :binning :nested-queries)
+  [nil nil]
+  (tt/with-temp Card [card {:database_id   (data/id)
+                            :dataset_query {:database (data/id)
+                                            :type    :query
+                                            :query    {:source-query {:source-table (data/id :venues)}}}}]
+    ;; By default result_metadata will be nil (and no fingerprint). Just asking for query_metadata after the card was
+    ;; created but before it was ran should not allow binning
+    (let [response ((user->client :crowberto) :get 200 (format "table/card__%d/query_metadata" (u/get-id card)))]
+      (map #(dimension-options-for-field response %)
+           ["latitude" "longitude"]))))
diff --git a/test/metabase/api/tiles_test.clj b/test/metabase/api/tiles_test.clj
index 059d24d22f7712c375465a60d2b1903c25ec3c98..f8f17775f11720e2da670e8738fc5be69c4a4893 100644
--- a/test/metabase/api/tiles_test.clj
+++ b/test/metabase/api/tiles_test.clj
@@ -2,15 +2,15 @@
   "Tests for `/api/tiles` endpoints."
   (:require [cheshire.core :as json]
             [expectations :refer :all]
-            [metabase.query-processor.middleware.expand :as ql]
-            [metabase.test.data :refer :all]
+            [metabase.test.data :as data]
             [metabase.test.data.users :refer :all]))
 
 ;;; GET /api/tiles/:zoom/:x/:y/:lat-field-id/:lon-field-id/:lat-col-idx/:lon-col-idx/
 (expect
   String
-  ((user->client :rasta) :get 200 (format "tiles/1/1/1/%d/%d/1/1/" (id :venues :latitude) (id :venues :longitude))
-   :query (json/generate-string {:database (id)
+  ((user->client :rasta) :get 200 (format "tiles/1/1/1/%d/%d/1/1/"
+                                          (data/id :venues :latitude)
+                                          (data/id :venues :longitude))
+   :query (json/generate-string {:database (data/id)
                                  :type     :query
-                                 :query    (ql/query
-                                             (ql/source-table (id :venues)))})))
+                                 :query    {:source-table (data/id :venues)}})))
diff --git a/test/metabase/api/user_test.clj b/test/metabase/api/user_test.clj
index 91db0f9ad2dfdb01b10cc6167ea18a261ab62ddb..945a224742415e1126a55334136f8749c08fa0c1 100644
--- a/test/metabase/api/user_test.clj
+++ b/test/metabase/api/user_test.clj
@@ -40,7 +40,7 @@
   [{:id (test-users/user->id :crowberto), :email "crowberto@metabase.com", :first_name "Crowberto", :last_name "Corv",   :common_name "Crowberto Corv"}
    {:id (test-users/user->id :lucky),     :email "lucky@metabase.com",     :first_name "Lucky",     :last_name "Pigeon", :common_name "Lucky Pigeon"}
    {:id (test-users/user->id :rasta),     :email "rasta@metabase.com",     :first_name "Rasta",     :last_name "Toucan", :common_name "Rasta Toucan"}]
-  (do
+  (tu/with-non-admin-groups-no-root-collection-perms
     ;; Delete all the other random Users we've created so far
     (test-users/delete-temp-users!)
     ;; Make sure personal Collections have been created
@@ -322,6 +322,28 @@
   ((test-users/user->client :crowberto) :put 404 (str "user/" (test-users/user->id :trashbird))
    {:email "toucan@metabase.com"}))
 
+;; Google auth users shouldn't be able to change their own password as we get that from Google
+(expect
+  "You don't have permissions to do that."
+  (tt/with-temp User [user {:email       "anemail@metabase.com"
+                            :password    "def123"
+                            :google_auth true}]
+    (let [creds {:username "anemail@metabase.com"
+                 :password "def123"}]
+      (http/client creds :put 403 (format "user/%d" (u/get-id user))
+                   {:email "adifferentemail@metabase.com"}))))
+
+;; Similar to Google auth accounts, we should not allow LDAP users to change their own email address as we get that
+;; from the LDAP server
+(expect
+  "You don't have permissions to do that."
+  (tt/with-temp User [user {:email       "anemail@metabase.com"
+                            :password    "def123"
+                            :ldap_auth true}]
+    (let [creds {:username "anemail@metabase.com"
+                 :password "def123"}]
+      (http/client creds :put 403 (format "user/%d" (u/get-id user))
+                   {:email "adifferentemail@metabase.com"}))))
 
 ;; ## PUT /api/user/:id/password
 ;; Test that a User can change their password (superuser and non-superuser)
diff --git a/test/metabase/automagic_dashboards/comparison_test.clj b/test/metabase/automagic_dashboards/comparison_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..5d374c6e19d12df5bf4448cff5851963f9a18f8d
--- /dev/null
+++ b/test/metabase/automagic_dashboards/comparison_test.clj
@@ -0,0 +1,59 @@
+(ns metabase.automagic-dashboards.comparison-test
+  (:require [expectations :refer :all]
+            [metabase.automagic-dashboards
+             [comparison :refer :all :as c]
+             [core :refer [automagic-analysis]]]
+            [metabase.models
+             [card :refer [Card]]
+             [query :as query]
+             [segment :refer [Segment]]
+             [table :refer [Table] :as table]]
+            [metabase.test.data :as data]
+            [metabase.test.automagic-dashboards :refer :all]
+            [toucan.util.test :as tt]))
+
+(def ^:private segment {:table_id (data/id :venues)
+                        :definition {:filter [:> [:field-id (data/id :venues :price)] 10]}})
+
+(defn- test-comparison
+  [left right]
+  (-> left
+      (automagic-analysis {})
+      (comparison-dashboard left right {})
+      :ordered_cards
+      count
+      pos?))
+
+(expect
+  (tt/with-temp* [Segment [{segment-id :id} segment]]
+    (with-rasta
+      (with-dashboard-cleanup
+        (and (test-comparison (Table (data/id :venues)) (Segment segment-id))
+             (test-comparison (Segment segment-id) (Table (data/id :venues))))))))
+
+(expect
+  (tt/with-temp* [Segment [{segment1-id :id} segment]
+                  Segment [{segment2-id :id} {:table_id (data/id :venues)
+                                              :definition {:filter [:< [:field-id (data/id :venues :price)] 4]}}]]
+    (with-rasta
+      (with-dashboard-cleanup
+        (test-comparison (Segment segment1-id) (Segment segment2-id))))))
+
+(expect
+  (with-rasta
+    (with-dashboard-cleanup
+      (let [q (query/adhoc-query {:query {:filter (-> segment :definition :filter)
+                                          :source-table (data/id :venues)}
+                                  :type :query
+                                  :database (data/id)})]
+        (test-comparison (Table (data/id :venues)) q)))))
+
+(expect
+  (tt/with-temp* [Card [{card-id :id} {:table_id      (data/id :venues)
+                                       :dataset_query {:query {:filter (-> segment :definition :filter)
+                                                               :source-table (data/id :venues)}
+                                                       :type :query
+                                                       :database (data/id)}}]]
+    (with-rasta
+      (with-dashboard-cleanup
+        (test-comparison (Table (data/id :venues)) (Card card-id))))))
diff --git a/test/metabase/automagic_dashboards/core_test.clj b/test/metabase/automagic_dashboards/core_test.clj
index fd17104868250334e912012325ff03e054eece9c..7aef64c5e674654e688ebe7b89d8c5884aedc9e7 100644
--- a/test/metabase/automagic_dashboards/core_test.clj
+++ b/test/metabase/automagic_dashboards/core_test.clj
@@ -1,39 +1,33 @@
 (ns metabase.automagic-dashboards.core-test
-  (:require [expectations :refer :all]
-            [metabase.api.common :as api]
+  (:require [clj-time
+             [core :as t]
+             [format :as t.format]]
+            [expectations :refer :all]
+            [metabase.api
+             [card :as card.api]
+             [common :as api]]
             [metabase.automagic-dashboards
-             [core :refer :all :as magic]
+             [core :as magic :refer :all]
              [rules :as rules]]
             [metabase.models
              [card :refer [Card]]
+             [collection :refer [Collection]]
              [database :refer [Database]]
              [field :as field :refer [Field]]
              [metric :refer [Metric]]
+             [permissions :as perms]
+             [permissions-group :as perms-group]
              [query :as query]
-             [table :refer [Table] :as table]
-             [user :as user]]
-            [metabase.query-processor :as qp]
-            [metabase.test.data :as data]
-            [metabase.test.data.users :as test-users]
-            [metabase.test.util :as tu]
+             [table :as table :refer [Table]]]
+            [metabase.test
+             [automagic-dashboards :refer :all]
+             [data :as data]
+             [util :as tu]]
+            [metabase.util.date :as date]
+            [puppetlabs.i18n.core :as i18n :refer [tru]]
             [toucan.db :as db]
-            [toucan.util.test :as tt]))
-
-(defmacro with-rasta
-  "Execute body with rasta as the current user."
-  [& body]
-  `(binding [api/*current-user-id*              (test-users/user->id :rasta)
-             api/*current-user-permissions-set* (-> :rasta
-                                                    test-users/user->id
-                                                    user/permissions-set
-                                                    atom)]
-     ~@body))
-
-(defmacro ^:private with-dashboard-cleanup
-  [& body]
-  `(tu/with-model-cleanup ['~'Card '~'Dashboard '~'Collection '~'DashboardCard]
-     ~@body))
-
+            [toucan.util.test :as tt]
+            [metabase.util :as u]))
 
 ;;; ------------------- `->reference` -------------------
 
@@ -76,39 +70,28 @@
 
 ;;; ------------------- `automagic-anaysis` -------------------
 
-(defn- collect-urls
-  [dashboard]
-  (->> dashboard
-       (tree-seq (some-fn sequential? map?) identity)
-       (keep (fn [form]
-               (when (map? form)
-                 (:url form))))))
-
-(defn- valid-urls?
-  [dashboard]
-  (->> dashboard
-       collect-urls
-       (every? (fn [url]
-                 ((test-users/user->client :rasta) :get 200 (format "automagic-dashboards/%s"
-                                                                    (subs url 16)))))))
-
-(def ^:private valid-card?
-  (comp qp/expand :dataset_query))
-
-(defn- valid-dashboard?
-  [dashboard]
-  (assert (:name dashboard))
-  (assert (-> dashboard :ordered_cards count pos?))
-  (assert (valid-urls? dashboard))
-  (assert (every? valid-card? (keep :card (:ordered_cards dashboard))))
-  true)
+(defn- test-automagic-analysis
+  ([entity] (test-automagic-analysis entity nil))
+  ([entity cell-query]
+   ;; We want to both generate as many cards as we can to catch all aberrations, but also make sure
+   ;; that size limiting works.
+   (and (valid-dashboard? (automagic-analysis entity {:cell-query cell-query :show :all}))
+        (valid-dashboard? (automagic-analysis entity {:cell-query cell-query :show 1})))))
 
 (expect
   (with-rasta
     (with-dashboard-cleanup
       (->> (db/select Table :db_id (data/id))
-           (keep #(automagic-analysis % {}))
-           (every? valid-dashboard?)))))
+           (every? test-automagic-analysis)))))
+
+(expect
+  (with-rasta
+    (with-dashboard-cleanup
+      (->> (automagic-analysis (Table (data/id :venues)) {:show 1})
+           :ordered_cards
+           (filter :card)
+           count
+           (= 1)))))
 
 (expect
   (with-rasta
@@ -116,150 +99,224 @@
       (->> (db/select Field
              :table_id [:in (db/select-field :id Table :db_id (data/id))]
              :visibility_type "normal")
-           (keep #(automagic-analysis % {}))
-           (every? valid-dashboard?)))))
-
-(expect
-  (tt/with-temp* [Metric [{metric-id :id} {:table_id (data/id :venues)
-                                           :definition {:query {:aggregation ["count"]}}}]]
-    (with-rasta
-      (with-dashboard-cleanup
-        (->> (Metric) (keep #(automagic-analysis % {})) (every? valid-dashboard?))))))
-
-(expect
-  (tt/with-temp* [Card [{card-id :id} {:table_id      (data/id :venues)
-                                       :dataset_query {:query {:filter [:> [:field-id (data/id :venues :price)] 10]
-                                                               :source_table (data/id :venues)}
-                                                       :type :query
-                                                       :database (data/id)}}]]
-    (with-rasta
-      (with-dashboard-cleanup
-        (-> card-id Card (automagic-analysis {}) valid-dashboard?)))))
-
-(expect
-  (tt/with-temp* [Card [{card-id :id} {:table_id      (data/id :venues)
-                                       :dataset_query {:query {:aggregation [[:count]]
-                                                               :breakout [[:field-id (data/id :venues :category_id)]]
-                                                               :source_table (data/id :venues)}
-                                                       :type :query
-                                                       :database (data/id)}}]]
-    (with-rasta
-      (with-dashboard-cleanup
-        (-> card-id Card (automagic-analysis {}) valid-dashboard?)))))
-
-(expect
-  (tt/with-temp* [Card [{card-id :id} {:table_id      nil
-                                       :dataset_query {:native {:query "select * from users"}
-                                                       :type :native
-                                                       :database (data/id)}}]]
-    (with-rasta
-      (with-dashboard-cleanup
-        (-> card-id Card (automagic-analysis {}) valid-dashboard?)))))
+           (every? test-automagic-analysis)))))
 
 (expect
-  (tt/with-temp* [Card [{source-id :id} {:table_id      (data/id :venues)
-                                         :dataset_query {:query    {:source_table (data/id :venues)}
-                                                         :type     :query
-                                                         :database (data/id)}}]
-                  Card [{card-id :id} {:table_id      (data/id :venues)
-                                       :dataset_query {:query    {:filter       [:> [:field-id (data/id :venues :price)] 10]
-                                                                  :source_table (str "card__" source-id)}
-                                                       :type     :query
-                                                       :database -1337}}]]
+  (tt/with-temp* [Metric [metric {:table_id (data/id :venues)
+                                  :definition {:aggregation [[:count]]}}]]
     (with-rasta
       (with-dashboard-cleanup
-        (-> card-id Card (automagic-analysis {}) valid-dashboard?)))))
-
-(expect
-  (tt/with-temp* [Card [{source-id :id} {:table_id      nil
+        (test-automagic-analysis metric)))))
+
+(expect
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [{collection-id :id}]
+                    Card [{card-id :id} {:table_id      (data/id :venues)
+                                         :collection_id collection-id
+                                         :dataset_query {:query {:filter [:> [:field-id (data/id :venues :price)] 10]
+                                                                 :source-table (data/id :venues)}
+                                                         :type :query
+                                                         :database (data/id)}}]]
+      (with-rasta
+        (with-dashboard-cleanup
+          (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection-id)
+          (-> card-id Card test-automagic-analysis))))))
+
+(expect
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [{collection-id :id}]
+                    Card [{card-id :id} {:table_id      (data/id :venues)
+                                         :collection_id collection-id
+                                         :dataset_query {:query {:aggregation [[:count]]
+                                                                 :breakout [[:field-id (data/id :venues :category_id)]]
+                                                                 :source-table (data/id :venues)}
+                                                         :type :query
+                                                         :database (data/id)}}]]
+      (with-rasta
+        (with-dashboard-cleanup
+          (-> card-id Card test-automagic-analysis))))))
+
+(expect
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [{collection-id :id}]
+                    Card [{card-id :id} {:table_id      nil
+                                         :collection_id collection-id
                                          :dataset_query {:native {:query "select * from users"}
                                                          :type :native
-                                                         :database (data/id)}}]
-                  Card [{card-id :id} {:table_id      (data/id :venues)
-                                       :dataset_query {:query    {:filter       [:> [:field-id (data/id :venues :price)] 10]
-                                                                  :source_table (str "card__" source-id)}
-                                                       :type     :query
-                                                       :database -1337}}]]
-    (with-rasta
-      (with-dashboard-cleanup
-        (-> card-id Card (automagic-analysis {}) valid-dashboard?)))))
-
-(expect
-  (tt/with-temp* [Card [{card-id :id} {:table_id      nil
-                                       :dataset_query {:native {:query "select * from users"}
-                                                       :type :native
-                                                       :database (data/id)}}]]
-    (with-rasta
-      (with-dashboard-cleanup
-        (-> card-id Card (automagic-analysis {}) valid-dashboard?)))))
-
-(expect
-  (tt/with-temp* [Card [{card-id :id} {:table_id      (data/id :venues)
-                                       :dataset_query {:query {:filter [:> [:field-id (data/id :venues :price)] 10]
-                                                               :source_table (data/id :venues)}
-                                                       :type :query
-                                                       :database (data/id)}}]]
-    (with-rasta
-      (with-dashboard-cleanup
-        (-> card-id
-            Card
-            (automagic-analysis {:cell-query [:= [:field-id (data/id :venues :category_id)] 2]})
-            valid-dashboard?)))))
-
-
-(expect
-  (tt/with-temp* [Card [{card-id :id} {:table_id      (data/id :venues)
-                                       :dataset_query {:query {:filter [:> [:field-id (data/id :venues :price)] 10]
-                                                               :source_table (data/id :venues)}
-                                                       :type :query
-                                                       :database (data/id)}}]]
-    (with-rasta
-      (with-dashboard-cleanup
-        (-> card-id
-            Card
-            (automagic-analysis {:cell-query [:!= [:field-id (data/id :venues :category_id)] 2]})
-            valid-dashboard?)))))
+                                                         :database (data/id)}}]]
+      (with-rasta
+        (with-dashboard-cleanup
+          (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection-id)
+          (-> card-id Card test-automagic-analysis))))))
+
+(expect
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (let [source-query {:query    {:source-table (data/id :venues)}
+                        :type     :query
+                        :database (data/id)}]
+      (tt/with-temp* [Collection [{collection-id :id}]
+                      Card [{source-id :id} {:table_id      (data/id :venues)
+                                             :collection_id   collection-id
+                                             :dataset_query   source-query
+                                             :result_metadata (#'card.api/result-metadata-for-query source-query)}]
+                      Card [{card-id :id} {:table_id      (data/id :venues)
+                                           :collection_id collection-id
+                                           :dataset_query {:query    {:filter       [:> [:field-literal "PRICE" "type/Number"] 10]
+                                                                      :source-table (str "card__" source-id)}
+                                                           :type     :query
+                                                           :database -1337}}]]
+        (with-rasta
+          (with-dashboard-cleanup
+            (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection-id)
+            (-> card-id Card test-automagic-analysis)))))))
+
+(expect
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [{collection-id :id}]
+                    Card [{card-id :id} {:table_id      (data/id :venues)
+                                         :collection_id collection-id
+                                         :dataset_query {:query    {:filter       [:> [:field-id (data/id :venues :price)] 10]
+                                                                    :source-table (data/id :venues)}
+                                                         :type     :query
+                                                         :database (data/id)}}]]
+      (with-rasta
+        (with-dashboard-cleanup
+          (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection-id)
+          (-> card-id Card test-automagic-analysis))))))
+
+(expect
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (let [source-query {:native   {:query "select * from venues"}
+                        :type     :native
+                        :database (data/id)}]
+      (tt/with-temp* [Collection [{collection-id :id}]
+                      Card [{source-id :id} {:table_id        nil
+                                             :collection_id   collection-id
+                                             :dataset_query   source-query
+                                             :result_metadata (#'card.api/result-metadata-for-query source-query)}]
+                      Card [{card-id :id} {:table_id      nil
+                                           :collection_id collection-id
+                                           :dataset_query {:query    {:filter       [:> [:field-literal "PRICE" "type/Number"] 10]
+                                                                      :source-table (str "card__" source-id)}
+                                                           :type     :query
+                                                           :database -1337}}]]
+        (with-rasta
+          (with-dashboard-cleanup
+            (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection-id)
+            (-> card-id Card test-automagic-analysis)))))))
+
+(expect
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [{collection-id :id}]
+                    Card [{card-id :id} {:table_id      (data/id :venues)
+                                         :collection_id collection-id
+                                         :dataset_query {:query    {:aggregation  [[:count]]
+                                                                    :breakout     [[:field-id (data/id :venues :category_id)]]
+                                                                    :source-table (data/id :venues)}
+                                                         :type     :query
+                                                         :database (data/id)}}]]
+      (with-rasta
+        (with-dashboard-cleanup
+          (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection-id)
+          (-> card-id Card test-automagic-analysis))))))
+
+(expect
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [{collection-id :id}]
+                    Card [{card-id :id} {:table_id      nil
+                                         :collection_id collection-id
+                                         :dataset_query {:native   {:query "select * from users"}
+                                                         :type     :native
+                                                         :database (data/id)}}]]
+      (with-rasta
+        (with-dashboard-cleanup
+          (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection-id)
+          (-> card-id Card test-automagic-analysis))))))
+
+(expect
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [{collection-id :id}]
+                    Card [{card-id :id} {:table_id      nil
+                                         :collection_id collection-id
+                                         :dataset_query {:native   {:query "select * from users"}
+                                                         :type     :native
+                                                         :database (data/id)}}]]
+      (with-rasta
+        (with-dashboard-cleanup
+          (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection-id)
+          (-> card-id Card test-automagic-analysis))))))
+
+(expect
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [{collection-id :id}]
+                    Card [{card-id :id} {:table_id      (data/id :venues)
+                                         :collection_id collection-id
+                                         :dataset_query {:query    {:filter       [:> [:field-id (data/id :venues :price)] 10]
+                                                                    :source-table (data/id :venues)}
+                                                         :type     :query
+                                                         :database (data/id)}}]]
+      (with-rasta
+        (with-dashboard-cleanup
+          (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection-id)
+          (-> card-id
+              Card
+              (test-automagic-analysis [:= [:field-id (data/id :venues :category_id)] 2])))))))
+
+
+(expect
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [{collection-id :id}]
+                    Card [{card-id :id} {:table_id      (data/id :venues)
+                                         :collection_id collection-id
+                                         :dataset_query {:query    {:filter       [:> [:field-id (data/id :venues :price)] 10]
+                                                                    :source-table (data/id :venues)}
+                                                         :type     :query
+                                                         :database (data/id)}}]]
+      (with-rasta
+        (with-dashboard-cleanup
+          (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection-id)
+          (-> card-id
+              Card
+              (test-automagic-analysis [:= [:field-id (data/id :venues :category_id)] 2])))))))
 
 
 (expect
   (with-rasta
     (with-dashboard-cleanup
       (let [q (query/adhoc-query {:query {:filter [:> [:field-id (data/id :venues :price)] 10]
-                                          :source_table (data/id :venues)}
+                                          :source-table (data/id :venues)}
                                   :type :query
                                   :database (data/id)})]
-        (-> q (automagic-analysis {}) valid-dashboard?)))))
+        (test-automagic-analysis q)))))
 
 (expect
   (with-rasta
     (with-dashboard-cleanup
       (let [q (query/adhoc-query {:query {:aggregation [[:count]]
                                           :breakout [[:field-id (data/id :venues :category_id)]]
-                                          :source_table (data/id :venues)}
+                                          :source-table (data/id :venues)}
                                   :type :query
                                   :database (data/id)})]
-        (-> q (automagic-analysis {}) valid-dashboard?)))))
+        (test-automagic-analysis q)))))
 
 (expect
   (with-rasta
     (with-dashboard-cleanup
       (let [q (query/adhoc-query {:query {:aggregation [[:count]]
                                           :breakout [[:fk-> (data/id :checkins) (data/id :venues :category_id)]]
-                                          :source_table (data/id :checkins)}
+                                          :source-table (data/id :checkins)}
                                   :type :query
                                   :database (data/id)})]
-        (-> q (automagic-analysis {}) valid-dashboard?)))))
+        (test-automagic-analysis q)))))
 
 (expect
   (with-rasta
     (with-dashboard-cleanup
       (let [q (query/adhoc-query {:query {:filter [:> [:field-id (data/id :venues :price)] 10]
-                                          :source_table (data/id :venues)}
+                                          :source-table (data/id :venues)}
                                   :type :query
                                   :database (data/id)})]
-        (-> q
-            (automagic-analysis {:cell-query [:= [:field-id (data/id :venues :category_id)] 2]})
-            valid-dashboard?)))))
+        (test-automagic-analysis q [:= [:field-id (data/id :venues :category_id)] 2])))))
 
 
 ;;; ------------------- /candidates -------------------
@@ -408,3 +465,60 @@
   (#'magic/optimal-datetime-resolution
    {:fingerprint {:type {:type/DateTime {:earliest "2017-01-01T00:00:00"
                                          :latest   "2017-01-01T00:02:00"}}}}))
+
+
+;;; ------------------- Datetime humanization (for chart and dashboard titles) -------------------
+
+(let [tz                     (-> date/jvm-timezone deref ^TimeZone .getID)
+      dt                     (t/from-time-zone (t/date-time 1990 9 9 12 30)
+                                               (t/time-zone-for-id tz))
+      unparse-with-formatter (fn [formatter dt]
+                               (t.format/unparse
+                                (t.format/formatter formatter (t/time-zone-for-id tz)) dt))]
+  (expect
+    [(tru "at {0}" (unparse-with-formatter "h:mm a, MMMM d, YYYY" dt))
+     (tru "at {0}" (unparse-with-formatter "h a, MMMM d, YYYY" dt))
+     (tru "on {0}" (unparse-with-formatter "MMMM d, YYYY" dt))
+     (tru "in {0} week - {1}"
+          (#'magic/pluralize (date/date-extract :week-of-year dt tz))
+          (str (date/date-extract :year dt tz)))
+     (tru "in {0}" (unparse-with-formatter "MMMM YYYY" dt))
+     (tru "in Q{0} - {1}"
+          (date/date-extract :quarter-of-year dt tz)
+          (str (date/date-extract :year dt tz)))
+     (unparse-with-formatter "YYYY" dt)
+     (unparse-with-formatter "EEEE" dt)
+     (tru "at {0}" (unparse-with-formatter "h a" dt))
+     (unparse-with-formatter "MMMM" dt)
+     (tru "Q{0}" (date/date-extract :quarter-of-year dt tz))
+     (date/date-extract :minute-of-hour dt tz)
+     (date/date-extract :day-of-month dt tz)
+     (date/date-extract :week-of-year dt tz)]
+    (let [dt (t.format/unparse (t.format/formatters :date-hour-minute-second) dt)]
+      [(#'magic/humanize-datetime dt :minute)
+       (#'magic/humanize-datetime dt :hour)
+       (#'magic/humanize-datetime dt :day)
+       (#'magic/humanize-datetime dt :week)
+       (#'magic/humanize-datetime dt :month)
+       (#'magic/humanize-datetime dt :quarter)
+       (#'magic/humanize-datetime dt :year)
+       (#'magic/humanize-datetime dt :day-of-week)
+       (#'magic/humanize-datetime dt :hour-of-day)
+       (#'magic/humanize-datetime dt :month-of-year)
+       (#'magic/humanize-datetime dt :quarter-of-year)
+       (#'magic/humanize-datetime dt :minute-of-hour)
+       (#'magic/humanize-datetime dt :day-of-month)
+       (#'magic/humanize-datetime dt :week-of-year)])))
+
+(expect
+  [(tru "{0}st" 1)
+   (tru "{0}nd" 22)
+   (tru "{0}rd" 303)
+   (tru "{0}th" 0)
+   (tru "{0}th" 8)]
+  (map #'magic/pluralize [1 22 303 0 8]))
+
+;; Make sure we have handlers for all the units available
+(expect
+  (every? (partial #'magic/humanize-datetime "1990-09-09T12:30:00")
+          (concat (var-get #'date/date-extract-units) (var-get #'date/date-trunc-units))))
diff --git a/test/metabase/automagic_dashboards/filters_test.clj b/test/metabase/automagic_dashboards/filters_test.clj
index 28f3e6584d08378e691066376a2e9873e2db5b2b..69f12e08cf8c74c27189463589fd663b528117cd 100644
--- a/test/metabase/automagic_dashboards/filters_test.clj
+++ b/test/metabase/automagic_dashboards/filters_test.clj
@@ -4,16 +4,26 @@
 
 ;; Replace range with the more specific `:=`.
 (expect
-  [:and [:= [:field-id 2] 42] [:= [:fk-> 1 9] "foo"]]
-  (inject-refinement [:and [:= [:fk-> 1 9] "foo"]
-                            [:and [:> [:field-id 2] 10]
-                                  [:< [:field-id 2] 100]]]
+  [:and
+   [:= [:field-id 2] 42]
+   [:= [:fk-> [:field-id 1] [:field-id 9]] "foo"]]
+  (inject-refinement [:and
+                      [:= [:fk-> [:field-id 1] [:field-id 9]] "foo"]
+                      [:and
+                       [:> [:field-id 2] 10]
+                       [:< [:field-id 2] 100]]]
                      [:= [:field-id 2] 42]))
 
 ;; If there's no overlap between filter clauses, just merge using `:and`.
 (expect
-  [:and [:and [:and [:= [:field-id 3] 42] [:= [:fk-> 1 9] "foo"]] [:> [:field-id 2] 10]] [:< [:field-id 2] 100]]
-  (inject-refinement [:and [:= [:fk-> 1 9] "foo"]
-                           [:and [:> [:field-id 2] 10]
-                                 [:< [:field-id 2] 100]]]
+  [:and
+   [:= [:field-id 3] 42]
+   [:= [:fk-> [:field-id 1] [:field-id 9]] "foo"]
+   [:> [:field-id 2] 10]
+   [:< [:field-id 2] 100]]
+  (inject-refinement [:and
+                      [:= [:fk-> [:field-id 1] [:field-id 9]] "foo"]
+                      [:and
+                       [:> [:field-id 2] 10]
+                       [:< [:field-id 2] 100]]]
                      [:= [:field-id 3] 42]))
diff --git a/test/metabase/db/migrations_test.clj b/test/metabase/db/migrations_test.clj
index 3e4f70ad67184dce85b0c6e55919d878966d32b7..3c8c3b5e14ebc33ed36c0c92fd04cd82891a8c1c 100644
--- a/test/metabase/db/migrations_test.clj
+++ b/test/metabase/db/migrations_test.clj
@@ -1,12 +1,18 @@
 (ns metabase.db.migrations-test
   "Tests to make sure the data migrations actually work as expected and don't break things. Shamefully, we have way less
   of these than we should... but that doesn't mean we can't write them for our new ones :)"
-  (:require [expectations :refer :all]
+  (:require [clojure.set :as set]
+            [expectations :refer :all]
             [medley.core :as m]
             [metabase.db.migrations :as migrations]
             [metabase.models
              [card :refer [Card]]
+             [collection :as collection :refer [Collection]]
+             [dashboard :refer [Dashboard]]
              [database :refer [Database]]
+             [permissions :as perms :refer [Permissions]]
+             [permissions-group :as perm-group :refer [PermissionsGroup]]
+             [pulse :refer [Pulse]]
              [user :refer [User]]]
             [metabase.util :as u]
             [metabase.util.password :as upass]
@@ -17,11 +23,11 @@
 (expect
   {"Card that should get directive"
    {:database true
-    :type     "native"
+    :type     :native
     :native   {:query "#legacySQL\nSELECT * FROM [dataset.table];"}}
    "Card that already has directive"
    {:database true
-    :type     "native"
+    :type     :native
     :native   {:query "#standardSQL\nSELECT * FROM `dataset.table`;"}}}
   ;; Create a BigQuery database with 2 SQL Cards, one that already has a directive and one that doesn't.
   (tt/with-temp* [Database [database {:engine "bigquery"}]
@@ -60,3 +66,115 @@
         [(upass/verify-password "something secret" ldap-salt ldap-pass)
          ;; There should be no change for a non ldap user
          (upass/verify-password "no change" user-salt user-pass)]))))
+
+
+;;; -------------------------------------------- add-migrated-collections --------------------------------------------
+
+(def ^:private migrated-collection-names #{"Migrated Dashboards" "Migrated Pulses" "Migrated Questions"})
+
+(defn- do-with-add-migrated-collections-cleanup [f]
+  ;; remove the root collection perms if they're already there so we don't see warnings about duplicate perms
+  (try
+    (doseq [group-id (db/select-ids PermissionsGroup :id [:not= (u/get-id (perm-group/admin))])]
+      (perms/revoke-collection-permissions! group-id collection/root-collection))
+    (f)
+    (finally
+      (doseq [collection-name migrated-collection-names]
+        (db/delete! Collection :name collection-name)))))
+
+(defmacro ^:private with-add-migrated-collections-cleanup [& body]
+  `(do-with-add-migrated-collections-cleanup (fn [] ~@body)))
+
+;; Should grant All Users Root Collection read permissions
+(expect
+  #{"/collection/root/"}
+  (with-add-migrated-collections-cleanup
+    (#'migrations/add-migrated-collections)
+    (db/select-field :object Permissions
+      :group_id (u/get-id (perm-group/all-users))
+      :object   [:like "/collection/root/%"])))
+
+;; should grant whatever other random groups perms as well
+(expect
+  #{"/collection/root/"}
+  (with-add-migrated-collections-cleanup
+    (tt/with-temp PermissionsGroup [group]
+      (#'migrations/add-migrated-collections)
+      (db/select-field :object Permissions
+                       :group_id (u/get-id group)
+                       :object   [:like "/collection/root/%"]))))
+
+;; Should create the new Collections
+(expect
+  migrated-collection-names
+  (with-add-migrated-collections-cleanup
+    (tt/with-temp* [Pulse     [_]
+                    Card      [_]
+                    Dashboard [_]]
+      (let [collections-before (db/select-field :name Collection)]
+        (#'migrations/add-migrated-collections)
+        (set/difference (db/select-field :name Collection) collections-before)))))
+
+;; Shouldn't create new Collections for models where there's nothing to migrate
+(expect
+  #{"Migrated Dashboards"}
+  (with-add-migrated-collections-cleanup
+    (tt/with-temp* [Dashboard [_]]
+      (let [collections-before (db/select-field :name Collection)]
+        (#'migrations/add-migrated-collections)
+        (set/difference (db/select-field :name Collection) collections-before)))))
+
+;; Should move stuff into the new Collections as appropriate
+(expect
+  (with-add-migrated-collections-cleanup
+    (tt/with-temp Pulse [pulse]
+      (#'migrations/add-migrated-collections)
+      (= (db/select-one-field :collection_id Pulse :id (u/get-id pulse))
+         (db/select-one-id Collection :name "Migrated Pulses")))))
+
+(expect
+  (with-add-migrated-collections-cleanup
+    (tt/with-temp Card [card]
+      (#'migrations/add-migrated-collections)
+      (= (db/select-one-field :collection_id Card :id (u/get-id card))
+         (db/select-one-id Collection :name "Migrated Questions")))))
+
+(expect
+  (with-add-migrated-collections-cleanup
+    (tt/with-temp Dashboard [dashboard]
+      (#'migrations/add-migrated-collections)
+      (= (db/select-one-field :collection_id Dashboard :id (u/get-id dashboard))
+         (db/select-one-id Collection :name "Migrated Dashboards")))))
+
+;; All Users shouldn't get any permissions for the 'migrated' groups
+(expect
+  []
+  (with-add-migrated-collections-cleanup
+    (tt/with-temp* [Pulse     [_]
+                    Card      [_]
+                    Dashboard [_]]
+      (#'migrations/add-migrated-collections)
+      (db/select Permissions
+        {:where [:and
+                 [:= :group_id (u/get-id (perm-group/all-users))]
+                 (cons
+                  :or
+                  (for [migrated-collection-id (db/select-ids Collection :name [:in migrated-collection-names])]
+                    [:like :object (format "/collection/%d/%%" migrated-collection-id)]))]}))))
+
+;; ...nor should other groups that happen to exist
+(expect
+  []
+  (tt/with-temp PermissionsGroup [group]
+    (with-add-migrated-collections-cleanup
+      (tt/with-temp* [Pulse     [_]
+                      Card      [_]
+                      Dashboard [_]]
+        (#'migrations/add-migrated-collections)
+        (db/select Permissions
+          {:where [:and
+                   [:= :group_id (u/get-id group)]
+                   (cons
+                    :or
+                    (for [migrated-collection-id (db/select-ids Collection :name [:in migrated-collection-names])]
+                      [:like :object (format "/collection/%d/%%" migrated-collection-id)]))]})))))
diff --git a/test/metabase/driver/bigquery_test.clj b/test/metabase/driver/bigquery_test.clj
index 2fa7f95947b9d29fc00ef436b36f20d38e2f36dd..63fb5c139b9cb110e27a6b0cc160743e74529aa4 100644
--- a/test/metabase/driver/bigquery_test.clj
+++ b/test/metabase/driver/bigquery_test.clj
@@ -1,20 +1,23 @@
 (ns metabase.driver.bigquery-test
-  (:require [expectations :refer :all]
+  (:require [clj-time.core :as time]
+            [expectations :refer :all]
             [honeysql.core :as hsql]
             [metabase
              [driver :as driver]
              [query-processor :as qp]
-             [query-processor-test :as qptest]]
+             [query-processor-test :as qptest]
+             [util :as u]]
             [metabase.driver.bigquery :as bigquery]
             [metabase.models
+             [database :refer [Database]]
              [field :refer [Field]]
              [table :refer [Table]]]
             [metabase.query-processor.interface :as qpi]
-            [metabase.query-processor.middleware.expand :as ql]
             [metabase.test
              [data :as data]
              [util :as tu]]
-            [metabase.test.data.datasets :refer [expect-with-engine]]))
+            [metabase.test.data.datasets :refer [expect-with-engine]]
+            [toucan.util.test :as tt]))
 
 (def ^:private col-defaults
   {:remapped_to nil, :remapped_from nil})
@@ -71,7 +74,7 @@
   (qptest/rows+column-names
     (qp/process-query {:database (data/id)
                        :type     "query"
-                       :query    {:source_table (data/id :checkins)
+                       :query    {:source-table (data/id :checkins)
                                   :aggregation  [["named" ["max" ["+" ["field-id" (data/id :checkins :user_id)]
                                                                       ["field-id" (data/id :checkins :venue_id)]]]
                                                   "User ID Plus Venue ID"]]}})))
@@ -85,50 +88,53 @@
   (binding [qpi/*driver* (driver/engine->driver :bigquery)]
     (aggregation-names (#'bigquery/pre-alias-aggregations query-map))))
 
-(defn- agg-query-map [aggregations]
-  (-> {}
-      (ql/source-table 1)
-      (ql/aggregation aggregations)))
+(defn- expanded-query-with-aggregations [aggregations]
+  (-> (qp/expand {:database (data/id)
+                  :type     :query
+                  :query    {:source-table (data/id :venues)
+                             :aggregation  aggregations}})
+      :query))
 
 ;; make sure BigQuery can handle two aggregations with the same name (#4089)
 (expect
   ["sum" "count" "sum_2" "avg" "sum_3" "min"]
-  (pre-alias-aggregations' (agg-query-map [(ql/sum (ql/field-id 2))
-                                           (ql/count (ql/field-id 2))
-                                           (ql/sum (ql/field-id 2))
-                                           (ql/avg (ql/field-id 2))
-                                           (ql/sum (ql/field-id 2))
-                                           (ql/min (ql/field-id 2))])))
+  (pre-alias-aggregations'
+   (expanded-query-with-aggregations
+    [[:sum [:field-id (data/id :venues :id)]]
+     [:count [:field-id (data/id :venues :id)]]
+     [:sum [:field-id (data/id :venues :id)]]
+     [:avg [:field-id (data/id :venues :id)]]
+     [:sum [:field-id (data/id :venues :id)]]
+     [:min [:field-id (data/id :venues :id)]]])))
 
 (expect
   ["sum" "count" "sum_2" "avg" "sum_2_2" "min"]
-  (pre-alias-aggregations' (agg-query-map [(ql/sum (ql/field-id 2))
-                                           (ql/count (ql/field-id 2))
-                                           (ql/sum (ql/field-id 2))
-                                           (ql/avg (ql/field-id 2))
-                                           (assoc (ql/sum (ql/field-id 2)) :custom-name "sum_2")
-                                           (ql/min (ql/field-id 2))])))
+  (pre-alias-aggregations'
+   (expanded-query-with-aggregations [[:sum [:field-id (data/id :venues :id)]]
+                                      [:count [:field-id (data/id :venues :id)]]
+                                      [:sum [:field-id (data/id :venues :id)]]
+                                      [:avg [:field-id (data/id :venues :id)]]
+                                      [:named [:sum [:field-id (data/id :venues :id)]] "sum_2"]
+                                      [:min [:field-id (data/id :venues :id)]]])))
 
 (expect-with-engine :bigquery
   {:rows [[7929 7929]], :columns ["sum" "sum_2"]}
   (qptest/rows+column-names
     (qp/process-query {:database (data/id)
                        :type     "query"
-                       :query    (-> {}
-                                     (ql/source-table (data/id :checkins))
-                                     (ql/aggregation (ql/sum (ql/field-id (data/id :checkins :user_id)))
-                                                     (ql/sum (ql/field-id (data/id :checkins :user_id)))))})))
+                       :query    {:source-table (data/id :checkins)
+                                  :aggregation [[:sum [:field-id (data/id :checkins :user_id)]]
+                                                [:sum [:field-id (data/id :checkins :user_id)]]]}})))
 
 (expect-with-engine :bigquery
   {:rows [[7929 7929 7929]], :columns ["sum" "sum_2" "sum_3"]}
   (qptest/rows+column-names
     (qp/process-query {:database (data/id)
                        :type     "query"
-                       :query    (-> {}
-                                     (ql/source-table (data/id :checkins))
-                                     (ql/aggregation (ql/sum (ql/field-id (data/id :checkins :user_id)))
-                                                     (ql/sum (ql/field-id (data/id :checkins :user_id)))
-                                                     (ql/sum (ql/field-id (data/id :checkins :user_id)))))})))
+                       :query    {:source-table (data/id :checkins)
+                                  :aggregation  [[:sum [:field-id (data/id :checkins :user_id)]]
+                                                 [:sum [:field-id (data/id :checkins :user_id)]]
+                                                 [:sum [:field-id (data/id :checkins :user_id)]]]}})))
 
 (expect-with-engine :bigquery
   "UTC"
@@ -168,3 +174,38 @@
 (expect
   ["SELECT `dataset.table`"]
   (hsql/format {:select [(#'bigquery/map->BigQueryIdentifier {:dataset-name "dataset", :table-name "table"})]}))
+
+(defn- native-timestamp-query [db-or-db-id timestamp-str timezone-str]
+  (-> (qp/process-query
+        {:database (u/get-id db-or-db-id)
+         :type     :native
+         :native   {:query (format "select datetime(TIMESTAMP \"%s\", \"%s\")" timestamp-str timezone-str)}})
+      :data
+      :rows
+      ffirst))
+
+;; This query tests out the timezone handling of parsed dates. For this test a UTC date is returned, we should
+;; read/return it as UTC
+(expect-with-engine :bigquery
+  "2018-08-31T00:00:00.000Z"
+  (native-timestamp-query (data/id) "2018-08-31 00:00:00" "UTC"))
+
+;; This test includes a `use-jvm-timezone` flag of true that will assume that the date coming from BigQuery is already
+;; in the JVM's timezone. The test puts the JVM's timezone into America/Chicago an ensures that the correct date is
+;; compared
+(expect-with-engine :bigquery
+  "2018-08-31T00:00:00.000-05:00"
+  (tu/with-jvm-tz (time/time-zone-for-id "America/Chicago")
+    (tt/with-temp* [Database [db {:engine :bigquery
+                                  :details (assoc (:details (Database (data/id)))
+                                             :use-jvm-timezone true)}]]
+      (native-timestamp-query db "2018-08-31 00:00:00-05" "America/Chicago"))))
+
+;; Similar to the above test, but covers a positive offset
+(expect-with-engine :bigquery
+  "2018-08-31T00:00:00.000+07:00"
+  (tu/with-jvm-tz (time/time-zone-for-id "Asia/Jakarta")
+    (tt/with-temp* [Database [db {:engine :bigquery
+                                  :details (assoc (:details (Database (data/id)))
+                                             :use-jvm-timezone true)}]]
+      (native-timestamp-query db "2018-08-31 00:00:00+07" "Asia/Jakarta"))))
diff --git a/test/metabase/driver/druid_test.clj b/test/metabase/driver/druid_test.clj
index fe90345cf78be19a5e638c5dcf28923e2acdf09e..facad238462e60a7585121719659865c5b9a6d8a 100644
--- a/test/metabase/driver/druid_test.clj
+++ b/test/metabase/driver/druid_test.clj
@@ -13,7 +13,6 @@
              [field :refer [Field]]
              [metric :refer [Metric]]
              [table :refer [Table]]]
-            [metabase.query-processor.middleware.expand :as ql]
             [metabase.test
              [data :as data]
              [util :as tu]]
@@ -24,11 +23,11 @@
 ;;; table-rows-sample
 (datasets/expect-with-engine :druid
   ;; druid returns a timestamp along with the query, but that shouldn't really matter here :D
-  [["1"    "The Misfit Restaurant + Bar" "2014-04-07T07:00:00.000Z"]
-   ["10"   "Dal Rae Restaurant"          "2015-08-22T07:00:00.000Z"]
-   ["100"  "PizzaHacker"                 "2014-07-26T07:00:00.000Z"]
-   ["1000" "Tito's Tacos"                "2014-06-03T07:00:00.000Z"]
-   ["101"  "Golden Road Brewing"         "2015-09-04T07:00:00.000Z"]]
+  [["1"    "The Misfit Restaurant + Bar" #inst "2014-04-07T07:00:00.000Z"]
+   ["10"   "Dal Rae Restaurant"          #inst "2015-08-22T07:00:00.000Z"]
+   ["100"  "PizzaHacker"                 #inst "2014-07-26T07:00:00.000Z"]
+   ["1000" "Tito's Tacos"                #inst "2014-06-03T07:00:00.000Z"]
+   ["101"  "Golden Road Brewing"         #inst "2015-09-04T07:00:00.000Z"]]
   (->> (driver/table-rows-sample (Table (data/id :checkins))
                                  [(Field (data/id :checkins :id))
                                   (Field (data/id :checkins :venue_name))])
@@ -37,11 +36,11 @@
 
 (datasets/expect-with-engine :druid
   ;; druid returns a timestamp along with the query, but that shouldn't really matter here :D
-  [["1"    "The Misfit Restaurant + Bar" "2014-04-07T00:00:00.000-07:00"]
-   ["10"   "Dal Rae Restaurant"          "2015-08-22T00:00:00.000-07:00"]
-   ["100"  "PizzaHacker"                 "2014-07-26T00:00:00.000-07:00"]
-   ["1000" "Tito's Tacos"                "2014-06-03T00:00:00.000-07:00"]
-   ["101"  "Golden Road Brewing"         "2015-09-04T00:00:00.000-07:00"]]
+  [["1"    "The Misfit Restaurant + Bar" #inst "2014-04-07T00:00:00.000-07:00"]
+   ["10"   "Dal Rae Restaurant"          #inst "2015-08-22T00:00:00.000-07:00"]
+   ["100"  "PizzaHacker"                 #inst "2014-07-26T00:00:00.000-07:00"]
+   ["1000" "Tito's Tacos"                #inst "2014-06-03T00:00:00.000-07:00"]
+   ["101"  "Golden Road Brewing"         #inst "2015-09-04T00:00:00.000-07:00"]]
   (tu/with-temporary-setting-values [report-timezone "America/Los_Angeles"]
     (->> (driver/table-rows-sample (Table (data/id :checkins))
                                    [(Field (data/id :checkins :id))
@@ -51,11 +50,11 @@
 
 (datasets/expect-with-engine :druid
   ;; druid returns a timestamp along with the query, but that shouldn't really matter here :D
-  [["1"    "The Misfit Restaurant + Bar" "2014-04-07T02:00:00.000-05:00"]
-   ["10"   "Dal Rae Restaurant"          "2015-08-22T02:00:00.000-05:00"]
-   ["100"  "PizzaHacker"                 "2014-07-26T02:00:00.000-05:00"]
-   ["1000" "Tito's Tacos"                "2014-06-03T02:00:00.000-05:00"]
-   ["101"  "Golden Road Brewing"         "2015-09-04T02:00:00.000-05:00"]]
+  [["1"    "The Misfit Restaurant + Bar" #inst "2014-04-07T02:00:00.000-05:00"]
+   ["10"   "Dal Rae Restaurant"          #inst "2015-08-22T02:00:00.000-05:00"]
+   ["100"  "PizzaHacker"                 #inst "2014-07-26T02:00:00.000-05:00"]
+   ["1000" "Tito's Tacos"                #inst "2014-06-03T02:00:00.000-05:00"]
+   ["101"  "Golden Road Brewing"         #inst "2015-09-04T02:00:00.000-05:00"]]
   (tu/with-jvm-tz (time/time-zone-for-id "America/Chicago")
     (->> (driver/table-rows-sample (Table (data/id :checkins))
                                    [(Field (data/id :checkins :id))
@@ -122,16 +121,15 @@
   (:status (process-native-query native-query-2)))
 
 
-;;; +------------------------------------------------------------------------------------------------------------------------+
-;;; |                                                EXPRESSION AGGREGATIONS                                                 |
-;;; +------------------------------------------------------------------------------------------------------------------------+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                            EXPRESSION AGGREGATIONS                                             |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
 
 (defmacro ^:private druid-query {:style/indent 0} [& body]
   `(tqpt/with-flattened-dbdef
-     (qp/process-query {:database (data/id)
-                        :type     :query
-                        :query    (data/query ~'checkins
-                                    ~@body)})))
+     (qp/process-query (data/mbql-query ~'checkins
+                         ~@body))))
 
 (defmacro ^:private druid-query-returning-rows {:style/indent 0} [& body]
   `(rows (druid-query ~@body)))
@@ -142,9 +140,9 @@
 (expect-with-engine :druid
   [["2015-10-04T00:00:00.000Z" 9]]
   (druid-query-returning-rows
-    (ql/filter (ql/between (ql/datetime-field $timestamp :day) "2015-10-04" "2015-10-10"))
-    (ql/aggregation (ql/count $id))
-    (ql/breakout (ql/datetime-field $timestamp :week))))
+    {:filter      [:between [:datetime-field $timestamp :day] "2015-10-04" "2015-10-10"]
+     :aggregation [[:count $id]]
+     :breakout    [[:datetime-field $timestamp :week]]}))
 
 ;; sum, *
 (expect-with-engine :druid
@@ -153,8 +151,8 @@
    ["3" 179661.0]
    ["4"  86284.0]]
   (druid-query-returning-rows
-    (ql/aggregation (ql/sum (ql/* $id $venue_price)))
-    (ql/breakout $venue_price)))
+    {:aggregation [[:sum [:* $id $venue_price]]]
+     :breakout    [$venue_price]}))
 
 ;; min, +
 (expect-with-engine :druid
@@ -163,8 +161,8 @@
    ["3"  8.0]
    ["4" 12.0]]
   (druid-query-returning-rows
-    (ql/aggregation (ql/min (ql/+ $id $venue_price)))
-    (ql/breakout $venue_price)))
+    {:aggregation [[:min [:+ $id $venue_price]]]
+     :breakout    [$venue_price]}))
 
 ;; max, /
 (expect-with-engine :druid
@@ -173,8 +171,8 @@
    ["3"  332.0]
    ["4"  248.25]]
   (druid-query-returning-rows
-    (ql/aggregation (ql/max (ql// $id $venue_price)))
-    (ql/breakout $venue_price)))
+    {:aggregation [[:max [:/ $id $venue_price]]]
+     :breakout    [$venue_price]}))
 
 ;; avg, -
 (expect-with-engine :druid
@@ -183,8 +181,8 @@
    ["3" 1562.2695652173913]
    ["4" 1760.8979591836735]]
   (druid-query-returning-rows
-    (ql/aggregation (ql/avg (ql/* $id $venue_price)))
-    (ql/breakout $venue_price)))
+    {:aggregation [[:avg [:* $id $venue_price]]]
+     :breakout    [$venue_price]}))
 
 ;; post-aggregation math w/ 2 args: count + sum
 (expect-with-engine :druid
@@ -193,9 +191,8 @@
    ["3"  460.0]
    ["4"  245.0]]
   (druid-query-returning-rows
-    (ql/aggregation (ql/+ (ql/count $id)
-                          (ql/sum $venue_price)))
-    (ql/breakout $venue_price)))
+    {:aggregation [[:+ [:count $id] [:sum $venue_price]]]
+     :breakout    [$venue_price]}))
 
 ;; post-aggregation math w/ 3 args: count + sum + count
 (expect-with-engine :druid
@@ -204,10 +201,11 @@
    ["3"  575.0]
    ["4"  294.0]]
   (druid-query-returning-rows
-    (ql/aggregation (ql/+ (ql/count $id)
-                          (ql/sum $venue_price)
-                          (ql/count $venue_price)))
-    (ql/breakout $venue_price)))
+    {:aggregation [[:+
+                    [:count $id]
+                    [:sum $venue_price]
+                    [:count $venue_price]]]
+     :breakout    [$venue_price]}))
 
 ;; post-aggregation math w/ a constant: count * 10
 (expect-with-engine :druid
@@ -216,9 +214,8 @@
    ["3" 1150.0]
    ["4"  490.0]]
   (druid-query-returning-rows
-    (ql/aggregation (ql/* (ql/count $id)
-                          10))
-    (ql/breakout $venue_price)))
+    {:aggregation [[:* [:count $id] 10]]
+     :breakout    [$venue_price]}))
 
 ;; nested post-aggregation math: count + (count * sum)
 (expect-with-engine :druid
@@ -227,10 +224,10 @@
    ["3"  39790.0]
    ["4"  9653.0]]
   (druid-query-returning-rows
-    (ql/aggregation (ql/+ (ql/count $id)
-                          (ql/* (ql/count $id)
-                                (ql/sum $venue_price))))
-    (ql/breakout $venue_price)))
+    {:aggregation [[:+
+                    [:count $id]
+                    [:* [:count $id] [:sum $venue_price]]]]
+     :breakout    [$venue_price]}))
 
 ;; post-aggregation math w/ avg: count + avg
 (expect-with-engine :druid
@@ -239,9 +236,8 @@
    ["3"  635.7565217391304]
    ["4"  489.2244897959184]]
   (druid-query-returning-rows
-    (ql/aggregation (ql/+ (ql/count $id)
-                          (ql/avg $id)))
-    (ql/breakout $venue_price)))
+    {:aggregation [[:+ [:count $id] [:avg $id]]]
+     :breakout    [$venue_price]}))
 
 ;; post aggregation math + math inside aggregations: max(venue_price) + min(venue_price - id)
 (expect-with-engine :druid
@@ -250,9 +246,10 @@
    ["3" -990.0]
    ["4" -985.0]]
   (druid-query-returning-rows
-    (ql/aggregation (ql/+ (ql/max $venue_price)
-                          (ql/min (ql/- $venue_price $id))))
-    (ql/breakout $venue_price)))
+    {:aggregation [[:+
+                    [:max $venue_price]
+                    [:min [:- $venue_price $id]]]]
+     :breakout    [$venue_price]}))
 
 ;; aggregation w/o field
 (expect-with-engine :druid
@@ -261,8 +258,8 @@
    ["3" 116.0]
    ["4"  50.0]]
   (druid-query-returning-rows
-    (ql/aggregation (ql/+ 1 (ql/count)))
-    (ql/breakout $venue_price)))
+    {:aggregation [[:+ 1 [:count]]]
+     :breakout    [$venue_price]}))
 
 ;; aggregation with math inside the aggregation :scream_cat:
 (expect-with-engine :druid
@@ -271,8 +268,8 @@
    ["3"  460.0]
    ["4"  245.0]]
   (druid-query-returning-rows
-    (ql/aggregation (ql/sum (ql/+ $venue_price 1)))
-    (ql/breakout $venue_price)))
+    {:aggregation [[:sum [:+ $venue_price 1]]]
+     :breakout    [$venue_price]}))
 
 ;; check that we can name an expression aggregation w/ aggregation at top-level
 (expect-with-engine :druid
@@ -284,8 +281,8 @@
              "New Price"]}
   (rows+column-names
     (druid-query
-      (ql/aggregation (ql/named (ql/sum (ql/+ $venue_price 1)) "New Price"))
-      (ql/breakout $venue_price))))
+      {:aggregation [[:named [:sum [:+ $venue_price 1]] "New Price"]]
+       :breakout    [$venue_price]})))
 
 ;; check that we can name an expression aggregation w/ expression at top-level
 (expect-with-engine :druid
@@ -296,8 +293,8 @@
    :columns ["venue_price" "Sum-41"]}
   (rows+column-names
     (druid-query
-      (ql/aggregation (ql/named (ql/- (ql/sum $venue_price) 41) "Sum-41"))
-      (ql/breakout $venue_price))))
+      {:aggregation [[:named [:- [:sum $venue_price] 41] "Sum-41"]]
+       :breakout    [$venue_price]})))
 
 ;; check that we can handle METRICS (ick) inside expression aggregation clauses
 (expect-with-engine :druid
@@ -311,23 +308,23 @@
               {:database (data/id)
                :type     :query
                :query    {:source-table (data/id :checkins)
-                          :aggregation  [:+ ["METRIC" (u/get-id metric)] 1]
-                          :breakout     [(ql/breakout (ql/field-id (data/id :checkins :venue_price)))]}})))))
+                          :aggregation  [:+ [:metric (u/get-id metric)] 1]
+                          :breakout     [[:field-id (data/id :checkins :venue_price)]]}})))))
 
 (expect
   #"com.jcraft.jsch.JSchException:"
   (try
     (let [engine :druid
-      details {:ssl false,
-               :password "changeme",
-               :tunnel-host "localhost",
-               :tunnel-pass "BOGUS-BOGUS",
-               :port 5432,
-               :dbname "test",
-               :host "http://localhost",
-               :tunnel-enabled true,
-               :tunnel-port 22,
-               :tunnel-user "bogus"}]
+          details    {:ssl            false
+                      :password       "changeme"
+                      :tunnel-host    "localhost"
+                      :tunnel-pass    "BOGUS-BOGUS"
+                      :port           5432
+                      :dbname         "test"
+                      :host           "http://localhost"
+                      :tunnel-enabled true
+                      :tunnel-port    22
+                      :tunnel-user    "bogus"}]
       (driver/can-connect-with-details? engine details :rethrow-exceptions))
        (catch Exception e
          (.getMessage e))))
@@ -348,5 +345,5 @@
        ;; stub out the query and delete functions so that we know when one is called vs. the other
        (with-redefs [druid/do-query (fn [details query] (deliver called-query? true) @pause-query)
                      druid/DELETE   (fn [url] (deliver called-cancel? true))]
-         (data/run-query checkins
-           (ql/aggregation (ql/count))))))))
+         (data/run-mbql-query checkins
+           {:aggregation [[:count]]}))))))
diff --git a/test/metabase/driver/google_test.clj b/test/metabase/driver/google_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..23b38895d703987c5ab2556fe20dbf9862cb2906
--- /dev/null
+++ b/test/metabase/driver/google_test.clj
@@ -0,0 +1,26 @@
+(ns metabase.driver.google-test
+  (:require [expectations :refer :all]
+            [metabase.config :as config]
+            [metabase.driver.google :as google]))
+
+;; Typical scenario, all config information included
+(expect
+  "Metabase/v0.30.0-snapshot (GPN:Metabse; NWNjNWY0Mw== master)"
+  (#'google/create-application-name  {:tag "v0.30.0-snapshot", :hash "5cc5f43", :branch "master", :date "2018-08-21"}))
+
+;; It's possible to have two hashes come back from our script. Sending a string with a newline in it for the
+;; application name will cause Google connections to fail
+(expect
+  "Metabase/v0.30.0-snapshot (GPN:Metabse; NWNjNWY0MwphYmNkZWYx master)"
+  (#'google/create-application-name {:tag "v0.30.0-snapshot", :hash "5cc5f43\nabcdef1", :branch "master", :date "2018-08-21"}))
+
+;; It's possible to have all ? values if there was some failure in reading version information, or if non was available
+(expect
+  "Metabase/? (GPN:Metabse; Pw== ?)"
+  (#'google/create-application-name {:tag "?", :hash "?", :branch "?", :date "?"}))
+
+;; This shouldn't be possible now that config/mb-version-info always returns a value, but testing an empty map just in
+;; case
+(expect
+  "Metabase/? (GPN:Metabse; ? ?)"
+  (#'google/create-application-name {}))
diff --git a/test/metabase/driver/googleanalytics_test.clj b/test/metabase/driver/googleanalytics_test.clj
index b6cb1885e42d3135594f33e83876436c657cc07b..a68299ca2100bd983b077302b0b752edb270e1c7 100644
--- a/test/metabase/driver/googleanalytics_test.clj
+++ b/test/metabase/driver/googleanalytics_test.clj
@@ -22,7 +22,7 @@
 (expect
   {:query {:filter nil}
    :ga    {:segment nil, :metrics "ga:users"}}
-  (qp/transform-query {:query {:aggregation ["METRIC" "ga:users"]}}))
+  (qp/transform-query {:query {:aggregation [:metric "ga:users"]}}))
 
 
 ;; check that a built-in segment gets removed from the query and put in `:ga`
@@ -198,7 +198,7 @@
            :visualization_settings {}
            :dataset_query          {:database (u/get-id db)
                                     :type     :query
-                                    :query    {:source_table (u/get-id table)
+                                    :query    {:source-table (u/get-id table)
                                                :aggregation  [[:METRIC "ga:sessions"]
                                                               [:METRIC "ga:1dayUsers"]]
                                                :breakout     [[:datetime-field [:field-id (u/get-id field)] :day]]}}
diff --git a/test/metabase/driver/mongo_test.clj b/test/metabase/driver/mongo_test.clj
index 4a346543db6a20e7ac834315ba7cdb6f819b8137..16debf800d3ed7b743e5707f6cd0f46b698071ff 100644
--- a/test/metabase/driver/mongo_test.clj
+++ b/test/metabase/driver/mongo_test.clj
@@ -6,12 +6,12 @@
              [driver :as driver]
              [query-processor :as qp]
              [query-processor-test :refer [rows]]]
+            [metabase.automagic-dashboards.core :as magic]
             [metabase.driver.mongo :as mongo]
             [metabase.driver.mongo.query-processor :as mongo-qp]
             [metabase.models
              [field :refer [Field]]
              [table :as table :refer [Table]]]
-            [metabase.query-processor.middleware.expand :as ql]
             [metabase.test.data :as data]
             [metabase.test.data
              [datasets :as datasets]
@@ -122,6 +122,24 @@
               :pk?           true}}}
   (driver/describe-table (MongoDriver.) (data/db) (Table (data/id :venues))))
 
+;; Make sure that all-NULL columns work and are synced correctly (#6875)
+(i/def-database-definition ^:private all-null-columns
+  [["bird_species"
+     [{:field-name "name", :base-type :type/Text}
+      {:field-name "favorite_snack", :base-type :type/Text}]
+     [["House Finch" nil]
+      ["Mourning Dove" nil]]]])
+
+(datasets/expect-with-engine :mongo
+  [{:name "_id",            :database_type "java.lang.Long",   :base_type :type/Integer, :special_type :type/PK}
+   {:name "favorite_snack", :database_type "NULL",             :base_type :type/*,       :special_type nil}
+   {:name "name",           :database_type "java.lang.String", :base_type :type/Text,    :special_type :type/Name}]
+  (data/dataset metabase.driver.mongo-test/all-null-columns
+    (map (partial into {})
+         (db/select [Field :name :database_type :base_type :special_type]
+           :table_id (data/id :bird_species)
+           {:order-by [:name]}))))
+
 
 ;;; table-rows-sample
 (datasets/expect-with-engine :mongo
@@ -187,8 +205,8 @@
 (datasets/expect-with-engine :mongo
   [[2 "Lucky Pigeon" (ObjectId. "abcdefabcdefabcdefabcdef")]]
   (rows (data/dataset metabase.driver.mongo-test/with-bson-ids
-          (data/run-query birds
-            (ql/filter (ql/= $bird_id "abcdefabcdefabcdefabcdef"))))))
+          (data/run-mbql-query birds
+            {:filter [:= $bird_id "abcdefabcdefabcdefabcdef"]}))))
 
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
@@ -249,3 +267,12 @@
 (expect
   nil
   (#'mongo/most-common-object-type [[Float 20] [nil 40] [Integer 10] [String 30]]))
+
+
+;; make sure x-rays don't use features that the driver doesn't support
+(datasets/expect-with-engine :mongo
+  true
+  (->> (magic/automagic-analysis (Field (data/id :venues :price)) {})
+       :ordered_cards
+       (mapcat (comp :breakout :query :dataset_query :card))
+       (not-any? #{[:binning-strategy [:field-id (data/id :venues :price)] "default"]})))
diff --git a/test/metabase/driver/mysql_test.clj b/test/metabase/driver/mysql_test.clj
index c0872489171bb13bcd0d86680f4aca1d1da621e9..25b3d938aa24abdf711012a30c8f9dc4176d607e 100644
--- a/test/metabase/driver/mysql_test.clj
+++ b/test/metabase/driver/mysql_test.clj
@@ -1,7 +1,10 @@
 (ns metabase.driver.mysql-test
   (:require [clj-time.core :as t]
             [expectations :refer :all]
+            [honeysql.core :as hsql]
             [metabase
+             [query-processor :as qp]
+             [query-processor-test :as qpt]
              [sync :as sync]
              [util :as u]]
             [metabase.driver
@@ -15,9 +18,7 @@
             [metabase.test.data
              [datasets :refer [expect-with-engine]]
              [interface :refer [def-database-definition]]]
-            [metabase.test.util :as tu]
             [metabase.util.date :as du]
-            [honeysql.core :as hsql]
             [toucan.db :as db]
             [toucan.util.test :as tt])
   (:import metabase.driver.mysql.MySQLDriver))
@@ -34,7 +35,7 @@
   ;; TODO - use the `rows` function from `metabse.query-processor-test`. Preferrably after it's moved to some sort of
   ;; shared test util namespace
   (-> (data/dataset metabase.driver.mysql-test/all-zero-dates
-        (data/run-query exciting-moments-in-history))
+        (data/run-mbql-query exciting-moments-in-history))
       :data :rows))
 
 
@@ -85,7 +86,7 @@
       (db->fields db))))
 
 (expect-with-engine :mysql
-  "America/Los_Angeles"
+  "UTC"
   (tu/db-timezone-id))
 
 (expect-with-engine :mysql
@@ -129,3 +130,49 @@
   ["?" (du/->Timestamp #inst "2018-01-03")]
   (tu/with-temporary-setting-values [report-timezone "UTC"]
     (hsql/format (sqlqp/->honeysql (MySQLDriver.) (du/->Timestamp #inst "2018-01-03")))))
+
+;; Most of our tests either deal in UTC (offset 00:00) or America/Los_Angeles timezones (-07:00/-08:00). When dealing
+;; with dates, we will often truncate the timestamp to a date. When we only test with negative timezone offsets, in
+;; combination with this truncation, means we could have a bug and it's hidden by this negative-only offset. As an
+;; example, if we have a datetime like 2018-08-17 00:00:00-08:00, converting to UTC this becomes 2018-08-17
+;; 08:00:00+00:00, which when truncated is still 2018-08-17. That same scenario in Hong Kong is 2018-08-17
+;; 00:00:00+08:00, which then becomes 2018-08-16 16:00:00+00:00 when converted to UTC, which will truncate to
+;; 2018-08-16, instead of 2018-08-17
+;;
+;; This test ensures if our JVM timezone and reporting timezone are Asia/Hong_Kong, we get a correctly formatted date
+(expect-with-engine :mysql
+  ["2018-04-18T00:00:00.000+08:00"]
+  (tu/with-jvm-tz (t/time-zone-for-id "Asia/Hong_Kong")
+    (tu/with-temporary-setting-values [report-timezone "Asia/Hong_Kong"]
+      (qpt/first-row
+        (du/with-effective-timezone (Database (data/id))
+          (qp/process-query
+           {:database   (data/id)
+            :type       :native
+            :settings   {:report-timezone "UTC"}
+            :native     {:query         "SELECT cast({{date}} as date)"
+                         :template-tags {:date {:name "date" :display_name "Date" :type "date" }}}
+            :parameters [{:type "date/single" :target ["variable" ["template-tag" "date"]] :value "2018-04-18"}]}))))))
+
+;; This tests a similar scenario, but one in which the JVM timezone is in Hong Kong, but the report timezone is in Los
+;; Angeles. The Joda Time date parsing functions for the most part default to UTC. Our tests all run with a UTC JVM
+;; timezone. This test catches a bug where we are incorrectly assuming a date is in UTC when the JVM timezone is
+;; different.
+;;
+;; The original bug can be found here: https://github.com/metabase/metabase/issues/8262. The MySQL driver code was
+;; parsing the date using JodateTime's date parser, which is in UTC. The MySQL driver code was assuming that date was
+;; in the system timezone rather than UTC which caused an incorrect conversion and with the trucation, let to it being
+;; off by a day
+(expect-with-engine :mysql
+  ["2018-04-18T00:00:00.000-07:00"]
+  (tu/with-jvm-tz (t/time-zone-for-id "Asia/Hong_Kong")
+    (tu/with-temporary-setting-values [report-timezone "America/Los_Angeles"]
+      (qpt/first-row
+        (du/with-effective-timezone (Database (data/id))
+          (qp/process-query
+            {:database (data/id),
+             :type :native,
+             :settings {:report-timezone "UTC"}
+             :native     {:query "SELECT cast({{date}} as date)"
+                          :template-tags {:date {:name "date" :display_name "Date" :type "date" }}}
+             :parameters [{:type "date/single" :target ["variable" ["template-tag" "date"]] :value "2018-04-18"}]}))))))
diff --git a/test/metabase/driver/postgres_test.clj b/test/metabase/driver/postgres_test.clj
index f4f5c4ba33eb66794ff33bd779ed236c80ea6976..0947daebe559105e8f432f8fe2243cb565b6c3b3 100644
--- a/test/metabase/driver/postgres_test.clj
+++ b/test/metabase/driver/postgres_test.clj
@@ -18,7 +18,6 @@
              [field :refer [Field]]
              [table :refer [Table]]]
             [metabase.query-processor.interface :as qpi]
-            [metabase.query-processor.middleware.expand :as ql]
             [metabase.sync.sync-metadata :as sync-metadata]
             [metabase.test
              [data :as data]
@@ -83,13 +82,12 @@
       [#uuid "7a5ce4a2-0958-46e7-9685-1a4eaa3bd08a"]
       [#uuid "84ed434e-80b4-41cf-9c88-e334427104ae"]]]])
 
-
 ;; Check that we can load a Postgres Database with a :type/UUID
 (expect-with-engine :postgres
   [{:name "id",      :base_type :type/Integer}
    {:name "user_id", :base_type :type/UUID}]
   (->> (data/dataset metabase.driver.postgres-test/with-uuid
-         (data/run-query users))
+         (data/run-mbql-query users))
        :data
        :cols
        (mapv (u/rpartial select-keys [:name :base_type]))))
@@ -99,15 +97,30 @@
 (expect-with-engine :postgres
   [[2 #uuid "4652b2e7-d940-4d55-a971-7e484566663e"]]
   (rows (data/dataset metabase.driver.postgres-test/with-uuid
-          (data/run-query users
-            (ql/filter (ql/= $user_id "4652b2e7-d940-4d55-a971-7e484566663e"))))))
+          (data/run-mbql-query users
+            {:filter [:= $user_id "4652b2e7-d940-4d55-a971-7e484566663e"]}))))
 
 ;; check that a nil value for a UUID field doesn't barf (#2152)
 (expect-with-engine :postgres
   []
   (rows (data/dataset metabase.driver.postgres-test/with-uuid
-          (data/run-query users
-            (ql/filter (ql/= $user_id nil))))))
+          (data/run-mbql-query users
+            {:filter [:= $user_id nil]}))))
+
+;; Check that we can filter by a UUID for SQL Field filters (#7955)
+(expect-with-engine :postgres
+  [[#uuid "4f01dcfd-13f7-430c-8e6f-e505c0851027" 1]]
+  (data/dataset metabase.driver.postgres-test/with-uuid
+    (rows (qp/process-query {:database   (data/id)
+                             :type       :native
+                             :native     {:query         "SELECT * FROM users WHERE {{user}}"
+                                          :template-tags {:user {:name         "user"
+                                                                 :display_name "User ID"
+                                                                 :type         "dimension"
+                                                                 :dimension    ["field-id" (data/id :users :user_id)]}}}
+                             :parameters [{:type   "text"
+                                           :target ["dimension" ["template-tag" "user"]]
+                                           :value  "4f01dcfd-13f7-430c-8e6f-e505c0851027"}]}))))
 
 
 ;; Make sure that Tables / Fields with dots in their names get escaped properly
@@ -124,7 +137,7 @@
              [2 "four_loko"]
              [3 "ouija_board"]]}
   (-> (data/dataset metabase.driver.postgres-test/dots-in-names
-        (data/run-query objects.stuff))
+        (data/run-mbql-query objects.stuff))
       :data (dissoc :cols :native_form :results_metadata)))
 
 
@@ -143,8 +156,8 @@
   {:columns ["name" "name_2"]
    :rows    [["Cam" "Rasta"]]}
   (-> (data/dataset metabase.driver.postgres-test/duplicate-names
-        (data/run-query people
-          (ql/fields $name $bird_id->birds.name)))
+        (data/run-mbql-query people
+          {:fields [$name $bird_id->birds.name]}))
       :data (dissoc :cols :native_form :results_metadata)))
 
 
@@ -160,9 +173,9 @@
 (expect-with-engine :postgres
   [[1]]
   (rows (data/dataset metabase.driver.postgres-test/ip-addresses
-          (data/run-query addresses
-            (ql/aggregation (ql/count))
-            (ql/filter (ql/= $ip "192.168.1.1"))))))
+          (data/run-mbql-query addresses
+            {:aggregation [[:count]]
+             :filter      [:= $ip "192.168.1.1"]}))))
 
 
 ;;; Util Fns
diff --git a/test/metabase/driver/presto_test.clj b/test/metabase/driver/presto_test.clj
index b8812612469a6d288436828decbae2181832b3e8..91747d66706275f0f2398177370e0f6716292362 100644
--- a/test/metabase/driver/presto_test.clj
+++ b/test/metabase/driver/presto_test.clj
@@ -6,13 +6,10 @@
             [metabase.models
              [field :refer [Field]]
              [table :as table :refer [Table]]]
-            [metabase.query-processor.middleware.expand :as ql]
             [metabase.test
              [data :as data]
              [util :as tu]]
-            [metabase.test.data
-             [dataset-definitions :as defs]
-             [datasets :as datasets :refer [expect-with-engine]]]
+            [metabase.test.data.datasets :as datasets]
             [toucan.db :as db])
   (:import metabase.driver.presto.PrestoDriver))
 
diff --git a/test/metabase/driver/sqlserver_test.clj b/test/metabase/driver/sqlserver_test.clj
index 75ac2428622a9b563cda18728ba81c485bc09789..530b712bd3b7fad3ab1b9a3ed5f2bd0544e115cb 100644
--- a/test/metabase/driver/sqlserver_test.clj
+++ b/test/metabase/driver/sqlserver_test.clj
@@ -6,12 +6,13 @@
              [sqlserver :as sqlserver]]
             [metabase.test
              [data :as data]
-             [util :refer [obj->json->obj] :as tu]]
+             [util :as tu :refer [obj->json->obj]]]
             [metabase.test.data
              [datasets :refer [expect-with-engine]]
              [interface :refer [def-database-definition]]]))
 
-;;; ------------------------------------------------------------ VARCHAR(MAX) ------------------------------------------------------------
+;;; -------------------------------------------------- VARCHAR(MAX) --------------------------------------------------
+
 ;; Make sure something long doesn't come back as some weird type like `ClobImpl`
 (def ^:private ^:const a-gene
   "Really long string representing a gene like \"GGAGCACCTCCACAAGTGCAGGCTATCCTGTCGAGTAAGGCCT...\""
@@ -25,7 +26,7 @@
 (expect-with-engine :sqlserver
   [[1 a-gene]]
   (-> (data/dataset metabase.driver.sqlserver-test/genetic-data
-        (data/run-query genetic-data))
+        (data/run-mbql-query genetic-data))
       :data :rows obj->json->obj)) ; convert to JSON + back so the Clob gets stringified
 
 ;;; Test that additional connection string options work (#5296)
diff --git a/test/metabase/events/dependencies_test.clj b/test/metabase/events/dependencies_test.clj
index 3b23f13b84491346eff8c6502deb9728adb7b06b..8168af95313b10edea6a8d397174df353f58c2c1 100644
--- a/test/metabase/events/dependencies_test.clj
+++ b/test/metabase/events/dependencies_test.clj
@@ -26,13 +26,13 @@
      :dependent_on_id    (u/get-id segment-2)}}
   (tt/with-temp Card [card {:dataset_query {:database (data/id)
                                             :type     :query
-                                            :query    {:source_table (data/id :categories)
-                                                       :filter       ["AND"
-                                                                      ["="
+                                            :query    {:source-table (data/id :categories)
+                                                       :filter       [:and
+                                                                      [:=
                                                                        (data/id :categories :name)
                                                                        "Toucan-friendly"]
-                                                                      ["SEGMENT" (u/get-id segment-1)]
-                                                                      ["SEGMENT" (u/get-id segment-2)]]}}}]
+                                                                      [:segment (u/get-id segment-1)]
+                                                                      [:segment (u/get-id segment-2)]]}}}]
     (process-dependencies-event {:topic :card-create
                                  :item  card})
     (set (map (partial into {})
@@ -44,7 +44,7 @@
   []
   (tt/with-temp Card [card {:dataset_query {:database (data/id)
                                             :type     :query
-                                            :query    {:source_table (data/id :categories)}}}]
+                                            :query    {:source-table (data/id :categories)}}}]
     (process-dependencies-event {:topic :card-create
                                  :item  card})
     (db/select [Dependency :dependent_on_model :dependent_on_id], :model "Card", :model_id (u/get-id card))))
@@ -59,10 +59,10 @@
   (tt/with-temp* [Database [{database-id :id}]
                   Table    [{table-id :id} {:db_id database-id}]
                   Metric   [metric         {:table_id   table-id
-                                            :definition {:aggregation ["count"]
-                                                         :filter      ["AND"
-                                                                       ["SEGMENT" (u/get-id segment-1)]
-                                                                       ["SEGMENT" (u/get-id segment-2)]]}}]]
+                                            :definition {:aggregation [[:count]]
+                                                         :filter      [:and
+                                                                       [:segment (u/get-id segment-1)]
+                                                                       [:segment (u/get-id segment-2)]]}}]]
     (process-dependencies-event {:topic :metric-create
                                  :item  metric})
     (set (map (partial into {})
@@ -81,8 +81,8 @@
                   Metric   [metric         {:table_id   table-id
                                             :definition {:aggregation ["count"]
                                                          :filter      ["AND"
-                                                                       ["SEGMENT" (u/get-id segment-1)]
-                                                                       ["SEGMENT" (u/get-id segment-2)]]}}]]
+                                                                       ["segment" (u/get-id segment-1)]
+                                                                       ["segment" (u/get-id segment-2)]]}}]]
     (process-dependencies-event {:topic :metric-update
                                  :item  metric})
     (set (map (partial into {})
diff --git a/test/metabase/events/revision_test.clj b/test/metabase/events/revision_test.clj
index 710cdffbeffb72a414142ffdfa0dde603ecbf4de..3919fbe07fd4c4b04ab4d17a50c08f259504c09a 100644
--- a/test/metabase/events/revision_test.clj
+++ b/test/metabase/events/revision_test.clj
@@ -21,9 +21,8 @@
   []
   {:display                "table"
    :dataset_query          {:database (data/id)
-                            :type     "query"
-                            :query    {:aggregation ["rows"]
-                                       :source_table (data/id :categories)}}
+                            :type     :query
+                            :query    {:source-table (data/id :categories)}}
    :visualization_settings {}
    :creator_id             (user->id :crowberto)})
 
@@ -36,15 +35,15 @@
    :dataset_query          (:dataset_query card)
    :read_permissions       nil
    :description            nil
-   :display                "table"
+   :display                :table
    :enable_embedding       false
    :embedding_params       nil
-   :id                     (:id card)
+   :id                     (u/get-id card)
    :made_public_by_id      nil
    :name                   (:name card)
    :public_uuid            nil
    :cache_ttl              nil
-   :query_type             "query"
+   :query_type             :query
    :table_id               (data/id :categories)
    :visualization_settings {}
    :result_metadata        nil})
diff --git a/test/metabase/mbql/normalize_test.clj b/test/metabase/mbql/normalize_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..b8bd055d1175a96a17d19b1b4d9eef558b8f602a
--- /dev/null
+++ b/test/metabase/mbql/normalize_test.clj
@@ -0,0 +1,721 @@
+(ns metabase.mbql.normalize-test
+  (:require [expectations :refer :all]
+            [metabase.mbql.normalize :as normalize]))
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                                NORMALIZE TOKENS                                                |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+;; Query type should get normalized
+(expect
+  {:type :native}
+  (#'normalize/normalize-tokens {:type "NATIVE"}))
+
+;; native queries should NOT get normalized
+(expect
+  {:type :native, :native {:query "SELECT COUNT(*) FROM CANS;"}}
+  (#'normalize/normalize-tokens {:type "NATIVE", :native {"QUERY" "SELECT COUNT(*) FROM CANS;"}}))
+
+(expect
+  {:native {:query {:NAME        "FAKE_QUERY"
+                    :description "Theoretical fake query in a JSON-based query lang"}}}
+  (#'normalize/normalize-tokens {:native {:query {:NAME        "FAKE_QUERY"
+                                                  :description "Theoretical fake query in a JSON-based query lang"}}}))
+
+;; do aggregations get normalized?
+(expect
+  {:query {:aggregation :rows}}
+  (#'normalize/normalize-tokens {:query {"AGGREGATION" "ROWS"}}))
+
+(expect
+  {:query {:aggregation [:rows]}}
+  (#'normalize/normalize-tokens {:query {"AGGREGATION" ["ROWS"]}}))
+
+(expect
+ {:query {:aggregation [:count 10]}}
+ (#'normalize/normalize-tokens {:query {"AGGREGATION" ["COUNT" 10]}}))
+
+(expect
+  {:query {:aggregation [[:count 10]]}}
+  (#'normalize/normalize-tokens {:query {"AGGREGATION" [["COUNT" 10]]}}))
+
+;; make sure we normalize ag tokens properly when there's wacky MBQL 95 ag syntax
+(expect
+  {:query {:aggregation [:rows :count]}}
+  (#'normalize/normalize-tokens {:query {:aggregation ["rows" "count"]}}))
+
+(expect
+  {:query {:aggregation [:count :count]}}
+  (#'normalize/normalize-tokens {:query {:aggregation ["count" "count"]}}))
+
+;; don't normalize names of expression refs!
+(expect
+  {:query {:aggregation [:count [:count [:expression "ABCDEF"]]]}}
+  (#'normalize/normalize-tokens {:query {:aggregation ["count" ["count" ["expression" "ABCDEF"]]]}}))
+
+;; make sure binning-strategy clauses get normalized the way we'd expect
+(expect
+  {:query {:breakout [[:binning-strategy 10 :bin-width 2000]]}}
+  (#'normalize/normalize-tokens {:query {:breakout [["BINNING_STRATEGY" 10 "BIN-WIDTH" 2000]]}}))
+
+;; or field literals!
+(expect
+  {:query {:aggregation [:count [:count [:field-literal "ABCDEF" :type/Text]]]}}
+  (#'normalize/normalize-tokens {:query {:aggregation ["count" ["count" ["field_literal" "ABCDEF" "type/Text"]]]}}))
+
+;; event if you try your best to break things it should handle it
+(expect
+  {:query {:aggregation [:count [:sum 10] [:count 20] :count]}}
+  (#'normalize/normalize-tokens {:query {:aggregation ["count" ["sum" 10] ["count" 20] "count"]}}))
+
+;; try an ag that is named
+(expect
+  {:query {:aggregation [:named [:sum 10] "My COOL AG"]}}
+  (#'normalize/normalize-tokens {:query {:aggregation ["named" ["SuM" 10] "My COOL AG"]}}))
+
+;; try an expression ag
+(expect
+  {:query {:aggregation [:+ [:sum 10] [:* [:sum 20] 3]]}}
+  (#'normalize/normalize-tokens {:query {:aggregation ["+" ["sum" 10] ["*" ["SUM" 20] 3]]}}))
+
+;; expression ags should handle varargs
+(expect
+  {:query {:aggregation [:+ [:sum 10] [:sum 20] [:sum 30]]}}
+  (#'normalize/normalize-tokens {:query {:aggregation ["+" ["sum" 10] ["SUM" 20] ["sum" 30]]}}))
+
+;; METRICS shouldn't get normalized in some kind of wacky way
+(expect
+  {:aggregation [:+ [:metric 10] 1]}
+  (#'normalize/normalize-tokens {:aggregation ["+" ["METRIC" 10] 1]}))
+
+;; Nor should SEGMENTS
+(expect
+  {:filter [:= [:+ [:segment 10] 1] 10]}
+  (#'normalize/normalize-tokens {:filter ["=" ["+" ["SEGMENT" 10] 1] 10]}))
+
+;; are expression names exempt from lisp-casing/lower-casing?
+(expect
+  {:query {:expressions {:sales_tax [:- [:field-id 10] [:field-id 20]]}}}
+  (#'normalize/normalize-tokens {"query" {"expressions" {:sales_tax ["-" ["field-id" 10] ["field-id" 20]]}}}))
+
+;; expression names should always be keywords
+(expect
+  {:query {:expressions {:sales_tax [:- [:field-id 10] [:field-id 20]]}}}
+  (#'normalize/normalize-tokens {"query" {"expressions" {:sales_tax ["-" ["field-id" 10] ["field-id" 20]]}}}))
+
+;; expression references should be exempt too
+(expect
+  {:order-by [[:desc [:expression "SALES_TAX"]]]}
+  (#'normalize/normalize-tokens {:order-by [[:desc [:expression "SALES_TAX"]]]}) )
+
+;; ... but they should be converted to strings if passed in as a KW for some reason. Make sure we preserve namespace!
+(expect
+  {:order-by [[:desc [:expression "SALES/TAX"]]]}
+  (#'normalize/normalize-tokens {:order-by [[:desc ["expression" :SALES/TAX]]]}))
+
+;; field literals should be exempt too
+(expect
+  {:order-by [[:desc [:field-literal "SALES_TAX" :type/Number]]]}
+  (#'normalize/normalize-tokens {:order-by [[:desc [:field-literal "SALES_TAX" :type/Number]]]}) )
+
+;; ... but they should be converted to strings if passed in as a KW for some reason
+(expect
+  {:order-by [[:desc [:field-literal "SALES/TAX" :type/Number]]]}
+  (#'normalize/normalize-tokens {:order-by [[:desc ["field_literal" :SALES/TAX "type/Number"]]]}))
+
+;; does order-by get properly normalized?
+(expect
+  {:query {:order-by [[10 :asc]]}}
+  (#'normalize/normalize-tokens {:query {"ORDER_BY" [[10 "ASC"]]}}))
+
+(expect
+  {:query {:order-by [[:asc 10]]}}
+  (#'normalize/normalize-tokens {:query {"ORDER_BY" [["ASC" 10]]}}))
+
+(expect
+  {:query {:order-by [[[:field-id 10] :asc]]}}
+  (#'normalize/normalize-tokens {:query {"ORDER_BY" [[["field_id" 10] "ASC"]]}}))
+
+(expect
+  {:query {:order-by [[:desc [:field-id 10]]]}}
+  (#'normalize/normalize-tokens {:query {"ORDER_BY" [["DESC" ["field_id" 10]]]}}))
+
+;; the unit & amount in time interval clauses should get normalized
+(expect
+  {:query {:filter [:time-interval 10 :current :day]}}
+  (#'normalize/normalize-tokens {:query {"FILTER" ["time-interval" 10 "current" "day"]}}))
+
+;; but amount should not get normalized if it's an integer
+(expect
+  {:query {:filter [:time-interval 10 -10 :day]}}
+  (#'normalize/normalize-tokens {:query {"FILTER" ["time-interval" 10 -10 "day"]}}))
+
+;; make sure we support time-interval options
+(expect
+  [:time-interval 10 -30 :day {:include-current true}]
+  (#'normalize/normalize-tokens ["TIME_INTERVAL" 10 -30 "DAY" {"include_current" true}]))
+
+;; the unit in relative datetime clauses should get normalized
+(expect
+  {:query {:filter [:= [:field-id 10] [:relative-datetime -31 :day]]}}
+  (#'normalize/normalize-tokens {:query {"FILTER" ["=" [:field-id 10] ["RELATIVE_DATETIME" -31 "DAY"]]}}))
+
+;; should work if we do [:relative-datetime :current] as well
+(expect
+  {:query {:filter [:= [:field-id 10] [:relative-datetime :current]]}}
+  (#'normalize/normalize-tokens {:query {"FILTER" ["=" [:field-id 10] ["RELATIVE_DATETIME" "CURRENT"]]}}))
+
+;; and in datetime-field clauses (MBQL 98)
+(expect
+  {:query {:filter [:= [:datetime-field [:field-id 10] :day] "2018-09-05"]}}
+  (#'normalize/normalize-tokens {:query {"FILTER" ["=" [:datetime-field ["field_id" 10] "day"] "2018-09-05"]}}))
+
+;; (or in long-since-deprecated MBQL 95 format)
+(expect
+  {:query {:filter [:= [:datetime-field 10 :as :day] "2018-09-05"]}}
+  (#'normalize/normalize-tokens {:query {"FILTER" ["=" [:datetime-field 10 "as" "day"] "2018-09-05"]}}))
+
+;; if string filters have an options map that should get normalized
+(expect
+  {:query {:filter [:starts-with 10 "ABC" {:case-sensitive true}]}}
+  (#'normalize/normalize-tokens {:query {"FILTER" ["starts_with" 10 "ABC" {"case_sensitive" true}]}}))
+
+;; make sure we're not running around trying to normalize the type in native query params
+(expect
+  {:type       :native
+   :parameters [{:type   :date/range
+                 :target [:dimension [:template-tag "checkin_date"]]
+                 :value  "2015-04-01~2015-05-01"}]}
+  (#'normalize/normalize-tokens {:type       :native
+                                 :parameters [{:type   "date/range"
+                                               :target [:dimension [:template-tag "checkin_date"]]
+                                               :value  "2015-04-01~2015-05-01"}]}))
+
+;; oh yeah, also we don't want to go around trying to normalize template-tag names
+(expect
+  {:type   :native
+   :native {:query         "SELECT COUNT(*) FROM \"PUBLIC\".\"CHECKINS\" WHERE {{checkin_date}}"
+            :template-tags {"checkin_date" {:name         "checkin_date"
+                                            :display-name "Checkin Date"
+                                            :type         :dimension
+                                            :dimension    [:field-id 14]}}}}
+  (#'normalize/normalize-tokens
+   {:type   "native"
+    :native {:query         "SELECT COUNT(*) FROM \"PUBLIC\".\"CHECKINS\" WHERE {{checkin_date}}"
+             :template_tags {:checkin_date {:name         "checkin_date"
+                                            :display_name "Checkin Date"
+                                            :type         :dimension
+                                            :dimension    ["field-id" 14]}}}}))
+
+;; native template tags `:type` should get normalized
+(expect
+  {:native {:query         "SELECT * FROM CATEGORIES WHERE {{names_list}}"
+            :template-tags {"names_list" {:name         "names_list"
+                                          :display-name "Names List"
+                                          :type         :dimension
+                                          :dimension    [:field-id 49]}}}}
+  (#'normalize/normalize-tokens
+   {:native {:query          "SELECT * FROM CATEGORIES WHERE {{names_list}}"
+             "template_tags" {:names_list {:name         "names_list"
+                                           :display_name "Names List"
+                                           :type         "dimension"
+                                           :dimension    ["field-id" 49]}}}}))
+
+;; `:parameters` `:type` should get normalized, but `:value` should not.
+(expect
+  {:type       :native
+   :parameters [{:type   :text
+                 :target [:dimension [:template-tag "names_list"]]
+                 :value  ["BBQ" "Bakery" "Bar"]}]}
+  (#'normalize/normalize-tokens
+   {:type       "native"
+    :parameters [{:type   "text"
+                  :target ["dimension" ["template-tag" "names_list"]]
+                  :value  ["BBQ" "Bakery" "Bar"]}]}))
+
+;; make sure normalization doesn't try to parse value as an MBQL clause
+(expect
+  {:type       :native
+   :parameters [{:type   :text
+                 :target [:dimension [:template-tag "names_list"]]
+                 :value  ["=" 10 20]}]}
+  (#'normalize/normalize-tokens
+   {:type       "native"
+    :parameters [{:type   "text"
+                  :target ["dimension" ["template-tag" "names_list"]]
+                  :value  ["=" 10 20]}]}))
+
+;; Make sure token normalization works correctly on source queries
+(expect
+  {:database 4
+   :type     :query
+   :query    {:source-query {:native        "SELECT * FROM PRODUCTS WHERE CATEGORY = {{category}} LIMIT 10",
+                             :template-tags {"category" {:name         "category"
+                                                         :display-name "Category"
+                                                         :type         :text
+                                                         :required     true
+                                                         :default      "Widget"}}}}}
+  (#'normalize/normalize-tokens
+   {:database 4
+    :type     :query
+    :query    {"source_query" {:native         "SELECT * FROM PRODUCTS WHERE CATEGORY = {{category}} LIMIT 10",
+                               "template_tags" {:category {:name         "category"
+                                                           :display-name "Category"
+                                                           :type         "text"
+                                                           :required     true
+                                                           :default      "Widget"}}}}}))
+
+(expect
+  {:database 4,
+   :type     :query,
+   :query    {:source-query {:source-table 1, :aggregation :rows}}}
+  (#'normalize/normalize-tokens
+   {:database 4
+    :type     :query
+    :query    {"source_query" {"source_table" 1, "aggregation" "rows"}}}))
+
+;; Does the QueryExecution context get normalized?
+(expect
+  {:context :json-download}
+  (#'normalize/normalize-tokens {:context "json-download"}))
+
+;; if `:context` is `nil` it's not our problem
+(expect
+  {:context nil}
+  (#'normalize/normalize-tokens {:context nil}))
+
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                                  CANONICALIZE                                                  |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+;; Does our `wrap-implict-field-id` fn work?
+(expect
+  [:field-id 10]
+  (#'normalize/wrap-implicit-field-id 10))
+
+(expect
+  [:field-id 10]
+  (#'normalize/wrap-implicit-field-id [:field-id 10]))
+
+;; Do aggregations get canonicalized properly?
+(expect
+  {:query {:aggregation [[:count [:field-id 10]]]}}
+  (#'normalize/canonicalize {:query {:aggregation [:count 10]}}))
+
+(expect
+  {:query {:aggregation [[:count]]}}
+  (#'normalize/canonicalize {:query {:aggregation [:count]}}))
+
+(expect
+  {:query {:aggregation [[:count [:field-id 1000]]]}}
+  (#'normalize/canonicalize {:query {:aggregation [:count [:field-id 1000]]}}))
+
+;; :rows aggregation type, being deprecated since FOREVER, should just get removed
+(expect
+  {:query {:aggregation []}}
+  (#'normalize/canonicalize {:query {:aggregation [:rows]}}))
+
+(expect
+  {:query {:aggregation []}}
+  (#'normalize/canonicalize {:query {:aggregation :rows}}))
+
+;; if just a single aggregation is supplied it should always be converted to new-style multiple-aggregation syntax
+(expect
+  {:query {:aggregation [[:count]]}}
+  (#'normalize/canonicalize {:query {:aggregation :count}}))
+
+;; Raw Field IDs in an aggregation should get wrapped in [:field-id] clause
+(expect
+  {:query {:aggregation [[:count [:field-id 10]]]}}
+  (#'normalize/canonicalize {:query {:aggregation [:count 10]}}))
+
+;; make sure we handle single :count with :field-id correctly
+(expect
+  {:query {:aggregation [[:count [:field-id 10]]]}}
+  (#'normalize/canonicalize {:query {:aggregation [:count [:field-id 10]]}}))
+
+;; make sure for multiple aggregations we can handle `:count` that doesn't appear wrapped in brackets
+(expect
+  {:query {:aggregation [[:count] [:sum [:field-id 10]]]}}
+  (#'normalize/canonicalize {:query {:aggregation [:count [:sum 10]]}}))
+
+;; this doesn't make sense, but make sure if someone specifies a `:rows` ag and another one we don't end up with a
+;; `nil` in the ags list
+(expect
+  {:query {:aggregation [[:count]]}}
+  (#'normalize/canonicalize {:query {:aggregation [:rows :count]}}))
+
+;; another stupid aggregation that we need to be able to handle
+(expect
+  {:query {:aggregation [[:count] [:count]]}}
+  (#'normalize/canonicalize {:query {:aggregation [:count :count]}}))
+
+;; a mix of unwrapped & wrapped should still work
+(expect
+  {:query {:aggregation [[:count] [:sum [:field-id 10]] [:count [:field-id 20]] [:count]]}}
+  (#'normalize/canonicalize {:query {:aggregation [:count [:sum 10] [:count 20] :count]}}))
+
+;; make sure we can deal with *named* aggregations!
+(expect
+  {:query {:aggregation [[:named [:sum [:field-id 10]] "Sum *TEN*"]]}}
+  (#'normalize/canonicalize {:query {:aggregation [:named [:sum 10] "Sum *TEN*"]}}))
+
+;; make sure expression aggregations work correctly
+(expect
+  {:query {:aggregation [[:+ [:sum [:field-id 10]] 2]]}}
+  (#'normalize/canonicalize {:query {:aggregation [:+ [:sum 10] 2]}}))
+
+(expect
+  {:query {:aggregation [[:+ [:sum [:field-id 10]] [:* [:sum [:field-id 20]] [:sum [:field-id 30]]]]]}}
+  (#'normalize/canonicalize {:query {:aggregation [:+ [:sum 10] [:* [:sum 20] [:sum 30]]]}}))
+
+;; expression ags should handle varargs
+(expect
+  {:query {:aggregation [[:+ [:sum [:field-id 10]] [:sum [:field-id 20]] [:sum [:field-id 30]]]]}}
+  (#'normalize/canonicalize {:query {:aggregation [[:+ [:sum 10] [:sum 20] [:sum 30]]]}}))
+
+;; METRICS shouldn't get canonicalized in some kind of wacky way
+(expect
+  {:query {:aggregation [[:+ [:metric 1] 2]]}}
+  (#'normalize/canonicalize {:query {:aggregation [:+ [:metric 1] 2]}}))
+
+;; can cumulative-count be handled with or without a Field?
+(expect
+  {:query {:aggregation [[:cum-count]]}}
+  (#'normalize/canonicalize {:query {:aggregation [:cum-count]}}))
+
+(expect
+  {:query {:aggregation [[:cum-count [:field-id 10]]]}}
+  (#'normalize/canonicalize {:query {:aggregation [:cum-count 10]}}))
+
+;; implicit Field IDs should get wrapped in [:field-id] in :breakout
+(expect
+  {:query {:breakout [[:field-id 10]]}}
+  (#'normalize/canonicalize {:query {:breakout [10]}}))
+
+(expect
+  {:query {:breakout [[:field-id 10] [:field-id 20]]}}
+  (#'normalize/canonicalize {:query {:breakout [10 20]}}))
+
+(expect
+  {:query {:breakout [[:field-id 1000]]}}
+  (#'normalize/canonicalize {:query {:breakout [[:field-id 1000]]}}))
+
+(expect
+  {:query {:fields [[:field-id 10]]}}
+  (#'normalize/canonicalize {:query {:fields [10]}}))
+
+;; implicit Field IDs should get wrapped in [:field-id] in :fields
+(expect
+  {:query {:fields [[:field-id 10] [:field-id 20]]}}
+  (#'normalize/canonicalize {:query {:fields [10 20]}}))
+
+(expect
+  {:query {:fields [[:field-id 1000]]}}
+  (#'normalize/canonicalize {:query {:fields [[:field-id 1000]]}}))
+
+;; implicit Field IDs should get wrapped in [:field-id] in filters
+(expect
+  {:query {:filter [:= [:field-id 10] 20]}}
+  (#'normalize/canonicalize {:query {:filter [:= 10 20]}}))
+
+(expect
+  {:query {:filter [:and [:= [:field-id 10] 20] [:= [:field-id 20] 30]]}}
+  (#'normalize/canonicalize {:query {:filter [:and [:= 10 20] [:= 20 30]]}}))
+
+(expect
+  {:query {:filter [:between [:field-id 10] 20 30]}}
+  (#'normalize/canonicalize {:query {:filter [:between 10 20 30]}}))
+
+;; `:inside` filters should get implict Field IDs for the first two args
+(expect
+  {:query {:filter [:inside [:field-id 1] [:field-id 2] 90 -90 90 -90]}}
+  (#'normalize/canonicalize {:query {:filter [:inside 1 2 90 -90 90 -90]}}))
+
+;; compound filters with only one arg should get automatically de-compounded
+(expect
+  {:query {:filter [:= [:field-id 100] 2]}}
+  (#'normalize/canonicalize {:query {:filter [:and [:= 100 2]]}}))
+
+(expect
+  {:query {:filter [:= [:field-id 100] 2]}}
+  (#'normalize/canonicalize {:query {:filter [:or [:= 100 2]]}}))
+
+;; compound filters should "pull-up" any args that are the same compound filter
+(expect
+  {:query {:filter [:and
+                    [:= [:field-id 100] 1]
+                    [:= [:field-id 200] 2]
+                    [:= [:field-id 300] 3]
+                    [:= [:field-id 400] 4]]}}
+  (#'normalize/canonicalize {:query {:filter [:and
+                                              [:and
+                                               [:= [:field-id 100] 1]
+                                               [:= [:field-id 200] 2]]
+                                              [:and
+                                               [:= [:field-id 300] 3]
+                                               [:= [:field-id 400] 4]]]}}))
+(expect
+  {:query {:filter [:and
+                    [:> [:field-id 4] 1]
+                    [:is-null [:field-id 7]]
+                    [:= [:field-id 5] "abc"]
+                    [:between [:field-id 9] 0 25]]}}
+  (#'normalize/canonicalize {:query {:filter [:and
+                                              [:> [:field-id 4] 1]
+                                              [:is-null [:field-id 7]]
+                                              [:and
+                                               [:= [:field-id 5] "abc"]
+                                               [:between [:field-id 9] 0 25]]]}}))
+
+(expect
+  {:query {:filter [:or
+                    [:= [:field-id 100] 1]
+                    [:= [:field-id 200] 2]
+                    [:= [:field-id 300] 3]
+                    [:= [:field-id 400] 4]]}}
+  (#'normalize/canonicalize {:query {:filter [:or
+                                              [:or
+                                               [:= 100 1]
+                                               [:= 200 2]]
+                                              [:or
+                                               [:= [:field-id 300] 3]
+                                               [:= [:field-id 400] 4]]]}}))
+
+(expect
+  {:query {:filter [:or
+                    [:> [:field-id 4] 1]
+                    [:is-null [:field-id 7]]
+                    [:= [:field-id 5] "abc"]
+                    [:between [:field-id 9] 0 25]]}}
+  (#'normalize/canonicalize {:query {:filter [:or
+                                              [:> [:field-id 4] 1]
+                                              [:is-null [:field-id 7]]
+                                              [:or
+                                               [:= [:field-id 5] "abc"]
+                                               [:between [:field-id 9] 0 25]]]}}))
+
+;; not inside of a not should get elimated entirely
+(expect
+  {:query {:filter [:= [:field-id 100] 1]}}
+  (#'normalize/canonicalize {:query {:filter [:not [:not [:= [:field-id 100] 1]]]}}))
+
+;; make sure we don't overwrite options if specified
+(expect
+  {:query {:filter [:contains [:field-id 10] "ABC" {:case-sensitive false}]}}
+  (#'normalize/canonicalize {:query {:filter [:contains 10 "ABC" {:case-sensitive false}]}}))
+
+;; or for time-interval options
+(expect
+  [:time-interval 10 -30 :day {:include-current true}]
+  (#'normalize/canonicalize [:time-interval 10 -30 :day {:include-current true}]))
+
+;; make sure empty filter clauses don't explode in canonicalize
+(expect
+  {:database 1, :type :query, :query {:filter []}}
+  (#'normalize/canonicalize
+   {:database 1
+    :type     :query
+    :query    {:filter []}}))
+
+;; ORDER BY: MBQL 95 [field direction] should get translated to MBQL 98 [direction field]
+(expect
+  {:query {:order-by [[:asc [:field-id 10]]]}}
+  (#'normalize/canonicalize {:query {:order-by [[[:field-id 10] :asc]]}}))
+
+;; MBQL 95 old order-by names should be handled
+(expect
+  {:query {:order-by [[:asc [:field-id 10]]]}}
+  (#'normalize/canonicalize {:query {:order-by [[10 :ascending]]}}))
+
+;; field-id should be added if needed
+(expect
+  {:query {:order-by [[:asc [:field-id 10]]]}}
+  (#'normalize/canonicalize {:query {:order-by [[10 :asc]]}}))
+
+(expect
+  {:query {:order-by [[:asc [:field-id 10]]]}}
+  (#'normalize/canonicalize {:query {:order-by [[:asc 10]]}}))
+
+;; fk-> clauses should get the field-id treatment
+(expect
+  {:query {:filter [:= [:fk-> [:field-id 10] [:field-id 20]] "ABC"]}}
+  (#'normalize/canonicalize {:query {:filter [:= [:fk-> 10 20] "ABC"]}}))
+
+;; as should datetime-field clauses...
+(expect
+  {:query {:filter [:= [:datetime-field [:field-id 10] :day] "2018-09-05"]}}
+  (#'normalize/canonicalize {:query {:filter [:= [:datetime-field 10 :day] "2018-09-05"]}}))
+
+;; MBQL 95 datetime-field clauses ([:datetime-field <field> :as <unit>]) should get converted to MBQL 98
+(expect
+  {:query {:filter [:= [:datetime-field [:field-id 10] :day] "2018-09-05"]}}
+  (#'normalize/canonicalize {:query {:filter [:= [:datetime-field 10 :as :day] "2018-09-05"]}}))
+
+;; if someone is dumb and passes something like a field-literal inside a field-id, fix it for them.
+(expect
+  {:query {:filter [:= [:field-literal "my_field" "type/Number"] 10]}}
+  (#'normalize/canonicalize {:query {:filter [:= [:field-id [:field-literal "my_field" "type/Number"]] 10]}}))
+
+;; we should fix :field-ids inside :field-ids too
+(expect
+  {:query {:filter [:= [:field-id 1] 10]}}
+  (#'normalize/canonicalize {:query {:filter [:= [:field-id [:field-id 1]] 10]}}))
+
+;; make sure `binning-strategy` wraps implicit Field IDs
+(expect
+  {:query {:breakout [[:binning-strategy [:field-id 10] :bin-width 2000]]}}
+  (#'normalize/canonicalize {:query {:breakout [[:binning-strategy 10 :bin-width 2000]]}}))
+
+
+;; Make sure canonicalization works correctly on source queries
+(expect
+  {:database 4
+   :type     :query
+   :query    {:source-query {:native        "SELECT * FROM PRODUCTS WHERE CATEGORY = {{category}} LIMIT 10",
+                             :template-tags {"category" {:name         "category"
+                                                         :display-name "Category"
+                                                         :type         :text
+                                                         :required     true
+                                                         :default      "Widget"}}}}}
+  ;; nothing to really do here
+  (#'normalize/canonicalize
+   {:database 4
+    :type     :query
+    :query    {:source-query {:native        "SELECT * FROM PRODUCTS WHERE CATEGORY = {{category}} LIMIT 10"
+                              :template-tags {"category" {:name         "category"
+                                                          :display-name "Category"
+                                                          :type         :text
+                                                          :required     true
+                                                          :default      "Widget"}}}}}))
+
+(expect
+  {:database 4,
+   :type     :query,
+   :query    {:source-query {:source-table 1, :aggregation []}}}
+  (#'normalize/canonicalize
+   {:database 4
+    :type     :query
+    :query    {:source-query {:source-table 1, :aggregation :rows}}}))
+
+;; make sure canonicalization can handle aggregations with expressions where the Field normally goes
+(expect
+  {:query {:aggregation [[:sum [:* [:field-id 4] [:field-id 1]]]]}}
+  (#'normalize/canonicalize
+   {:query {:aggregation [[:sum [:* [:field-id 4] [:field-id 1]]]]}}))
+
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                              REMOVE EMPTY CLAUSES                                              |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+;; empty sequences should get removed
+(expect
+  {:y [100]}
+  (#'normalize/remove-empty-clauses {:x [], :y [100]}))
+
+;; nil values should get removed
+(expect
+  {:y 100}
+  (#'normalize/remove-empty-clauses {:x nil, :y 100}))
+
+;; sequences containing only nil should get removed
+(expect
+  {:a [nil 100]}
+  (#'normalize/remove-empty-clauses {:a [nil 100], :b [nil nil]}))
+
+;; empty maps should get removed
+(expect
+  {:a {:b 100}}
+  (#'normalize/remove-empty-clauses {:a {:b 100}, :c {}}))
+
+(expect
+  {:a {:b 100}}
+  (#'normalize/remove-empty-clauses {:a {:b 100}, :c {:d nil}}))
+
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                            PUTTING IT ALL TOGETHER                                             |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+;; With an ugly MBQL 95 query, does everything get normalized nicely?
+(expect
+  {:type :query
+   :query  {:source-table 10
+            :breakout     [[:field-id 10] [:field-id 20]]
+            :filter       [:= [:field-id 10] [:datetime-field [:field-id 20] :day]]
+            :order-by     [[:desc [:field-id 10]]]}}
+  (normalize/normalize {:type  "query"
+                        :query {"source_table" 10
+                                "AGGREGATION"  "ROWS"
+                                "breakout"     [10 20]
+                                "filter"       ["and" ["=" 10 ["datetime-field" 20 "as" "day"]]]
+                                "order-by"     [[10 "desc"]]}}))
+
+;; let's try doing the full normalization on a native query w/ params
+(expect
+  {:native     {:query         "SELECT * FROM CATEGORIES WHERE {{names_list}}"
+                :template-tags {"names_list" {:name         "names_list"
+                                              :display-name "Names List"
+                                              :type         :dimension
+                                              :dimension    [:field-id 49]}}}
+   :parameters [{:type   :text
+                 :target [:dimension [:template-tag "names_list"]]
+                 :value  ["BBQ" "Bakery" "Bar"]}]}
+  (normalize/normalize
+   {:native     {:query          "SELECT * FROM CATEGORIES WHERE {{names_list}}"
+                 "template_tags" {:names_list {:name         "names_list"
+                                               :display_name "Names List"
+                                               :type         "dimension"
+                                               :dimension    ["field-id" 49]}}}
+    :parameters [{:type   "text"
+                  :target ["dimension" ["template-tag" "names_list"]]
+                  :value  ["BBQ" "Bakery" "Bar"]}]}))
+
+;; let's try normalizing a big query with SEGMENTS
+(expect
+  {:database 1
+   :type     :query
+   :query    {:source-table 2
+              :filter       [:and
+                             [:= [:field-id 3] "Toucan-friendly"]
+                             [:segment 4]
+                             [:segment 5]]}}
+  (normalize/normalize
+   {:database 1
+    :type     :query
+    :query    {:source-table 2
+               :filter       ["AND"
+                              ["=" 3 "Toucan-friendly"]
+                              ["SEGMENT" 4]
+                              ["SEGMENT" 5]]}}))
+
+;; make sure source queries get normalized properly!
+(expect
+  {:database 4
+   :type     :query
+   :query    {:source-query {:native        "SELECT * FROM PRODUCTS WHERE CATEGORY = {{category}} LIMIT 10",
+                             :template-tags {"category" {:name         "category"
+                                                         :display-name "Category"
+                                                         :type         :text
+                                                         :required     true
+                                                         :default      "Widget"}}}}}
+  (normalize/normalize
+   {:database 4
+    :type     :query
+    :query    {"source_query" {:native         "SELECT * FROM PRODUCTS WHERE CATEGORY = {{category}} LIMIT 10",
+                               "template_tags" {:category {:name         "category"
+                                                           :display-name "Category"
+                                                           :type         "text"
+                                                           :required     true
+                                                           :default      "Widget"}}}}}))
+
+(expect
+  {:database 4,
+   :type     :query,
+   :query    {:source-query {:source-table 1}}}
+  (normalize/normalize
+   {:database 4
+    :type     :query
+    :query    {"source_query" {"source_table" 1, "aggregation" "rows"}}}))
diff --git a/test/metabase/mbql/util_test.clj b/test/metabase/mbql/util_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..efdc60aa38b880b4d41a39b98e40f3c4e0b5721d
--- /dev/null
+++ b/test/metabase/mbql/util_test.clj
@@ -0,0 +1,47 @@
+(ns metabase.mbql.util-test
+  (:require [expectations :refer :all]
+            [metabase.mbql.util :as mbql.u]))
+
+;; can we use `clause-instances` to find the instances of a clause?
+(expect
+  [[:field-id 10]
+   [:field-id 20]]
+  (mbql.u/clause-instances :field-id {:query {:filter [:=
+                                                       [:field-id 10]
+                                                       [:field-id 20]]}}))
+
+(expect
+  [[:field-id 10]
+   [:field-id 20]]
+  (mbql.u/clause-instances #{:field-id :+ :-}
+    {:query {:filter [:=
+                      [:field-id 10]
+                      [:field-id 20]]}}))
+
+;; can `simplify-compound-filter` fix `and` or `or` with only one arg?
+(expect
+  [:= [:field-id 1] 2]
+  (mbql.u/simplify-compound-filter [:and [:= [:field-id 1] 2]]))
+
+;; can `simplify-compound-filter` unnest nested `and`s or `or`s?
+(expect
+  [:and
+   [:= [:field-id 1] 2]
+   [:= [:field-id 3] 4]
+   [:= [:field-id 5] 6]]
+  (mbql.u/simplify-compound-filter [:and
+                                    [:= [:field-id 1] 2]
+                                    [:and
+                                     [:= [:field-id 3] 4]
+                                     [:and
+                                      [:= [:field-id 5] 6]]]]))
+
+;; can `simplify-compound-filter` remove duplicates?
+(expect
+  [:and [:= [:field-id 1] 2] [:= [:field-id 3] 4]]
+  (mbql.u/simplify-compound-filter [:and [:= [:field-id 1] 2] [:= [:field-id 3] 4] [:= [:field-id 1] 2]]))
+
+;; can `simplify-compound-filter` eliminate `not` inside a `not`?
+(expect
+  [:= [:field-id 1] 2]
+  (mbql.u/simplify-compound-filter [:not [:not [:= [:field-id 1] 2]]]))
diff --git a/test/metabase/metabot_test.clj b/test/metabase/metabot_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..bf6c834fec5e9f9de9e532b405228b4ade4a5f13
--- /dev/null
+++ b/test/metabase/metabot_test.clj
@@ -0,0 +1,46 @@
+(ns metabase.metabot-test
+  (:require [expectations :refer :all]
+            [metabase.metabot :as metabot]
+            [metabase.models.card :refer [Card]]
+            [metabase.util.date :as du]
+            [toucan.util.test :as tt]))
+
+;; test that if we're not the MetaBot based on Settings, our function to check is working correctly
+(expect
+  false
+  (do
+    (#'metabot/metabot-instance-uuid nil)
+    (#'metabot/metabot-instance-last-checkin nil)
+    (#'metabot/am-i-the-metabot?)))
+
+;; test that if nobody is currently the MetaBot, we will become the MetaBot
+(expect
+  (do
+    (#'metabot/metabot-instance-uuid nil)
+    (#'metabot/metabot-instance-last-checkin nil)
+    (#'metabot/check-and-update-instance-status!)
+    (#'metabot/am-i-the-metabot?)))
+
+;; test that if nobody has checked in as MetaBot for a while, we will become the MetaBot
+(expect
+  (do
+    (#'metabot/metabot-instance-uuid (str (java.util.UUID/randomUUID)))
+    (#'metabot/metabot-instance-last-checkin (du/relative-date :minute -10 (#'metabot/current-timestamp-from-db)))
+    (#'metabot/check-and-update-instance-status!)
+    (#'metabot/am-i-the-metabot?)))
+
+;; check that if another instance has checked in recently, we will *not* become the MetaBot
+(expect
+  false
+  (do
+    (#'metabot/metabot-instance-uuid (str (java.util.UUID/randomUUID)))
+    (#'metabot/metabot-instance-last-checkin (#'metabot/current-timestamp-from-db))
+    (#'metabot/check-and-update-instance-status!)
+    (#'metabot/am-i-the-metabot?)))
+
+;; Check that `metabot/list` returns a string with card information and passes the permissions checks
+(expect
+  #"2 most recent cards"
+  (tt/with-temp* [Card [_]
+                  Card [_]]
+    (metabot/list)))
diff --git a/test/metabase/models/card_test.clj b/test/metabase/models/card_test.clj
index 9edb80f9eaed0cb86fca42aa60afb7af454355f6..97fa45d62a89151a46c24f039568f9703cc1b935 100644
--- a/test/metabase/models/card_test.clj
+++ b/test/metabase/models/card_test.clj
@@ -30,25 +30,31 @@
 
 (expect
   {:Segment #{2 3}
-   :Metric  nil}
+   :Metric  #{}}
   (card-dependencies
-   {:dataset_query {:type :query
-                    :query {:aggregation ["rows"]
-                            :filter      ["AND" [">" 4 "2014-10-19"] ["=" 5 "yes"] ["SEGMENT" 2] ["SEGMENT" 3]]}}}))
+   {:dataset_query {:type  :query
+                    :query {:filter [:and
+                                     [:> [:field-id 4] "2014-10-19"]
+                                     [:= [:field-id 5] "yes"]
+                                     [:segment 2]
+                                     [:segment 3]]}}}))
 
 (expect
   {:Segment #{1}
-   :Metric #{7}}
+   :Metric  #{7}}
   (card-dependencies
-   {:dataset_query {:type :query
-                    :query {:aggregation ["METRIC" 7]
-                            :filter      ["AND" [">" 4 "2014-10-19"] ["=" 5 "yes"] ["OR" ["SEGMENT" 1] ["!=" 5 "5"]]]}}}))
+   {:dataset_query {:type  :query
+                    :query {:aggregation [:metric 7]
+                            :filter      [:and
+                                          [:> [:field-id 4] "2014-10-19"]
+                                          [:= [:field-id 5] "yes"]
+                                          [:or [:segment 1] [:!= [:field-id 5] "5"]]]}}}))
 
 (expect
-  {:Segment nil
-   :Metric  nil}
+  {:Segment #{}
+   :Metric  #{}}
   (card-dependencies
-   {:dataset_query {:type :query
+   {:dataset_query {:type  :query
                     :query {:aggregation nil
                             :filter      nil}}}))
 
@@ -94,7 +100,6 @@
        (into {} (db/select-one [Card :name :database_id] :id id)))]))
 
 
-
 ;;; ------------------------------------------ Circular Reference Detection ------------------------------------------
 
 (defn- card-with-source-table
diff --git a/test/metabase/models/collection_test.clj b/test/metabase/models/collection_test.clj
index 766410e9d8239b9993d279291410593a09bfbb59..4c159ffdb21bdf655a1c9bd22dcafa348c8a75fb 100644
--- a/test/metabase/models/collection_test.clj
+++ b/test/metabase/models/collection_test.clj
@@ -2,19 +2,22 @@
   (:refer-clojure :exclude [ancestors descendants])
   (:require [clojure.string :as str]
             [expectations :refer :all]
+            [medley.core :as m]
             [metabase.api.common :refer [*current-user-id* *current-user-permissions-set*]]
             [metabase.models
              [card :refer [Card]]
              [collection :as collection :refer [Collection]]
              [dashboard :refer [Dashboard]]
-             [permissions :refer [Permissions] :as perms]
+             [permissions :as perms :refer [Permissions]]
              [permissions-group :as group :refer [PermissionsGroup]]
              [pulse :refer [Pulse]]
              [user :refer [User]]]
             [metabase.test.data.users :as test-users]
             [metabase.test.util :as tu]
             [metabase.util :as u]
-            [toucan.db :as db]
+            [toucan
+             [db :as db]
+             [hydrate :refer [hydrate]]]
             [toucan.util.test :as tt]))
 
 (defn force-create-personal-collections!
@@ -28,6 +31,15 @@
 (defn- lucky-collection-children-location []
   (collection/children-location (collection/user->personal-collection (test-users/user->id :lucky))))
 
+(defn- replace-collection-ids
+  "In Collection perms `graph`, replace instances of the ID of `collection-or-id` with `:COLLECTION`, making it possible
+  to write tests that don't need to know its actual numeric ID."
+  [collection-or-id graph]
+  (update graph :groups (partial m/map-vals (partial m/map-keys (fn [collection-id]
+                                                                  (if (= collection-id (u/get-id collection-or-id))
+                                                                    :COLLECTION
+                                                                    collection-id))))))
+
 ;; test that we can create a new Collection with valid inputs
 (expect
   {:name              "My Favorite Cards"
@@ -128,35 +140,40 @@
    :groups   {(u/get-id (group/all-users)) {:root :none}
               (u/get-id (group/metabot))   {:root :none}
               (u/get-id (group/admin))     {:root :write}}}
-  (graph :clear-revisions? true))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (graph :clear-revisions? true)))
 
 ;; Creating a new Collection shouldn't give perms to anyone but admins
-(tt/expect-with-temp [Collection [collection]]
+(expect
   {:revision 0
-   :groups   {(u/get-id (group/all-users)) {:root :none,  (u/get-id collection) :none}
-              (u/get-id (group/metabot))   {:root :none,  (u/get-id collection) :none}
-              (u/get-id (group/admin))     {:root :write, (u/get-id collection) :write}}}
-  (graph :clear-revisions? true))
+   :groups   {(u/get-id (group/all-users)) {:root :none,  :COLLECTION :none}
+              (u/get-id (group/metabot))   {:root :none,  :COLLECTION :none}
+              (u/get-id (group/admin))     {:root :write, :COLLECTION :write}}}
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [collection]
+      (replace-collection-ids collection (graph :clear-revisions? true)))))
 
 ;; make sure read perms show up correctly
-(tt/expect-with-temp [Collection [collection]]
+(expect
   {:revision 0
-   :groups   {(u/get-id (group/all-users)) {:root :none,  (u/get-id collection) :read}
-              (u/get-id (group/metabot))   {:root :none,  (u/get-id collection) :none}
-              (u/get-id (group/admin))     {:root :write, (u/get-id collection) :write}}}
-  (do
-    (perms/grant-collection-read-permissions! (group/all-users) collection)
-    (graph :clear-revisions? true)))
+   :groups   {(u/get-id (group/all-users)) {:root :none,  :COLLECTION :read}
+              (u/get-id (group/metabot))   {:root :none,  :COLLECTION :none}
+              (u/get-id (group/admin))     {:root :write, :COLLECTION :write}}}
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [collection]
+      (perms/grant-collection-read-permissions! (group/all-users) collection)
+      (replace-collection-ids collection (graph :clear-revisions? true)))))
 
 ;; make sure we can grant write perms for new collections (!)
-(tt/expect-with-temp [Collection [collection]]
+(expect
   {:revision 0
-   :groups   {(u/get-id (group/all-users)) {:root :none,  (u/get-id collection) :write}
-              (u/get-id (group/metabot))   {:root :none,  (u/get-id collection) :none}
-              (u/get-id (group/admin))     {:root :write, (u/get-id collection) :write}}}
-  (do
-    (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
-    (graph :clear-revisions? true)))
+   :groups   {(u/get-id (group/all-users)) {:root :none,  :COLLECTION :write}
+              (u/get-id (group/metabot))   {:root :none,  :COLLECTION :none}
+              (u/get-id (group/admin))     {:root :write, :COLLECTION :write}}}
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [collection]
+      (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
+      (replace-collection-ids collection (graph :clear-revisions? true)))))
 
 ;; make sure a non-magical group will show up
 (tt/expect-with-temp [PermissionsGroup [new-group]]
@@ -165,7 +182,8 @@
               (u/get-id (group/metabot))   {:root :none}
               (u/get-id (group/admin))     {:root :write}
               (u/get-id new-group)         {:root :none}}}
-  (graph :clear-revisions? true))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (graph :clear-revisions? true)))
 
 ;; How abut *read* permissions for the Root Collection?
 (tt/expect-with-temp [PermissionsGroup [new-group]]
@@ -174,7 +192,7 @@
               (u/get-id (group/metabot))   {:root :none}
               (u/get-id (group/admin))     {:root :write}
               (u/get-id new-group)         {:root :read}}}
-  (do
+  (tu/with-non-admin-groups-no-root-collection-perms
     (perms/grant-collection-read-permissions! new-group collection/root-collection)
     (graph :clear-revisions? true)))
 
@@ -185,7 +203,7 @@
               (u/get-id (group/metabot))   {:root :none}
               (u/get-id (group/admin))     {:root :write}
               (u/get-id new-group)         {:root :write}}}
-  (do
+  (tu/with-non-admin-groups-no-root-collection-perms
     (perms/grant-collection-readwrite-permissions! new-group collection/root-collection)
     (graph :clear-revisions? true)))
 
@@ -197,46 +215,53 @@
               (u/get-id (group/metabot))   {:root :none}
               (u/get-id (group/admin))     {:root :write}}}
   ;; need to bind *current-user-id* or the Revision won't get updated
-  (binding [*current-user-id* (test-users/user->id :crowberto)]
-    (collection/update-graph! (graph :clear-revisions? true))
-    (graph)))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (binding [*current-user-id* (test-users/user->id :crowberto)]
+      (collection/update-graph! (graph :clear-revisions? true))
+      (graph))))
 
 ;; Can we give someone read perms via the graph?
-(tt/expect-with-temp [Collection [collection]]
+(expect
   {:revision 1
-   :groups   {(u/get-id (group/all-users)) {:root :none,  (u/get-id collection) :read}
-              (u/get-id (group/metabot))   {:root :none,  (u/get-id collection) :none}
-              (u/get-id (group/admin))     {:root :write, (u/get-id collection) :write}}}
-  (binding [*current-user-id* (test-users/user->id :crowberto)]
-    (collection/update-graph! (assoc-in (graph :clear-revisions? true)
-                                        [:groups (u/get-id (group/all-users)) (u/get-id collection)]
-                                        :read))
-    (graph)))
+   :groups   {(u/get-id (group/all-users)) {:root :none,  :COLLECTION :read}
+              (u/get-id (group/metabot))   {:root :none,  :COLLECTION :none}
+              (u/get-id (group/admin))     {:root :write, :COLLECTION :write}}}
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [collection]
+      (binding [*current-user-id* (test-users/user->id :crowberto)]
+        (collection/update-graph! (assoc-in (graph :clear-revisions? true)
+                                            [:groups (u/get-id (group/all-users)) (u/get-id collection)]
+                                            :read))
+        (replace-collection-ids collection (graph))))))
 
 ;; can we give them *write* perms?
-(tt/expect-with-temp [Collection [collection]]
-  {:revision 1
-   :groups   {(u/get-id (group/all-users)) {:root :none,  (u/get-id collection) :write}
-              (u/get-id (group/metabot))   {:root :none,  (u/get-id collection) :none}
-              (u/get-id (group/admin))     {:root :write, (u/get-id collection) :write}}}
-  (binding [*current-user-id* (test-users/user->id :crowberto)]
-    (collection/update-graph! (assoc-in (graph :clear-revisions? true)
-                                        [:groups (u/get-id (group/all-users)) (u/get-id collection)]
-                                        :write))
-    (graph)))
+(expect
+ {:revision 1
+  :groups   {(u/get-id (group/all-users)) {:root :none,  :COLLECTION :write}
+             (u/get-id (group/metabot))   {:root :none,  :COLLECTION :none}
+             (u/get-id (group/admin))     {:root :write, :COLLECTION :write}}}
+ (tu/with-non-admin-groups-no-root-collection-perms
+   (tt/with-temp Collection [collection]
+     (binding [*current-user-id* (test-users/user->id :crowberto)]
+       (collection/update-graph! (assoc-in (graph :clear-revisions? true)
+                                           [:groups (u/get-id (group/all-users)) (u/get-id collection)]
+                                           :write))
+       (replace-collection-ids collection (graph))))))
 
 ;; can we *revoke* perms?
-(tt/expect-with-temp [Collection [collection]]
+(expect
   {:revision 1
-   :groups   {(u/get-id (group/all-users)) {:root :none,  (u/get-id collection) :none}
-              (u/get-id (group/metabot))   {:root :none,  (u/get-id collection) :none}
-              (u/get-id (group/admin))     {:root :write, (u/get-id collection) :write}}}
-  (binding [*current-user-id* (test-users/user->id :crowberto)]
-    (perms/grant-collection-read-permissions! (group/all-users) collection)
-    (collection/update-graph! (assoc-in (graph :clear-revisions? true)
-                                        [:groups (u/get-id (group/all-users)) (u/get-id collection)]
-                                        :none))
-    (graph)))
+   :groups   {(u/get-id (group/all-users)) {:root :none,  :COLLECTION :none}
+              (u/get-id (group/metabot))   {:root :none,  :COLLECTION :none}
+              (u/get-id (group/admin))     {:root :write, :COLLECTION :write}}}
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [collection]
+      (binding [*current-user-id* (test-users/user->id :crowberto)]
+        (perms/grant-collection-read-permissions! (group/all-users) collection)
+        (collection/update-graph! (assoc-in (graph :clear-revisions? true)
+                                            [:groups (u/get-id (group/all-users)) (u/get-id collection)]
+                                            :none))
+        (replace-collection-ids collection (graph))))))
 
 ;; How abut *read* permissions for the Root Collection?
 (tt/expect-with-temp [PermissionsGroup [new-group]]
@@ -245,11 +270,12 @@
               (u/get-id (group/metabot))   {:root :none}
               (u/get-id (group/admin))     {:root :write}
               (u/get-id new-group)         {:root :read}}}
-  (binding [*current-user-id* (test-users/user->id :crowberto)]
-    (collection/update-graph! (assoc-in (graph :clear-revisions? true)
-                                        [:groups (u/get-id new-group) :root]
-                                        :read))
-    (graph)))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (binding [*current-user-id* (test-users/user->id :crowberto)]
+      (collection/update-graph! (assoc-in (graph :clear-revisions? true)
+                                          [:groups (u/get-id new-group) :root]
+                                          :read))
+      (graph))))
 
 ;; How about granting *write* permissions for the Root Collection?
 (tt/expect-with-temp [PermissionsGroup [new-group]]
@@ -258,11 +284,12 @@
               (u/get-id (group/metabot))   {:root :none}
               (u/get-id (group/admin))     {:root :write}
               (u/get-id new-group)         {:root :write}}}
-  (binding [*current-user-id* (test-users/user->id :crowberto)]
-    (collection/update-graph! (assoc-in (graph :clear-revisions? true)
-                                        [:groups (u/get-id new-group) :root]
-                                        :write))
-    (graph)))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (binding [*current-user-id* (test-users/user->id :crowberto)]
+      (collection/update-graph! (assoc-in (graph :clear-revisions? true)
+                                          [:groups (u/get-id new-group) :root]
+                                          :write))
+      (graph))))
 
 ;; can we *revoke* RootCollection perms?
 (tt/expect-with-temp [PermissionsGroup [new-group]]
@@ -271,12 +298,13 @@
               (u/get-id (group/metabot))   {:root :none}
               (u/get-id (group/admin))     {:root :write}
               (u/get-id new-group)         {:root :none}}}
-  (binding [*current-user-id* (test-users/user->id :crowberto)]
-    (perms/grant-collection-readwrite-permissions! new-group collection/root-collection)
-    (collection/update-graph! (assoc-in (graph :clear-revisions? true)
-                                        [:groups (u/get-id new-group) :root]
-                                        :none))
-    (graph)))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (binding [*current-user-id* (test-users/user->id :crowberto)]
+      (perms/grant-collection-readwrite-permissions! new-group collection/root-collection)
+      (collection/update-graph! (assoc-in (graph :clear-revisions? true)
+                                          [:groups (u/get-id new-group) :root]
+                                          :none))
+      (graph))))
 
 ;; Make sure that personal Collections *do not* appear in the Collections graph
 (expect
@@ -284,7 +312,7 @@
    :groups   {(u/get-id (group/all-users)) {:root :none}
               (u/get-id (group/metabot))   {:root :none}
               (u/get-id (group/admin))     {:root :write}}}
-  (do
+  (tu/with-non-admin-groups-no-root-collection-perms
     (force-create-personal-collections!)
     (graph :clear-revisions? true)))
 
@@ -302,7 +330,7 @@
    :groups   {(u/get-id (group/all-users)) {:root :none}
               (u/get-id (group/metabot))   {:root :none}
               (u/get-id (group/admin))     {:root :write}}}
-  (do
+  (tu/with-non-admin-groups-no-root-collection-perms
     (u/ignore-exceptions
       (let [lucky-personal-collection-id (u/get-id (collection/user->personal-collection (test-users/user->id :lucky)))]
         (collection/update-graph! (assoc-in (graph :clear-revisions? true)
@@ -316,8 +344,9 @@
    :groups   {(u/get-id (group/all-users)) {:root :none}
               (u/get-id (group/metabot))   {:root :none}
               (u/get-id (group/admin))     {:root :write}}}
-  (tt/with-temp Collection [_ {:location (lucky-collection-children-location)}]
-    (graph)))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp Collection [_ {:location (lucky-collection-children-location)}]
+      (graph))))
 
 ;; ...and that you can't be sneaky and try to edit them either...
 (expect
@@ -337,14 +366,15 @@
 ;;; +----------------------------------------------------------------------------------------------------------------+
 
 (defn do-with-collection-hierarchy [a-fn]
-  (tt/with-temp* [Collection [a {:name "A"}]
-                  Collection [b {:name "B", :location (collection/location-path a)}]
-                  Collection [c {:name "C", :location (collection/location-path a)}]
-                  Collection [d {:name "D", :location (collection/location-path a c)}]
-                  Collection [e {:name "E", :location (collection/location-path a c d)}]
-                  Collection [f {:name "F", :location (collection/location-path a c)}]
-                  Collection [g {:name "G", :location (collection/location-path a c f)}]]
-    (a-fn {:a a, :b b, :c c, :d d, :e e, :f f, :g g})))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [a {:name "A"}]
+                    Collection [b {:name "B", :location (collection/location-path a)}]
+                    Collection [c {:name "C", :location (collection/location-path a)}]
+                    Collection [d {:name "D", :location (collection/location-path a c)}]
+                    Collection [e {:name "E", :location (collection/location-path a c d)}]
+                    Collection [f {:name "F", :location (collection/location-path a c)}]
+                    Collection [g {:name "G", :location (collection/location-path a c f)}]]
+      (a-fn {:a a, :b b, :c c, :d d, :e e, :f f, :g g}))))
 
 (defmacro with-collection-hierarchy
   "Run `body` with a hierarchy of Collections that looks like:
@@ -852,15 +882,18 @@
 ;;           |
 ;;           +-> F -> G
 
-(defn- perms-path-ids->names
-  "Given a set of permissions and the `collections` map returned by the `with-collection-hierarchy` macro above, replace
-  the numeric IDs in the permissions paths with corresponding Collection names, making our tests easier to read."
+(defn perms-path-ids->names
+  "Given a set of permissions and `collections` (the map returned by the `with-collection-hierarchy` macro above, or a
+  sequential collection), replace the numeric IDs in the permissions paths with corresponding Collection names, making
+  our tests easier to read."
   [collections perms-set]
   ;; first build a function that will replace any instances of numeric IDs with their respective names
   ;; e.g. /123/ would become something like /A/
   ;; Do this by composing together a series of functions that will handle one string replacement for each ID + name
   ;; pair
-  (let [replace-ids-with-names (reduce comp (for [{:keys [id name]} (vals collections)]
+  (let [replace-ids-with-names (reduce comp (for [{:keys [id name]} (if (sequential? collections)
+                                                                      collections
+                                                                      (vals collections))]
                                               #(str/replace % (re-pattern (format "/%d/" id)) (str "/" name "/"))))]
     (set (for [perms-path perms-set]
            (replace-ids-with-names perms-path)))))
@@ -1242,23 +1275,21 @@
       (db/update! Collection (u/get-id c) :archived true)
       (db/select-one-field :archived Dashboard :id (u/get-id dashboard)))))
 
-;; Test that archiving *deletes* Pulses (Pulses cannot currently be archived)
-;; Pulse is in E; archiving E should cause Pulse to get deleted
+;; Test that archiving Collections applies to Pulses
+;; Pulse is in E; archiving E should cause Pulse to be archived
 (expect
-  false
   (with-collection-hierarchy [{:keys [e], :as collections}]
     (tt/with-temp Pulse [pulse {:collection_id (u/get-id e)}]
       (db/update! Collection (u/get-id e) :archived true)
-      (db/exists? Pulse :id (u/get-id pulse)))))
+      (db/select-one-field :archived Pulse :id (u/get-id pulse)))))
 
-;; Test that archiving *deletes* Pulses belonging to descendant Collections
+;; Test that archiving works on Pulses belonging to descendant Collections
 ;; Pulse is in E, a descendant of C; archiving C should cause Pulse to be archived
 (expect
-  false
   (with-collection-hierarchy [{:keys [c e], :as collections}]
     (tt/with-temp Pulse [pulse {:collection_id (u/get-id e)}]
       (db/update! Collection (u/get-id c) :archived true)
-      (db/exists? Pulse :id (u/get-id pulse)))))
+      (db/select-one-field :archived Pulse :id (u/get-id pulse)))))
 
 ;; Test that unarchiving applies to Cards
 ;; Card is in E; unarchiving E should cause Card to be unarchived
@@ -1460,6 +1491,13 @@
     (let [personal-collection (collection/user->personal-collection my-cool-user)]
       (db/update! Collection (u/get-id personal-collection) :personal_owner_id (test-users/user->id :crowberto)))))
 
+;; Does hydrating `:personal_collection_id` force creation of Personal Collections?
+(expect
+  (tt/with-temp User [temp-user]
+    (-> (hydrate temp-user :personal_collection_id)
+        :personal_collection_id
+        integer?)))
+
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                    Moving Collections "Across the Boundary"                                    |
diff --git a/test/metabase/models/dashboard_test.clj b/test/metabase/models/dashboard_test.clj
index f3548eb6f6c6cee6f1b7dbccb9601acd5856708f..443c5601aeaca9e21fd59c6531e356578eb8ecf3 100644
--- a/test/metabase/models/dashboard_test.clj
+++ b/test/metabase/models/dashboard_test.clj
@@ -183,15 +183,16 @@
 ;;; +----------------------------------------------------------------------------------------------------------------+
 
 (defn do-with-dash-in-collection [f]
-  (tt/with-temp* [Collection    [collection]
-                  Dashboard     [dash  {:collection_id (u/get-id collection)}]
-                  Database      [db    {:engine :h2}]
-                  Table         [table {:db_id (u/get-id db)}]
-                  Card          [card  {:dataset_query {:database (u/get-id db)
-                                                        :type     :query
-                                                        :query    {:source-table (u/get-id table)}}}]
-                  DashboardCard [_ {:dashboard_id (u/get-id dash), :card_id (u/get-id card)}]]
-    (f db collection dash)))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection    [collection]
+                    Dashboard     [dash  {:collection_id (u/get-id collection)}]
+                    Database      [db    {:engine :h2}]
+                    Table         [table {:db_id (u/get-id db)}]
+                    Card          [card  {:dataset_query {:database (u/get-id db)
+                                                          :type     :query
+                                                          :query    {:source-table (u/get-id table)}}}]
+                    DashboardCard [_ {:dashboard_id (u/get-id dash), :card_id (u/get-id card)}]]
+      (f db collection dash))))
 
 (defmacro with-dash-in-collection
   "Execute `body` with a Dashboard in a Collection. Dashboard will contain one Card in a Database."
@@ -229,14 +230,16 @@
 
 ;; test that we save a transient dashboard
 (expect
-  8
   (tu/with-model-cleanup ['Card 'Dashboard 'DashboardCard 'Collection]
     (binding [api/*current-user-id*              (users/user->id :rasta)
               api/*current-user-permissions-set* (-> :rasta
                                                      users/user->id
                                                      user/permissions-set
                                                      atom)]
-      (->> (magic/automagic-analysis (Table (id :venues)) {})
-           save-transient-dashboard!
-           :id
-           (db/count 'DashboardCard :dashboard_id)))))
+      (let [dashboard (magic/automagic-analysis (Table (id :venues)) {})
+            rastas-personal-collection (db/select-one-field :id 'Collection
+                                         :personal_owner_id api/*current-user-id*)]
+        (->> (save-transient-dashboard! dashboard rastas-personal-collection)
+             :id
+             (db/count 'DashboardCard :dashboard_id)
+             (= (-> dashboard :ordered_cards count)))))))
diff --git a/test/metabase/models/metric_test.clj b/test/metabase/models/metric_test.clj
index dc9ad43db67700747c2899de674aa1e80f990363..6e0859dccac03202dcec7110729d069de57c24b2 100644
--- a/test/metabase/models/metric_test.clj
+++ b/test/metabase/models/metric_test.clj
@@ -5,7 +5,7 @@
              [metric :as metric :refer :all]
              [table :refer [Table]]]
             [metabase.test.data :refer :all]
-            [metabase.test.data.users :refer :all]
+            [metabase.test.data.users :refer [user->id fetch-user]]
             [metabase.util :as u]
             [toucan.util.test :as tt]))
 
@@ -16,7 +16,7 @@
    :caveats                 nil
    :points_of_interest      nil
    :archived                false
-   :definition              {}})
+   :definition              nil})
 
 (defn- user-details
   [username]
@@ -42,10 +42,10 @@
          {:creator_id (user->id :rasta)
           :creator    (user-details :rasta)
           :name       "I only want *these* things"
-          :definition {:clause ["a" "b"]}})
+          :definition {:filter [:= [:field-id 1] 2]}})
   (tt/with-temp* [Database [{database-id :id}]
                   Table    [{:keys [id]}      {:db_id database-id}]]
-    (create-metric-then-select! id "I only want *these* things" nil (user->id :rasta) {:clause ["a" "b"]})))
+    (create-metric-then-select! id "I only want *these* things" nil (user->id :rasta) {:filter [:= [:field-id 1] 2]})))
 
 
 ;; exists?
@@ -55,8 +55,7 @@
   (tt/with-temp* [Database [{database-id :id}]
                   Table    [{table-id :id}    {:db_id database-id}]
                   Metric   [{metric-id :id}   {:table_id   table-id
-                                               :definition {:database 45
-                                                            :query    {:filter ["yay"]}}}]]
+                                               :definition {:filter [:= [:field-id 1] 2]}}]]
     [(metric/exists? metric-id)
      (metric/exists? Integer/MAX_VALUE)])) ; a Metric that definitely doesn't exist
 
@@ -68,13 +67,11 @@
           :creator     (user-details :rasta)
           :name        "Toucans in the rainforest"
           :description "Lookin' for a blueberry"
-          :definition  {:database 45
-                        :query    {:filter ["yay"]}}})
+          :definition  {:filter [:= [:field-id 1] 2]}})
   (tt/with-temp* [Database [{database-id :id}]
                   Table    [{table-id :id}    {:db_id database-id}]
                   Metric   [{metric-id :id}   {:table_id    table-id
-                                               :definition  {:database 45
-                                                             :query    {:filter ["yay"]}}}]]
+                                               :definition  {:filter [:= [:field-id 1] 2]}}]]
     (let [{:keys [creator] :as metric} (retrieve-metric metric-id)]
       (update (dissoc metric :id :table_id :created_at :updated_at)
               :creator (u/rpartial dissoc :date_joined :last_login)))))
@@ -110,8 +107,7 @@
          {:creator_id (user->id :rasta)
           :creator    (user-details :rasta)
           :name       "Costa Rica"
-          :definition {:database 2
-                       :query    {:filter ["not" "the toucans you're looking for"]}}})
+          :definition {:filter [:not [:= [:field-id 1] "toucans"]]}})
   (tt/with-temp* [Database [{database-id :id}]
                   Table  [{table-id :id}  {:db_id database-id}]
                   Metric [{metric-id :id} {:table_id table-id}]]
@@ -124,8 +120,7 @@
                                  :points_of_interest      nil
                                  :creator_id              (user->id :crowberto)
                                  :table_id                456
-                                 :definition              {:database 2
-                                                           :query    {:filter ["not" "the toucans you're looking for"]}}
+                                 :definition              {:filter [:not [:= [:field-id 1] "toucans"]]}
                                  :revision_message        "Just horsing around"})))
 
 ;; delete-metric!
@@ -153,13 +148,13 @@
           :creator_id  (user->id :rasta)
           :name        "Toucans in the rainforest"
           :description "Lookin' for a blueberry"
-          :definition  {:aggregation ["count"]
-                        :filter      ["AND" [">" 4 "2014-10-19"]]}})
+          :definition  {:aggregation [[:count]]
+                        :filter      [:> [:field-id 4] "2014-10-19"]}})
   (tt/with-temp* [Database [{database-id :id}]
                   Table    [{table-id :id} {:db_id database-id}]
                   Metric   [metric         {:table_id   table-id
-                                            :definition {:aggregation ["count"]
-                                                         :filter      ["AND" [">" 4 "2014-10-19"]]}}]]
+                                            :definition {:aggregation [[:count]]
+                                                         :filter      [:and [:> [:field-id 4] "2014-10-19"]]}}]]
     (-> (#'metric/serialize-metric Metric (:id metric) metric)
         (update :id boolean)
         (update :table_id boolean))))
@@ -167,8 +162,8 @@
 ;; #'metric/diff-metrics
 
 (expect
-  {:definition  {:before {:filter ["AND" [">" 4 "2014-10-19"]]}
-                 :after  {:filter ["AND" ["BETWEEN" 4 "2014-07-01" "2014-10-19"]]}}
+  {:definition  {:before {:filter [:> [:field-id 4] "2014-10-19"]}
+                 :after  {:filter [:between [:field-id 4] "2014-07-01" "2014-10-19"]}}
    :description {:before "Lookin' for a blueberry"
                  :after  "BBB"}
    :name        {:before "Toucans in the rainforest"
@@ -176,11 +171,11 @@
   (tt/with-temp* [Database [{database-id :id}]
                   Table    [{table-id :id} {:db_id database-id}]
                   Metric   [metric         {:table_id   table-id
-                                            :definition {:filter ["AND" [">" 4 "2014-10-19"]]}}]]
+                                            :definition {:filter [:and [:> [:field-id 4] "2014-10-19"]]}}]]
     (#'metric/diff-metrics Metric metric (assoc metric
                                            :name        "Something else"
                                            :description "BBB"
-                                           :definition  {:filter ["AND" ["BETWEEN" 4 "2014-07-01" "2014-10-19"]]}))))
+                                           :definition  {:filter [:between [:field-id 4] "2014-07-01" "2014-10-19"]}))))
 
 ;; test case where definition doesn't change
 (expect
@@ -189,33 +184,33 @@
   (#'metric/diff-metrics Metric
                          {:name        "A"
                           :description "Unchanged"
-                          :definition  {:filter ["AND" [">" 4 "2014-10-19"]]}}
+                          :definition  {:filter [:and [:> 4 "2014-10-19"]]}}
                          {:name        "B"
                           :description "Unchanged"
-                          :definition  {:filter ["AND" [">" 4 "2014-10-19"]]}}))
+                          :definition  {:filter [:and [:> 4 "2014-10-19"]]}}))
 
 ;; first version, so comparing against nil
 (expect
   {:name        {:after  "A"}
    :description {:after "Unchanged"}
-   :definition  {:after {:filter ["AND" [">" 4 "2014-10-19"]]}}}
+   :definition  {:after {:filter [:and [:> 4 "2014-10-19"]]}}}
   (#'metric/diff-metrics Metric
                          nil
                          {:name        "A"
                           :description "Unchanged"
-                          :definition  {:filter ["AND" [">" 4 "2014-10-19"]]}}))
+                          :definition  {:filter [:and [:> 4 "2014-10-19"]]}}))
 
 ;; removals only
 (expect
-  {:definition  {:before {:filter ["AND" [">" 4 "2014-10-19"] ["=" 5 "yes"]]}
-                 :after  {:filter ["AND" [">" 4 "2014-10-19"]]}}}
+  {:definition  {:before {:filter [:and [:> 4 "2014-10-19"] [:= 5 "yes"]]}
+                 :after  {:filter [:and [:> 4 "2014-10-19"]]}}}
   (#'metric/diff-metrics Metric
                          {:name        "A"
                           :description "Unchanged"
-                          :definition  {:filter ["AND" [">" 4 "2014-10-19"] ["=" 5 "yes"]]}}
+                          :definition  {:filter [:and [:> 4 "2014-10-19"] [:= 5 "yes"]]}}
                          {:name        "A"
                           :description "Unchanged"
-                          :definition  {:filter ["AND" [">" 4 "2014-10-19"]]}}))
+                          :definition  {:filter [:and [:> 4 "2014-10-19"]]}}))
 
 
 
@@ -223,16 +218,24 @@
 
 (expect
   {:Segment #{2 3}}
-  (metric-dependencies Metric 12 {:definition {:aggregation ["rows"]
-                                               :breakout    [4 5]
-                                               :filter      ["AND" [">" 4 "2014-10-19"] ["=" 5 "yes"] ["SEGMENT" 2] ["SEGMENT" 3]]}}))
+  (metric-dependencies Metric 12 {:definition {:breakout [[:field-id 4] [:field-id 5]]
+                                               :filter   [:and
+                                                          [:> 4 "2014-10-19"]
+                                                          [:= 5 "yes"]
+                                                          [:segment 2]
+                                                          [:segment 3]]}}))
 
 (expect
   {:Segment #{1}}
-  (metric-dependencies Metric 12 {:definition {:aggregation ["METRIC" 7]
-                                               :filter      ["AND" [">" 4 "2014-10-19"] ["=" 5 "yes"] ["OR" ["SEGMENT" 1] ["!=" 5 "5"]]]}}))
+  (metric-dependencies Metric 12 {:definition {:aggregation [:metric 7]
+                                               :filter      [:and
+                                                             [:> 4 "2014-10-19"]
+                                                             [:= 5 "yes"]
+                                                             [:or
+                                                              [:segment 1]
+                                                              [:!= 5 "5"]]]}}))
 
 (expect
-  {:Segment nil}
+  {:Segment #{}}
   (metric-dependencies Metric 12 {:definition {:aggregation nil
                                                :filter      nil}}))
diff --git a/test/metabase/models/on_demand_test.clj b/test/metabase/models/on_demand_test.clj
index 3626cd7e80e7f143538ab150ef1c8b5c8b215cd2..d0a3121c8cf78493e1811d6b7ecf1f1e609d72b0 100644
--- a/test/metabase/models/on_demand_test.clj
+++ b/test/metabase/models/on_demand_test.clj
@@ -33,11 +33,11 @@
   {:database (data/id)
    :type     "native"
    :native   {:query         "SELECT AVG(SUBTOTAL) AS \"Average Price\"\nFROM ORDERS nWHERE {{category}}"
-              :template_tags {:category {:name         "category"
-                                         :display_name "Category"
+              :template-tags {:category {:name         "category"
+                                         :display-name "Category"
                                          :type         "dimension"
                                          :dimension    ["field-id" (u/get-id field-or-id)]
-                                         :widget_type  "category"
+                                         :widget-type  "category"
                                          :default      "Widget"}}}})
 
 (defn- do-with-updated-fields-for-card {:style/indent 1} [options & [f]]
@@ -145,12 +145,12 @@
 (defn- basic-mbql-query []
   {:database (data/id)
    :type     :query
-   :query    {:source_table (data/id :venues)
-              :aggregation  [["count"]]}})
+   :query    {:source-table (data/id :venues)
+              :aggregation  [[:count]]}})
 
 (defn- parameter-mappings-for-card-and-field [card-or-id field-or-id]
   [{:card_id (u/get-id card-or-id)
-    :target  ["dimension" ["field-id" (u/get-id field-or-id)]]}])
+    :target  [:dimension [:field-id (u/get-id field-or-id)]]}])
 
 (defn- add-dashcard-with-parameter-mapping! [dashboard-or-id card-or-id field-or-id]
   (dashboard/add-dashcard! dashboard-or-id card-or-id
diff --git a/test/metabase/models/params_test.clj b/test/metabase/models/params_test.clj
index c6c9e45898cc934de1d8377ccd96fd35214aa099..67ce8188251dc5e1b400f3b405d0f0a5abc297d8 100644
--- a/test/metabase/models/params_test.clj
+++ b/test/metabase/models/params_test.clj
@@ -75,7 +75,7 @@
                             {:database (data/id)
                              :type     :native
                              :native   {:query         "SELECT COUNT(*) FROM VENUES WHERE {{x}}"
-                                        :template_tags {:name {:name         :name
+                                        :template-tags {:name {:name         :name
                                                                :display_name "Name"
                                                                :type         :dimension
                                                                :dimension    [:field-id (data/id :venues :id)]}}}}}]
diff --git a/test/metabase/models/pulse_test.clj b/test/metabase/models/pulse_test.clj
index 7b68c6ad4bc7cf5a22676471f84f8353ef2bdffc..759172e2acf0dbcd44bd0374e0cc684c75b96786 100644
--- a/test/metabase/models/pulse_test.clj
+++ b/test/metabase/models/pulse_test.clj
@@ -223,15 +223,16 @@
 ;;; +----------------------------------------------------------------------------------------------------------------+
 
 (defn do-with-pulse-in-collection [f]
-  (tt/with-temp* [Collection [collection]
-                  Pulse      [pulse  {:collection_id (u/get-id collection)}]
-                  Database   [db    {:engine :h2}]
-                  Table      [table {:db_id (u/get-id db)}]
-                  Card       [card  {:dataset_query {:database (u/get-id db)
-                                                     :type     :query
-                                                     :query    {:source-table (u/get-id table)}}}]
-                  PulseCard  [_ {:pulse_id (u/get-id pulse), :card_id (u/get-id card)}]]
-    (f db collection pulse card)))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [collection]
+                    Pulse      [pulse  {:collection_id (u/get-id collection)}]
+                    Database   [db    {:engine :h2}]
+                    Table      [table {:db_id (u/get-id db)}]
+                    Card       [card  {:dataset_query {:database (u/get-id db)
+                                                       :type     :query
+                                                       :query    {:source-table (u/get-id table)}}}]
+                    PulseCard  [_ {:pulse_id (u/get-id pulse), :card_id (u/get-id card)}]]
+      (f db collection pulse card))))
 
 (defmacro with-pulse-in-collection
   "Execute `body` with a temporary Pulse, in a Colleciton, containing a single Card."
diff --git a/test/metabase/models/query/permissions_test.clj b/test/metabase/models/query/permissions_test.clj
index 9eb0a12b9bd2cb70c91fab56d48246725a1593b7..00cfc75755fbcd18ed330fe1e3e1328e014138a8 100644
--- a/test/metabase/models/query/permissions_test.clj
+++ b/test/metabase/models/query/permissions_test.clj
@@ -8,7 +8,6 @@
              [interface :as mi]
              [permissions :as perms]]
             [metabase.models.query.permissions :as query-perms]
-            [metabase.query-processor.middleware.expand :as ql]
             [metabase.test.data :as data]
             [metabase.util :as u]
             [toucan.util.test :as tt]))
@@ -123,15 +122,9 @@
 
 ;;; ------------------------------------------------- MBQL w/o JOIN --------------------------------------------------
 
-(defn- mbql [query]
-  {:database (data/id)
-   :type     :query
-   :query    query})
-
 (expect
   #{(perms/object-path (data/id) "PUBLIC" (data/id :venues))}
-  (query-perms/perms-set (mbql (ql/query
-                                 (ql/source-table (data/id :venues))))))
+  (query-perms/perms-set (data/mbql-query venues)))
 
 
 ;;; -------------------------------------------------- MBQL w/ JOIN --------------------------------------------------
@@ -141,15 +134,14 @@
   #{(perms/object-path (data/id) "PUBLIC" (data/id :checkins))
     (perms/object-path (data/id) "PUBLIC" (data/id :venues))}
   (query-perms/perms-set
-   (mbql (ql/query
-           (ql/source-table (data/id :checkins))
-           (ql/order-by (ql/asc (ql/fk-> (data/id :checkins :venue_id) (data/id :venues :name))))))))
+   (data/mbql-query checkins
+     {:order-by [[:asc $checkins.venue_id->venues.name]]})))
 
 
 ;;; ------------------------------------------- MBQL w/ nested MBQL query --------------------------------------------
 
 (defn- query-with-source-card [card]
-  {:database database/virtual-id, :type "query", :query {:source_table (str "card__" (u/get-id card))}})
+  {:database database/virtual-id, :type "query", :query {:source-table (str "card__" (u/get-id card))}})
 
 ;; if source card is *not* in a Collection, we require Root Collection read perms
 (expect
@@ -207,4 +199,5 @@
 ;; invalid/legacy queries should return perms for something that doesn't exist so no one gets to see it
 (expect
   #{"/db/0/"}
-  (query-perms/perms-set (mbql {:filter [:WOW 100 200]})))
+  (query-perms/perms-set (data/mbql-query venues
+                           {:filter [:WOW 100 200]})))
diff --git a/test/metabase/models/revision_test.clj b/test/metabase/models/revision_test.clj
index 7f89cba34e7a6f6957389fc1611d952d7931965a..db13cc9dc192b1d9ef038cb3ee1b606046112808 100644
--- a/test/metabase/models/revision_test.clj
+++ b/test/metabase/models/revision_test.clj
@@ -2,7 +2,7 @@
   (:require [expectations :refer :all]
             [metabase.models
              [card :refer [Card]]
-             [revision :refer :all]]
+             [revision :refer :all :as revision]]
             [metabase.test.data.users :refer :all]
             [metabase.util :as u]
             [toucan.models :as models]
@@ -33,6 +33,12 @@
     :object   (dissoc object :message)
     :message  message))
 
+;; make sure we call the appropriate post-select methods on `:object` when a revision comes out of the DB. This is
+;; especially important for things like Cards where we need to make sure query is normalized
+(expect
+  {:model "Card", :object {:dataset_query {:type :query}}}
+  (#'revision/do-post-select-for-object {:model "Card", :object {:dataset_query {:type "query"}}}))
+
 
 ;;; # Default diff-* implementations
 
diff --git a/test/metabase/models/segment_test.clj b/test/metabase/models/segment_test.clj
index a78c9bc1c983ef146602fec0f7162a58ff93a0af..53d5eddfe5eb3660ec2117988d2302e3f2fb6388 100644
--- a/test/metabase/models/segment_test.clj
+++ b/test/metabase/models/segment_test.clj
@@ -38,10 +38,10 @@
    :caveats                 nil
    :points_of_interest      nil
    :archived                false
-   :definition              {:clause ["a" "b"]}}
+   :definition              {:aggregation [[:count]]}}
   (tt/with-temp* [Database [{database-id :id}]
                   Table    [{table-id :id} {:db_id database-id}]]
-    (create-segment-then-select! table-id "I only want *these* things" nil (user->id :rasta) {:clause ["a" "b"]})))
+    (create-segment-then-select! table-id "I only want *these* things" nil (user->id :rasta) {:aggregation [[:count]]})))
 
 
 ;; exists?
@@ -65,13 +65,11 @@
    :caveats                 nil
    :points_of_interest      nil
    :archived                false
-   :definition              {:database 45
-                             :query    {:filter ["yay"]}}}
+   :definition              {:filter [:= [:field-id 1] 2]}}
   (tt/with-temp* [Database [{database-id :id}]
                   Table    [{table-id :id}   {:db_id database-id}]
                   Segment  [{segment-id :id} {:table_id   table-id
-                                              :definition {:database 45
-                                                           :query    {:filter ["yay"]}}}]]
+                                              :definition {:filter [:= [:field-id 1] 2]}}]]
     (let [{:keys [creator] :as segment} (retrieve-segment segment-id)]
       (-> segment
           (dissoc :id :table_id :created_at :updated_at)
@@ -88,7 +86,7 @@
     :caveats                 nil
     :points_of_interest      nil
     :archived                false
-    :definition              {}}]
+    :definition              nil}]
   (tt/with-temp* [Database [{database-id :id}]
                   Table    [{table-id-1 :id}    {:db_id database-id}]
                   Table    [{table-id-2 :id}    {:db_id database-id}]
@@ -117,8 +115,7 @@
    :caveats                 nil
    :points_of_interest      nil
    :archived                false
-   :definition              {:database 2
-                             :query    {:filter ["not" "the toucans you're looking for"]}}}
+   :definition              {:filter [:!= [:field-id 10] "toucans"]}}
   (tt/with-temp* [Database [{database-id :id}]
                   Table    [{:keys [id]} {:db_id database-id}]
                   Segment  [{:keys [id]} {:table_id id}]]
@@ -130,8 +127,7 @@
                                   :points_of_interest      nil
                                   :creator_id              (user->id :crowberto)
                                   :table_id                456
-                                  :definition              {:database 2
-                                                            :query    {:filter ["not" "the toucans you're looking for"]}}
+                                  :definition              {:filter [:!= [:field-id 10] "toucans"]}
                                   :revision_message        "Just horsing around"})))
 
 ;; delete-segment!
@@ -144,7 +140,7 @@
    :caveats                 nil
    :points_of_interest      nil
    :archived                true
-   :definition              {}}
+   :definition              nil}
   (tt/with-temp* [Database [{database-id :id}]
                   Table    [{:keys [id]} {:db_id database-id}]
                   Segment  [{:keys [id]} {:table_id id}]]
@@ -164,12 +160,12 @@
    :show_in_getting_started false
    :caveats                 nil
    :points_of_interest      nil
-   :definition              {:filter ["AND",[">",4,"2014-10-19"]]}
+   :definition              {:filter [:> [:field-id 4] "2014-10-19"]}
    :archived                false}
   (tt/with-temp* [Database [{database-id :id}]
                   Table    [{table-id :id} {:db_id database-id}]
                   Segment  [segment        {:table_id   table-id
-                                            :definition {:filter ["AND" [">" 4 "2014-10-19"]]}}]]
+                                            :definition {:filter [:and [:> [:field-id 4] "2014-10-19"]]}}]]
     (-> (#'segment/serialize-segment Segment (:id segment) segment)
         (update :id boolean)
         (update :table_id boolean))))
@@ -177,8 +173,8 @@
 
 ;; #'segment/diff-segments
 (expect
-  {:definition  {:before {:filter ["AND" [">" 4 "2014-10-19"]]}
-                 :after  {:filter ["AND" ["BETWEEN" 4 "2014-07-01" "2014-10-19"]]}}
+  {:definition  {:before {:filter [:> [:field-id 4] "2014-10-19"]}
+                 :after  {:filter [:between [:field-id 4] "2014-07-01" "2014-10-19"]}}
    :description {:before "Lookin' for a blueberry"
                  :after  "BBB"}
    :name        {:before "Toucans in the rainforest"
@@ -186,11 +182,11 @@
   (tt/with-temp* [Database [{database-id :id}]
                   Table    [{table-id :id} {:db_id database-id}]
                   Segment  [segment        {:table_id   table-id
-                                            :definition {:filter ["AND" [">" 4 "2014-10-19"]]}}]]
+                                            :definition {:filter [:and [:> [:field-id 4] "2014-10-19"]]}}]]
     (#'segment/diff-segments Segment segment (assoc segment
                                                :name        "Something else"
                                                :description "BBB"
-                                               :definition  {:filter ["AND" ["BETWEEN" 4 "2014-07-01" "2014-10-19"]]}))))
+                                               :definition  {:filter [:between [:field-id 4] "2014-07-01" "2014-10-19"]}))))
 
 ;; test case where definition doesn't change
 (expect
@@ -199,30 +195,30 @@
   (#'segment/diff-segments Segment
                            {:name        "A"
                             :description "Unchanged"
-                            :definition  {:filter ["AND" [">" 4 "2014-10-19"]]}}
+                            :definition  {:filter [:and [:> [:field-id 4] "2014-10-19"]]}}
                            {:name        "B"
                             :description "Unchanged"
-                            :definition  {:filter ["AND" [">" 4 "2014-10-19"]]}}))
+                            :definition  {:filter [:and [:> [:field-id 4] "2014-10-19"]]}}))
 
 ;; first version  so comparing against nil
 (expect
   {:name        {:after  "A"}
    :description {:after "Unchanged"}
-   :definition  {:after {:filter ["AND" [">" 4 "2014-10-19"]]}}}
+   :definition  {:after {:filter [:and [:> [:field-id 4] "2014-10-19"]]}}}
   (#'segment/diff-segments Segment
                            nil
                            {:name        "A"
                             :description "Unchanged"
-                            :definition  {:filter ["AND" [">" 4 "2014-10-19"]]}}))
+                            :definition  {:filter [:and [:> [:field-id 4] "2014-10-19"]]}}))
 
 ;; removals only
 (expect
-  {:definition  {:before {:filter ["AND" [">" 4 "2014-10-19"] ["=" 5 "yes"]]}
-                 :after  {:filter ["AND" [">" 4 "2014-10-19"]]}}}
+  {:definition  {:before {:filter [:and [:> [:field-id 4] "2014-10-19"] [:= 5 "yes"]]}
+                 :after  {:filter [:and [:> [:field-id 4] "2014-10-19"]]}}}
   (#'segment/diff-segments Segment
                            {:name        "A"
                             :description "Unchanged"
-                            :definition  {:filter ["AND" [">" 4 "2014-10-19"] ["=" 5 "yes"]]}}
+                            :definition  {:filter [:and [:> [:field-id 4] "2014-10-19"] [:= 5 "yes"]]}}
                            {:name        "A"
                             :description "Unchanged"
-                            :definition  {:filter ["AND" [">" 4 "2014-10-19"]]}}))
+                            :definition  {:filter [:and [:> [:field-id 4] "2014-10-19"]]}}))
diff --git a/test/metabase/models/setting_test.clj b/test/metabase/models/setting_test.clj
index cf617ab39ec60da3fc1759968595fdd9613a7108..006752d48d590058e6051c4f5769ea8f13f4dc34 100644
--- a/test/metabase/models/setting_test.clj
+++ b/test/metabase/models/setting_test.clj
@@ -1,8 +1,17 @@
 (ns metabase.models.setting-test
-  (:require [expectations :refer :all]
+  (:require [clojure.core.memoize :as memoize]
+            [expectations :refer :all]
+            [honeysql.core :as hsql]
+            [metabase
+             [db :as mdb]
+             [util :as u]]
             [metabase.models.setting :as setting :refer [defsetting Setting]]
             [metabase.test.util :refer :all]
-            [puppetlabs.i18n.core :refer [tru]]
+            [metabase.util
+             [encryption :as encryption]
+             [encryption-test :as encryption-test]
+             [i18n :refer [tru]]]
+            [puppetlabs.i18n.core :as i18n]
             [toucan.db :as db]))
 
 ;; ## TEST SETTINGS DEFINITIONS
@@ -11,21 +20,24 @@
 ;; these tests will fail. FIXME
 
 (defsetting test-setting-1
-  "Test setting - this only shows up in dev (1)")
+  "Test setting - this only shows up in dev (1)"
+  :internal? true)
 
 (defsetting test-setting-2
   "Test setting - this only shows up in dev (2)"
+  :internal? true
   :default "[Default Value]")
 
 (defsetting ^:private test-boolean-setting
   "Test setting - this only shows up in dev (3)"
+  :internal? true
   :type :boolean)
 
 (defsetting ^:private test-json-setting
   "Test setting - this only shows up in dev (4)"
+  :internal? true
   :type :json)
 
-
 ;; ## HELPER FUNCTIONS
 
 (defn db-fetch-setting
@@ -41,6 +53,10 @@
   (test-setting-2 setting-2-value))
 
 
+(expect
+  String
+  (:tag (meta #'test-setting-1)))
+
 ;; ## GETTERS
 ;; Test defsetting getter fn. Should return the value from env var MB_TEST_SETTING_1
 (expect "ABCDEFG"
@@ -167,7 +183,12 @@
 
 ;; all
 (expect
-  {:key :test-setting-2, :value "TOUCANS", :description "Test setting - this only shows up in dev (2)",  :is_env_setting false, :env_name "MB_TEST_SETTING_2", :default "[Default Value]"}
+  {:key            :test-setting-2
+   :value          "TOUCANS"
+   :description    "Test setting - this only shows up in dev (2)"
+   :is_env_setting false
+   :env_name       "MB_TEST_SETTING_2"
+   :default        "[Default Value]"}
   (do (set-settings! nil "TOUCANS")
       (some (fn [setting]
               (when (re-find #"^test-setting-2$" (name (:key setting)))
@@ -176,8 +197,18 @@
 
 ;; all
 (expect
-  [{:key :test-setting-1, :value nil, :is_env_setting true, :env_name "MB_TEST_SETTING_1", :description "Test setting - this only shows up in dev (1)", :default "Using $MB_TEST_SETTING_1"}
-   {:key :test-setting-2, :value "S2", :is_env_setting false, :env_name "MB_TEST_SETTING_2",  :description "Test setting - this only shows up in dev (2)", :default "[Default Value]"}]
+  [{:key            :test-setting-1
+    :value          nil
+    :is_env_setting true
+    :env_name       "MB_TEST_SETTING_1"
+    :description    "Test setting - this only shows up in dev (1)"
+    :default        "Using $MB_TEST_SETTING_1"}
+   {:key            :test-setting-2
+    :value          "S2"
+    :is_env_setting false,
+    :env_name       "MB_TEST_SETTING_2"
+    :description    "Test setting - this only shows up in dev (2)"
+    :default        "[Default Value]"}]
   (do (set-settings! nil "S2")
       (for [setting (setting/all)
             :when   (re-find #"^test-setting-\d$" (name (:key setting)))]
@@ -188,14 +219,21 @@
 
 ;; Validate setting description with i18n string
 (expect
-  ["Test setting - with i18n"]
-  (for [{:keys [key description]} (setting/all)
-        :when (= :test-i18n-setting key)]
-    description))
+  ["TEST SETTING - WITH I18N"]
+  (let [zz (i18n/string-as-locale "zz")]
+    (i18n/with-user-locale zz
+      (doall
+       (for [{:keys [key description]} (setting/all)
+             :when (= :test-i18n-setting key)]
+         description)))))
 
 
 ;;; ------------------------------------------------ BOOLEAN SETTINGS ------------------------------------------------
 
+(expect
+  Boolean
+  (:tag (meta #'test-boolean-setting)))
+
 (expect
   {:value nil, :is_env_setting false, :env_name "MB_TEST_BOOLEAN_SETTING", :default nil}
   (user-facing-info-with-db-and-env-var-values :test-boolean-setting nil nil))
@@ -254,7 +292,8 @@
 ;; (#4178)
 
 (setting/defsetting ^:private toucan-name
-  "Name for the Metabase Toucan mascot.")
+  "Name for the Metabase Toucan mascot."
+  :internal? true)
 
 (expect
   "Banana Beak"
@@ -264,8 +303,214 @@
     ;; restore the cache
     ((resolve 'metabase.models.setting/restore-cache-if-needed!))
     ;; now set a value for the `toucan-name` setting the wrong way
-    (db/insert! setting/Setting {:key "toucan-name", :value "Rasta"})
+    (db/insert! setting/Setting {:key "toucan-name", :value "Reggae"})
     ;; ok, now try to set the Setting the correct way
     (toucan-name "Banana Beak")
     ;; ok, make sure the setting was set
     (toucan-name)))
+
+(expect
+  String
+  (:tag (meta #'toucan-name)))
+
+
+;;; ----------------------------------------------- Encrypted Settings -----------------------------------------------
+
+(defn- actual-value-in-db [setting-key]
+  (-> (db/query {:select [:value]
+                 :from   [:setting]
+                 :where  [:= :key (name setting-key)]})
+      first :value u/jdbc-clob->str))
+
+;; If encryption is *enabled*, make sure Settings get saved as encrypted!
+(expect
+  (encryption-test/with-secret-key "ABCDEFGH12345678"
+    (toucan-name "Sad Can")
+    (u/base64-string? (actual-value-in-db :toucan-name))))
+
+;; make sure it can be decrypted as well...
+(expect
+  "Sad Can"
+  (encryption-test/with-secret-key "12345678ABCDEFGH"
+    (toucan-name "Sad Can")
+    (encryption/decrypt (actual-value-in-db :toucan-name))))
+
+;; But if encryption is not enabled, of course Settings shouldn't get saved as encrypted.
+(expect
+  "Sad Can"
+  (encryption-test/with-secret-key nil
+    (toucan-name "Sad Can")
+    (actual-value-in-db :toucan-name)))
+
+
+;;; ----------------------------------------------- TIMESTAMP SETTINGS -----------------------------------------------
+
+(defsetting ^:private test-timestamp-setting
+  "Test timestamp setting"
+  :internal? true
+  :type :timestamp)
+
+(expect
+  java.sql.Timestamp
+  (:tag (meta #'test-timestamp-setting)))
+
+;; make sure we can set & fetch the value and that it gets serialized/deserialized correctly
+(expect
+  #inst "2018-07-11T09:32:00.000Z"
+  (do (test-timestamp-setting #inst "2018-07-11T09:32:00.000Z")
+      (test-timestamp-setting)))
+
+
+;;; --------------------------------------------- Cache Synchronization ----------------------------------------------
+
+(def ^:private settings-last-updated-key @(resolve 'metabase.models.setting/settings-last-updated-key))
+
+(defn- clear-settings-last-updated-value-in-db! []
+  (db/simple-delete! Setting {:key settings-last-updated-key}))
+
+(defn- settings-last-updated-value-in-db []
+  (db/select-one-field :value Setting :key settings-last-updated-key))
+
+(defn- clear-cache! []
+  (reset! @(resolve 'metabase.models.setting/cache) nil))
+
+(defn- settings-last-updated-value-in-cache []
+  (get @@(resolve 'metabase.models.setting/cache) settings-last-updated-key))
+
+(defn- update-settings-last-updated-value-in-db!
+  "Simulate a different instance updating the value of `settings-last-updated` in the DB by updating its value without
+  updating our locally cached value.."
+  []
+  (db/update-where! Setting {:key settings-last-updated-key}
+    :value (hsql/raw (case (mdb/db-type)
+                       ;; make it one second in the future so we don't end up getting an exact match when we try to test
+                       ;; to see if things update below
+                       :h2       "cast(dateadd('second', 1, current_timestamp) AS text)"
+                       :mysql    "cast((current_timestamp + interval 1 second) AS char)"
+                       :postgres "cast((current_timestamp + interval '1 second') AS text)"))))
+
+(defn- simulate-another-instance-updating-setting! [setting-name new-value]
+  (db/update-where! Setting {:key (name setting-name)} :value new-value)
+  (update-settings-last-updated-value-in-db!))
+
+(defn- flush-memoized-results-for-should-restore-cache!
+  "Remove any memoized results for `should-restore-cache?`, so we can test `restore-cache-if-needed!` works the way we'd
+  expect."
+  []
+  (memoize/memo-clear! @(resolve 'metabase.models.setting/should-restore-cache?)))
+
+;; When I update a Setting, does it set/update `settings-last-updated`?
+(expect
+  (do
+    (clear-settings-last-updated-value-in-db!)
+    (toucan-name "Bird Can")
+    (string? (settings-last-updated-value-in-db))))
+
+;; ...and is the value updated in the cache as well?
+(expect
+  (do
+    (clear-cache!)
+    (toucan-name "Bird Can")
+    (string? (settings-last-updated-value-in-cache))))
+
+;; ...and if I update it again, will the value be updated?
+(expect
+  (do
+    (clear-settings-last-updated-value-in-db!)
+    (toucan-name "Bird Can")
+    (let [first-value (settings-last-updated-value-in-db)]
+      ;; MySQL only has the resolution of one second on the timestamps here so we should wait that long to make sure
+      ;; the second-value actually ends up being greater than the first
+      (Thread/sleep 1200)
+      (toucan-name "Bird Can")
+      (let [second-value (settings-last-updated-value-in-db)]
+        ;; first & second values should be different, and first value should be "less than" the second value
+        (and (not= first-value second-value)
+             (neg? (compare first-value second-value)))))))
+
+;; If there is no cache, it should be considered out of date!`
+(expect
+  (do
+    (clear-cache!)
+    (#'setting/cache-out-of-date?)))
+
+;; But if I set a setting, it should cause the cache to be populated, and be up-to-date
+(expect
+  false
+  (do
+    (clear-cache!)
+    (toucan-name "Reggae Toucan")
+    (#'setting/cache-out-of-date?)))
+
+;; If another instance updates a Setting, `cache-out-of-date?` should return `true` based on DB comparisons...
+;; be true!
+(expect
+  (do
+    (clear-cache!)
+    (toucan-name "Reggae Toucan")
+    (simulate-another-instance-updating-setting! :toucan-name "Bird Can")
+    (#'setting/cache-out-of-date?)))
+
+;; of course, `restore-cache-if-needed!` should use TTL memoization, and the cache should not get updated right away
+;; even if another instance updates a value...
+(expect
+  "Sam"
+  (do
+    (flush-memoized-results-for-should-restore-cache!)
+    (clear-cache!)
+    (toucan-name "Sam")                 ; should restore cache, and put in {"toucan-name" "Sam"}
+    ;; since we cleared the memoized value of `should-restore-cache?` call it again to make sure it gets set to
+    ;; `false` as it would IRL if we were calling it again from the same instance
+    (#'setting/should-restore-cache?)
+    ;; now have another instance change the value
+    (simulate-another-instance-updating-setting! :toucan-name "Bird Can")
+    ;; our cache should not be updated yet because it's on a TTL
+    (toucan-name)))
+
+;; ...and when it comes time to check our cache for updating (when calling `restore-cache-if-needed!`, it should get
+;; the updated value. (we're not actually going to wait a minute for the memoized values of `should-restore-cache?` to
+;; be invalidated, so we will manually flush the memoization cache to simulate it happening)
+(expect
+  "Bird Can"
+  (do
+    (clear-cache!)
+    (toucan-name "Reggae Toucan")
+    (simulate-another-instance-updating-setting! :toucan-name "Bird Can")
+    (flush-memoized-results-for-should-restore-cache!)
+    ;; calling `toucan-name` will call `restore-cache-if-needed!`, which will in turn call `should-restore-cache?`.
+    ;; Since memoized value is no longer present, this should call `cache-out-of-date?`, which checks the DB; it will
+    ;; detect a cache out-of-date situation and flush the cache as appropriate, giving us the updated value when we
+    ;; call! :wow:
+    (toucan-name)))
+
+
+;;; ----------------------------------------------- Uncached Settings ------------------------------------------------
+
+(defsetting ^:private uncached-setting
+  "A test setting that should *not* be cached."
+  :internal? true
+  :cache? false)
+
+;; make sure uncached setting still saves to the DB
+(expect
+  "ABCDEF"
+  (encryption-test/with-secret-key nil
+    (uncached-setting "ABCDEF")
+    (actual-value-in-db "uncached-setting")))
+
+;; make sure that fetching the Setting always fetches the latest value from the DB
+(expect
+  "123456"
+  (encryption-test/with-secret-key nil
+    (uncached-setting "ABCDEF")
+    (db/update-where! Setting {:key "uncached-setting"}
+      :value "123456")
+    (uncached-setting)))
+
+;; make sure that updating the setting doesn't update the last-updated timestamp in the cache $$
+(expect
+  nil
+  (encryption-test/with-secret-key nil
+    (clear-settings-last-updated-value-in-db!)
+    (uncached-setting "abcdef")
+    (settings-last-updated-value-in-db)))
diff --git a/test/metabase/models/task_history_test.clj b/test/metabase/models/task_history_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..4274582e1484a259ec8469ad77642699b4a6b287
--- /dev/null
+++ b/test/metabase/models/task_history_test.clj
@@ -0,0 +1,74 @@
+(ns metabase.models.task-history-test
+  (:require [clj-time
+             [coerce :as tcoerce]
+             [core :as time]]
+            [expectations :refer :all]
+            [metabase.models.task-history :refer :all]
+            [metabase.test.util :as tu]
+            [metabase.util :as u]
+            [metabase.util.date :as du]
+            [toucan.db :as db]
+            [toucan.util.test :as tt]))
+
+(defn add-second
+  "Adds one second to `t`"
+  [t]
+  (time/plus t (time/seconds 1)))
+
+(defn add-10-millis
+  "Adds 10 milliseconds to `t`"
+  [t]
+  (time/plus t (time/millis 10)))
+
+(defn make-10-millis-task
+  "Creates a map suitable for a `with-temp*` call for `TaskHistory`. Uses the `started_at` param sets the `ended_at`
+  to 10 milliseconds later"
+  [started-at]
+  (let [ended-at (add-10-millis started-at)]
+    {:started_at (du/->Timestamp started-at)
+     :ended_at   (du/->Timestamp ended-at)
+     :duration   (du/calculate-duration started-at ended-at)}))
+
+;; Basic cleanup test where older rows are deleted and newer rows kept
+(let [task-4 (tu/random-name)
+      task-5 (tu/random-name)]
+  (expect
+    #{task-4 task-5}
+    (let [t1-start (time/now)
+          t2-start (add-second t1-start)
+          t3-start (add-second t2-start)
+          t4-start (add-second t3-start)
+          t5-start (add-second t4-start)]
+      (tt/with-temp* [TaskHistory [t1 (make-10-millis-task t1-start)]
+                      TaskHistory [t2 (make-10-millis-task t2-start)]
+                      TaskHistory [t3 (make-10-millis-task t3-start)]
+                      TaskHistory [t4 (assoc (make-10-millis-task t4-start)
+                                        :task task-4)]
+                      TaskHistory [t5 (assoc (make-10-millis-task t5-start)
+                                        :task task-5)]]
+        ;; When the sync process runs, it creates several TaskHistory rows. We just want to work with the temp ones
+        ;; created, so delete any stale ones from previous tests
+        (db/delete! TaskHistory :id [:not-in (map u/get-id [t1 t2 t3 t4 t5])])
+        ;; Delete all but 2 task history rows
+        (cleanup-task-history! 2)
+        (set (map :task (TaskHistory)))))))
+
+;; Basic cleanup test where no work needs to be done and nothing is deleted
+(let [task-1 (tu/random-name)
+      task-2 (tu/random-name)]
+  (expect
+    [#{task-1 task-2}
+     #{task-1 task-2}]
+    (let [t1-start (time/now)
+          t2-start (add-second t1-start)]
+      (tt/with-temp* [TaskHistory [t1 (assoc (make-10-millis-task t1-start)
+                                        :task task-1)]
+                      TaskHistory [t2 (assoc (make-10-millis-task t2-start)
+                                        :task task-2)]]
+        ;; Cleanup any stale TalkHistory entries that are not the two being tested
+        (db/delete! TaskHistory :id [:not-in (map u/get-id [t1 t2])])
+        ;; We're keeping 100 rows, but there are only 2 present, so there should be no affect on running this
+        [(set (map :task (TaskHistory)))
+         (do
+           (cleanup-task-history! 100)
+           (set (map :task (TaskHistory))))]))))
diff --git a/test/metabase/models/user_test.clj b/test/metabase/models/user_test.clj
index 26e67a8e4841172634c2c88c03e8f207d2dc93b8..89b0077bb549cc2eda15e34f4430b29460742843 100644
--- a/test/metabase/models/user_test.clj
+++ b/test/metabase/models/user_test.clj
@@ -6,6 +6,7 @@
              [http-client :as http]]
             [metabase.models
              [collection :as collection :refer [Collection]]
+             [collection-test :as collection-test]
              [permissions :as perms]
              [permissions-group :refer [PermissionsGroup]]
              [permissions-group-membership :refer [PermissionsGroupMembership]]
@@ -38,26 +39,32 @@
          perms-path)))
 (expect
   #{(perms/collection-readwrite-path (collection/user->personal-collection (user->id :lucky)))}
-  (-> (user/permissions-set (user->id :lucky))
-      remove-non-collection-perms))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (-> (user/permissions-set (user->id :lucky))
+        remove-non-collection-perms)))
 
 ;; ...and for any descendant Collections of my Personal Collection?
-(tt/expect-with-temp [Collection [child-collection {:location (collection/children-location
-                                                               (collection/user->personal-collection
-                                                                (user->id :lucky)))}]
-                      Collection [grandchild-collection {:location (collection/children-location child-collection)}]]
+(expect
   #{(perms/collection-readwrite-path (collection/user->personal-collection (user->id :lucky)))
-    (perms/collection-readwrite-path child-collection)
-    (perms/collection-readwrite-path grandchild-collection)}
-  (-> (user/permissions-set (user->id :lucky))
-      remove-non-collection-perms))
+    "/collection/child/"
+    "/collection/grandchild/"}
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (tt/with-temp* [Collection [child-collection      {:name     "child"
+                                                       :location (collection/children-location
+                                                                  (collection/user->personal-collection
+                                                                   (user->id :lucky)))}]
+                    Collection [grandchild-collection {:name     "grandchild"
+                                                       :location (collection/children-location child-collection)}]]
+      (->> (user/permissions-set (user->id :lucky))
+           remove-non-collection-perms
+           (collection-test/perms-path-ids->names [child-collection grandchild-collection])))))
 
 
 ;;; Tests for invite-user and create-new-google-auth-user!
 
 (defn- maybe-accept-invite!
-  "Accept an invite if applicable. Look in the body of the content of the invite email for the reset token
-   since this is the only place to get it (the token stored in the DB is an encrypted hash)."
+  "Accept an invite if applicable. Look in the body of the content of the invite email for the reset token since this is
+  the only place to get it (the token stored in the DB is an encrypted hash)."
   [new-user-email-address]
   (when-let [[{[{invite-email :content}] :body}] (get @email-test/inbox new-user-email-address)]
     (let [[_ reset-token] (re-find #"/auth/reset_password/(\d+_[\w_-]+)#new" invite-email)]
@@ -66,7 +73,7 @@
 
 (defn sent-emails
   "Fetch the emails that have been sent in the form of a map of email address -> sequence of email subjects.
-   For test-writing convenience the random email and names assigned to the new user are replaced with `<New User>`."
+  For test-writing convenience the random email and names assigned to the new user are replaced with `<New User>`."
   [new-user-email-address new-user-first-name new-user-last-name]
   (into {} (for [[address emails] @email-test/inbox
                  :let             [address (if (= address new-user-email-address)
@@ -78,7 +85,7 @@
 
 (defn- invite-user-accept-and-check-inboxes!
   "Create user by passing INVITE-USER-ARGS to `invite-user!` or `create-new-google-auth-user!`,
-   and return a map of addresses emails were sent to to the email subjects."
+  and return a map of addresses emails were sent to to the email subjects."
   [& {:keys [google-auth? accept-invite? password invitor]
       :or   {accept-invite? true}}]
   (tu/with-temporary-setting-values [site-name "Metabase"]
diff --git a/test/metabase/publc_settings_test.clj b/test/metabase/publc_settings_test.clj
index 22249289c1791d4f04416d1b30683b8aa2314cc3..fc7f82c6bc135ebb8ec450422823a4a88e87ff4a 100644
--- a/test/metabase/publc_settings_test.clj
+++ b/test/metabase/publc_settings_test.clj
@@ -1,7 +1,8 @@
 (ns metabase.publc-settings-test
   (:require [expectations :refer :all]
             [metabase.public-settings :as public-settings]
-            [metabase.test.util :as tu]))
+            [metabase.test.util :as tu]
+            [puppetlabs.i18n.core :as i18n :refer [tru trs]]))
 
  ;; double-check that setting the `site-url` setting will automatically strip off trailing slashes
 (expect
@@ -35,3 +36,16 @@
   (tu/with-temporary-setting-values [site-url nil]
     (public-settings/site-url "https://localhost:3000")
     (public-settings/site-url)))
+
+(expect
+  "HOST"
+  (let [zz (i18n/string-as-locale "zz")]
+    (i18n/with-user-locale zz
+      (str (:display-name (first (get-in (public-settings/public-settings) [:engines :postgres :details-fields])))))))
+
+(expect
+  [true "HOST"]
+  (let [zz (i18n/string-as-locale "zz")]
+    (i18n/with-user-locale zz
+      [(= zz (i18n/user-locale))
+       (tru "Host")])))
diff --git a/test/metabase/pulse/color_test.clj b/test/metabase/pulse/color_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..13ef9ab2b3deffac0b9aaa89f628afd3505c2e01
--- /dev/null
+++ b/test/metabase/pulse/color_test.clj
@@ -0,0 +1,48 @@
+(ns metabase.pulse.color-test
+  (:require [expectations :refer :all]
+            [metabase.pulse.color :as color :refer :all]))
+
+(def ^:private red "#ff0000")
+(def ^:private green "#00ff00")
+
+(def ^:private ^String test-script
+  "function makeCellBackgroundGetter(rows, colsJSON, settingsJSON) {
+     cols = JSON.parse(colsJSON);
+     settings = JSON.parse(settingsJSON);
+     cols.map(function (a) { return a; });
+     return function(value, rowIndex, columnName) {
+        if(rowIndex % 2 == 0){
+          return settings[\"even\"]
+        } else {
+          return settings[\"odd\"]
+        }
+    }
+   }")
+
+(defmacro ^:private with-test-js-engine
+  "Setup a javascript engine with a stubbed script useful making sure `get-background-color` works independently from
+  the real color picking script"
+  [script & body]
+  `(with-redefs [color/js-engine (delay (#'color/make-js-engine-with-script ~script))]
+     ~@body))
+
+;; The test script above should return red on even rows, green on odd rows
+(expect
+  [red green red green]
+  (with-test-js-engine test-script
+    (let [color-selector (make-color-selector {:cols [{:name "test"}]
+                                               :rows [[1] [2] [3] [4]]}
+                                              {"even" red, "odd" green})]
+      (for [row-index (range 0 4)]
+        (get-background-color color-selector "any value" "any column" row-index)))))
+
+;; Same test as above, but make sure we convert any keywords as keywords don't get converted to strings automatically
+;; when passed to a nashorn function
+(expect
+  [red green red green]
+  (with-test-js-engine test-script
+    (let [color-selector (make-color-selector {:cols [{:name "test"}]
+                                               :rows [[1] [2] [3] [4]]}
+                                              {:even red, :odd  green})]
+      (for [row-index (range 0 4)]
+        (get-background-color color-selector "any value" "any column" row-index)))))
diff --git a/test/metabase/pulse/render_test.clj b/test/metabase/pulse/render_test.clj
index f59991c0a704357eb7c3b01b0b335f449935ea78..87bfcf4ec05ac4b069afbdc13ef338d2dd10f6dc 100644
--- a/test/metabase/pulse/render_test.clj
+++ b/test/metabase/pulse/render_test.clj
@@ -2,7 +2,10 @@
   (:require [clojure.walk :as walk]
             [expectations :refer :all]
             [hiccup.core :refer [html]]
-            [metabase.pulse.render :as render :refer :all])
+            [metabase.pulse
+             [color :as color]
+             [render :as render :refer :all]]
+            [metabase.query-processor.util :as qputil])
   (:import java.util.TimeZone))
 
 (def ^:private pacific-tz (TimeZone/getTimeZone "America/Los_Angeles"))
@@ -288,3 +291,165 @@
   4
   (count-displayed-columns
    (concat test-columns [description-col detail-col sensitive-col retired-col])))
+
+(defn- find-table-body
+  "Given the hiccup data structure, find the table body and return it"
+  [results]
+  (qputil/postwalk-collect (every-pred vector? #(= :tbody (first %)))
+                           ;; The Hiccup form is [:tbody (...rows...)], so grab the second item
+                           second
+                           results))
+
+(defn- style-map->background-color
+  "Finds the background color in the style string of a Hiccup style map"
+  [{:keys [style]}]
+  (let [[_ color-str] (re-find #".*background-color: ([^;]*);" style)]
+    color-str))
+
+(defn- cell-value->background-color
+  "Returns a map of cell values to background colors of the pulse table found in the hiccup `results` data
+  structure. This only includes the data cell values, not the header values."
+  [results]
+  (into {} (qputil/postwalk-collect (every-pred vector? #(= :td (first %)))
+                                    (fn [[_ style-map cell-value]]
+                                      [cell-value (style-map->background-color style-map)])
+                                    results)))
+
+(defn- query-results->header+rows
+  "Makes pulse header and data rows with no bar-width. Including bar-width just adds extra HTML that will be ignored."
+  [{:keys [cols rows]}]
+  (for [row-values (cons (map :name cols) rows)]
+    {:row row-values
+     :bar-width nil}))
+
+(def ^:private default-test-card
+  {:visualization_settings
+   {"table.column_formatting" [{:columns       ["a"]
+                                :type          :single
+                                :operator      ">"
+                                :value         5
+                                :color         "#ff0000"
+                                :highlight_row true}
+                               {:columns       ["c"]
+                                :type          "range"
+                                :min_type      "custom"
+                                :min_value     3
+                                :max_type      "custom"
+                                :max_value     9
+                                :colors        ["#00ff00" "#0000ff"]}]}})
+
+;; Smoke test for background color selection. Background color decided by some shared javascript code. It's being
+;; invoked and included in the cell color of the pulse table. This is somewhat fragile code as the only way to find
+;; that style information is to crawl the clojure-ized HTML datastructure and pick apart the style string associated
+;; with the cell value. The script right now is hard coded to always return #ff0000. Once the real script is in place,
+;; we should find some similar basic values that can rely on. The goal isn't to test out the javascript choosing in
+;; the color (that should be done in javascript) but to verify that the pieces are all connecting correctly
+(expect
+   {"1" "",                     "2" "",                     "3" "rgba(0, 255, 0, 0.75)"
+    "4" "",                     "5" "",                     "6" "rgba(0, 128, 128, 0.75)"
+    "7" "rgba(255, 0, 0, 0.65)" "8" "rgba(255, 0, 0, 0.2)"  "9" "rgba(0, 0, 255, 0.75)"}
+  (let [query-results {:cols [{:name "a"} {:name "b"} {:name "c"}]
+                       :rows [[1 2 3]
+                              [4 5 6]
+                              [7 8 9]]}]
+    (-> (color/make-color-selector query-results (:visualization_settings default-test-card))
+        (#'render/render-table ["a" "b" "c"] (query-results->header+rows query-results))
+        find-table-body
+        cell-value->background-color)))
+
+;; Test rendering a bar graph
+;;
+;; These test render the bar graph to ensure no exceptions are thrown, then look at the flattened HTML data structures
+;; to see if the column names for the columns we're graphing are present in the result
+
+(defn- flatten-html-data
+  "Takes the tree-based Clojure HTML data structure and flattens it to a seq"
+  [html-data]
+  (tree-seq coll? seq html-data))
+
+(defn- render-bar-graph [results]
+  ;; `doall` here as the flatten won't force lazy-seqs
+  (doall (flatten-html-data (#'render/render:bar pacific-tz default-test-card results))))
+
+(def ^:private default-columns
+  [{:name         "Price",
+    :display_name "Price",
+    :base_type    :type/BigInteger
+    :special_type nil}
+   {:name         "NumPurchased",
+    :display_name "NumPurchased",
+    :base_type    :type/BigInteger
+    :special_type nil}])
+
+;; Render a bar graph with non-nil values for the x and y axis
+(expect
+  [true true]
+  (let [result (render-bar-graph {:cols default-columns
+                                  :rows [[10.0 1] [5.0 10] [2.50 20] [1.25 30]]})]
+    [(some #(= "Price" %) result)
+     (some #(= "NumPurchased" %) result)]))
+
+;; Check to make sure we allow nil values for the y-axis
+(expect
+  [true true]
+  (let [result (render-bar-graph {:cols default-columns
+                                  :rows [[10.0 1] [5.0 10] [2.50 20] [1.25 nil]]})]
+    [(some #(= "Price" %) result)
+     (some #(= "NumPurchased" %) result)]))
+
+;; Check to make sure we allow nil values for the y-axis
+(expect
+  [true true]
+  (let [result (render-bar-graph {:cols default-columns
+                                  :rows [[10.0 1] [5.0 10] [2.50 20] [nil 30]]})]
+    [(some #(= "Price" %) result)
+     (some #(= "NumPurchased" %) result)]))
+
+;; Check to make sure we allow nil values for both x and y on different rows
+(expect
+  [true true]
+  (let [result (render-bar-graph {:cols default-columns
+                                  :rows [[10.0 1] [5.0 10] [nil 20] [1.25 nil]]})]
+    [(some #(= "Price" %) result)
+     (some #(= "NumPurchased" %) result)]))
+
+;; Test rendering a sparkline
+;;
+;; Sparklines are a binary image either in-line or as an attachment, so there's not much introspection that we can do
+;; with the result. The tests below just check that we can render a sparkline (without eceptions) and that the
+;; attachment is included
+
+(defn- render-sparkline [results]
+  (-> (#'render/render:sparkline :attachment pacific-tz default-test-card results)
+      :attachments
+      count))
+
+;; Test that we can render a sparkline with all valid values
+(expect
+  1
+  (render-sparkline {:cols default-columns
+                     :rows [[10.0 1] [5.0 10] [2.50 20] [1.25 30]]}))
+
+;; Tex that we can have a nil value in the middle
+(expect
+  1
+  (render-sparkline {:cols default-columns
+                     :rows [[10.0 1] [11.0 2] [5.0 nil] [2.50 20] [1.25 30]]}))
+
+;; Test that we can have a nil value for the y-axis at the end of the results
+(expect
+  1
+  (render-sparkline {:cols default-columns
+                     :rows [[10.0 1] [11.0 2] [2.50 20] [1.25 nil]]}))
+
+;; Test that we can have a nil value for the x-axis at the end of the results
+(expect
+  1
+  (render-sparkline {:cols default-columns
+                     :rows [[10.0 1] [11.0 2] [nil 20] [1.25 30]]}))
+
+;; Test that we can have a nil value for both x and y axis for different rows
+(expect
+  1
+  (render-sparkline {:cols default-columns
+                     :rows [[10.0 1] [11.0 2] [nil 20] [1.25 nil]]}))
diff --git a/test/metabase/pulse_test.clj b/test/metabase/pulse_test.clj
index dd74847b9895151392591f17e6953afeef1a9849..c45c9ce89c3683dba69abecf8de8e6afc994be9c 100644
--- a/test/metabase/pulse_test.clj
+++ b/test/metabase/pulse_test.clj
@@ -33,7 +33,7 @@
   {:name          card-name
    :dataset_query {:database (data/id)
                    :type     :query
-                   :query    (merge {:source_table (data/id :checkins)
+                   :query    (merge {:source-table (data/id :checkins)
                                      :aggregation  [["count"]]}
                                     query-map)}})
 
@@ -643,7 +643,7 @@
   {:name          card-name
    :dataset_query {:database (data/id)
                    :type     :query
-                   :query    {:source_table (data/id :venues)
+                   :query    {:source-table (data/id :venues)
                               :aggregation  [[aggregation-op (data/id :venues :price)]]}}})
 
 ;; Above goal alert with a progress bar
diff --git a/test/metabase/query_processor/expand_resolve_test.clj b/test/metabase/query_processor/expand_resolve_test.clj
deleted file mode 100644
index 0df02c081d7eead799c0cf0f69780a4a5edc6dbc..0000000000000000000000000000000000000000
--- a/test/metabase/query_processor/expand_resolve_test.clj
+++ /dev/null
@@ -1,355 +0,0 @@
-(ns metabase.query-processor.expand-resolve-test
-  "Tests query expansion/resolution"
-  (:require [clojure.string :as str]
-            [expectations :refer :all]
-            [metabase
-             [query-processor :as qp]
-             [util :as u]]
-            [metabase.query-processor.middleware
-             [expand :as ql]
-             [resolve :as resolve]
-             [source-table :as source-table]]
-            [metabase.query-processor-test :as qpt]
-            [metabase.test
-             [data :as data :refer :all]
-             [util :as tu]]
-            [metabase.test.data.dataset-definitions :as defs]
-            [metabase.util.date :as du]))
-
-;; this is here because expectations has issues comparing and object w/ a map and most of the output
-;; below has objects for the various place holders in the expanded/resolved query
-(defn- obj->map [o]
-  (cond
-    (sequential? o) (vec (for [v o]
-                           (obj->map v)))
-    (set? o)        (set (for [v o]
-                           (obj->map v)))
-    (map? o)        (into {} (for [[k v] o]
-                               {k (obj->map v)}))
-    :else           o))
-
-(def ^:private resolve'
-  "Testing the resolve middleware requires that the source table be
-  resolved before calling the resolve function. In the query pipeline
-  this is two separate steps. This function combines the function for
-  resolving the source table and the middleware that resolves the rest
-  of the expanded query into a single function to make tests more
-  concise."
-  (comp resolve/resolve (source-table/resolve-source-table-middleware identity)))
-
-(def ^:private field-ph-defaults
-  {:fk-field-id        nil
-   :datetime-unit      nil
-   :remapped-from      nil
-   :remapped-to        nil
-   :field-display-name nil
-   :binning-strategy   nil
-   :binning-param      nil})
-
-(def ^:private field-defaults
-  {:fk-field-id     nil
-   :visibility-type :normal
-   :position        nil
-   :description     nil
-   :parent-id       nil
-   :parent          nil
-   :schema-name     nil
-   :remapped-from   nil
-   :remapped-to     nil
-   :dimensions      []
-   :values          []})
-
-(def ^:private price-field-values
-  {:field-value-id        true
-   :created-at            true
-   :updated-at            true
-   :values                [1 2 3 4]
-   :human-readable-values {}
-   :field-id              true})
-
-;; basic rows query w/ filter
-(expect
-  [ ;; expanded form
-   {:database (id)
-    :type     :query
-    :query    {:source-table (id :venues)
-               :filter       {:filter-type :>
-                              :field       (merge field-ph-defaults
-                                                  {:field-id true})
-                              :value       {:field-placeholder (merge field-ph-defaults
-                                                                      {:field-id true})
-                                            :value             1}}}}
-   ;; resolved form
-   {:database (id)
-    :type     :query
-    :query    {:source-table {:schema "PUBLIC"
-                              :name   "VENUES"
-                              :id     true}
-               :filter       {:filter-type :>
-                              :field       (merge field-defaults
-                                                  {:field-id           true
-                                                   :field-name         "PRICE"
-                                                   :field-display-name "Price"
-                                                   :database-type      "INTEGER"
-                                                   :base-type          :type/Integer
-                                                   :special-type       :type/Category
-                                                   :table-id           (id :venues)
-                                                   :schema-name        "PUBLIC"
-                                                   :table-name         "VENUES"
-                                                   :values             price-field-values
-                                                   :fingerprint        {:global {:distinct-count 4}
-                                                                        :type   {:type/Number {:min 1, :max 4, :avg 2.03}}}})
-                              :value       {:value 1
-                                            :field (merge field-defaults
-                                                          {:field-id           true
-                                                           :field-name         "PRICE"
-                                                           :field-display-name "Price"
-                                                           :database-type      "INTEGER"
-                                                           :base-type          :type/Integer
-                                                           :special-type       :type/Category
-                                                           :table-id           (id :venues)
-                                                           :schema-name        "PUBLIC"
-                                                           :table-name         "VENUES"
-                                                           :values             price-field-values
-                                                           :fingerprint        {:global {:distinct-count 4}
-                                                                                :type   {:type/Number {:min 1, :max 4, :avg 2.03}}}})}}
-
-
-               :join-tables nil}
-    :fk-field-ids #{}
-    :table-ids    #{(id :venues)}}]
-  (let [expanded-form (ql/expand (wrap-inner-query (query venues
-                                                     (ql/filter (ql/and (ql/> $price 1))))))]
-    (tu/boolean-ids-and-timestamps
-     (mapv obj->map [expanded-form
-                     (resolve' expanded-form)]))))
-
-(def category-field-values
-  {:values                (defs/field-values defs/test-data-map "categories" "name")
-   :human-readable-values {}
-   :field-value-id        true
-   :field-id              true
-   :created-at            true
-   :updated-at            true})
-
-;; basic rows query w/ FK filter
-(expect
-  [ ;; expanded form
-   {:database (id)
-    :type     :query
-    :query    {:source-table (id :venues)
-               :filter       {:filter-type :=
-                              :field       (merge field-ph-defaults
-                                                  {:field-id    true
-                                                   :fk-field-id (id :venues :category_id)})
-                              :value       {:field-placeholder (merge field-ph-defaults
-                                                                      {:field-id    true
-                                                                       :fk-field-id (id :venues :category_id)})
-                                            :value             "abc"}}}}
-   ;; resolved form
-   {:database     (id)
-    :type         :query
-    :query        {:source-table {:schema "PUBLIC"
-                                  :name   "VENUES"
-                                  :id     true}
-                   :filter       {:filter-type :=
-                                  :field       (merge field-defaults
-                                                      {:field-id           true
-                                                       :fk-field-id        (id :venues :category_id)
-                                                       :field-name         "NAME"
-                                                       :field-display-name "Name"
-                                                       :database-type      "VARCHAR"
-                                                       :base-type          :type/Text
-                                                       :special-type       :type/Name
-                                                       :table-id           (id :categories)
-                                                       :table-name         "CATEGORIES__via__CATEGORY_ID"
-                                                       :values             category-field-values
-                                                       :fingerprint        {:global {:distinct-count 75}
-                                                                            :type   {:type/Text {:percent-json   0.0
-                                                                                                 :percent-url    0.0
-                                                                                                 :percent-email  0.0
-                                                                                                 :average-length 8.333333333333334}}}})
-                                  :value       {:value "abc"
-                                                :field (merge field-defaults
-                                                              {:field-id           true
-                                                               :fk-field-id        (id :venues :category_id)
-                                                               :field-name         "NAME"
-                                                               :field-display-name "Name"
-                                                               :database-type      "VARCHAR"
-                                                               :base-type          :type/Text
-                                                               :special-type       :type/Name
-                                                               :table-id           (id :categories)
-                                                               :table-name         "CATEGORIES__via__CATEGORY_ID"
-                                                               :values             category-field-values
-                                                               :fingerprint        {:global {:distinct-count 75}
-                                                                                    :type   {:type/Text {:percent-json   0.0
-                                                                                                         :percent-url    0.0
-                                                                                                         :percent-email  0.0
-                                                                                                         :average-length 8.333333333333334}}}})}}
-                   :join-tables  [{:source-field {:field-id   true
-                                                  :field-name "CATEGORY_ID"}
-                                   :pk-field     {:field-id   true
-                                                  :field-name "ID"}
-                                   :table-id     (id :categories)
-                                   :table-name   "CATEGORIES"
-                                   :schema       "PUBLIC"
-                                   :join-alias   "CATEGORIES__via__CATEGORY_ID"}]}
-    :fk-field-ids #{(id :venues :category_id)}
-    :table-ids    #{(id :categories)}}]
-  (tu/boolean-ids-and-timestamps
-   (let [expanded-form (ql/expand (wrap-inner-query (query venues
-                                                           (ql/filter (ql/= $category_id->categories.name
-                                                                            "abc")))))]
-     (mapv obj->map [expanded-form
-                     (resolve' expanded-form)]))))
-
-
-;; basic rows query w/ FK filter on datetime
-(expect
-  [ ;; expanded form
-   {:database (id)
-    :type     :query
-    :query    {:source-table (id :checkins)
-               :filter       {:filter-type :>
-                              :field       (merge field-ph-defaults
-                                                  {:field-id      (id :users :last_login)
-                                                   :fk-field-id   (id :checkins :user_id)
-                                                   :datetime-unit :year})
-                              :value       {:field-placeholder (merge field-ph-defaults
-                                                                      {:field-id      (id :users :last_login)
-                                                                       :fk-field-id   (id :checkins :user_id)
-                                                                       :datetime-unit :year})
-                                            :value             "1980-01-01"}}}}
-   ;; resolved form
-   {:database     (id)
-    :type         :query
-    :query        {:source-table {:schema "PUBLIC"
-                                  :name   "CHECKINS"
-                                  :id     (id :checkins)}
-                   :filter       {:filter-type :>
-                                  :field       {:field (merge field-defaults
-                                                              {:field-id           (id :users :last_login)
-                                                               :fk-field-id        (id :checkins :user_id)
-                                                               :field-name         "LAST_LOGIN"
-                                                               :field-display-name "Last Login"
-                                                               :database-type      "TIMESTAMP"
-                                                               :base-type          :type/DateTime
-                                                               :special-type       nil
-                                                               :table-id           (id :users)
-                                                               :table-name         "USERS__via__USER_ID"
-                                                               :fingerprint        {:global {:distinct-count 11}
-                                                                                    :type   {:type/DateTime {:earliest "2014-01-01T00:00:00.000Z"
-                                                                                                             :latest   "2014-12-05T00:00:00.000Z"}}}})
-                                                :unit  :year}
-                                  :value       {:value (du/->Timestamp #inst "1980-01-01")
-                                                :field {:field
-                                                        (merge field-defaults
-                                                               {:field-id           (id :users :last_login)
-                                                                :fk-field-id        (id :checkins :user_id)
-                                                                :field-name         "LAST_LOGIN"
-                                                                :field-display-name "Last Login"
-                                                                :database-type      "TIMESTAMP"
-                                                                :base-type          :type/DateTime
-                                                                :special-type       nil
-                                                                :visibility-type    :normal
-                                                                :table-id           (id :users)
-                                                                :table-name         "USERS__via__USER_ID"
-                                                                :fingerprint        {:global {:distinct-count 11}
-                                                                                     :type   {:type/DateTime {:earliest "2014-01-01T00:00:00.000Z"
-                                                                                                              :latest   "2014-12-05T00:00:00.000Z"}}}})
-                                                        :unit :year}}}
-                   :join-tables  [{:source-field {:field-id   (id :checkins :user_id)
-                                                  :field-name "USER_ID"}
-                                   :pk-field     {:field-id   (id :users :id)
-                                                  :field-name "ID"}
-                                   :table-id     (id :users)
-                                   :table-name   "USERS"
-                                   :schema       "PUBLIC"
-                                   :join-alias   "USERS__via__USER_ID"}]}
-    :fk-field-ids #{(id :checkins :user_id)}
-    :table-ids    #{(id :users)}}]
-  (qpt/with-h2-db-timezone
-    (let [expanded-form (ql/expand (wrap-inner-query (query checkins
-                                                       (ql/filter (ql/> (ql/datetime-field $user_id->users.last_login :year)
-                                                                        "1980-01-01")))))]
-      (mapv obj->map [expanded-form (resolve' expanded-form)]))))
-
-
-;; sum aggregation w/ datetime breakout
-(expect
-  [ ;; expanded form
-   {:database (id)
-    :type     :query
-    :query    {:source-table (id :checkins)
-               :aggregation  [{:aggregation-type :sum
-                               :custom-name      nil
-                               :field            (merge field-ph-defaults
-                                                        {:field-id    true
-                                                         :fk-field-id (id :checkins :venue_id)})}]
-               :breakout     [(merge field-ph-defaults
-                                     {:field-id      true
-                                      :datetime-unit :day-of-week})]}}
-   ;; resolved form
-   {:database     (id)
-    :type         :query
-    :query        {:source-table {:schema "PUBLIC"
-                                  :name   "CHECKINS"
-                                  :id     true}
-                   :aggregation  [{:aggregation-type :sum
-                                   :custom-name      nil
-                                   :field            (merge field-defaults
-                                                            {:database-type      "INTEGER"
-                                                             :base-type          :type/Integer
-                                                             :table-id           (id :venues)
-                                                             :special-type       :type/Category
-                                                             :field-name         "PRICE"
-                                                             :field-display-name "Price"
-                                                             :field-id           true
-                                                             :fk-field-id        (id :checkins :venue_id)
-                                                             :table-name         "VENUES__via__VENUE_ID"
-                                                             :values             price-field-values
-                                                             :fingerprint        {:global {:distinct-count 4}
-                                                                                  :type   {:type/Number {:min 1, :max 4, :avg 2.03}}}})}]
-                   :breakout     [{:field (merge field-defaults
-                                                 {:database-type      "DATE"
-                                                  :base-type          :type/Date
-                                                  :table-id           (id :checkins)
-                                                  :special-type       nil
-                                                  :field-name         "DATE"
-                                                  :field-display-name "Date"
-                                                  :field-id           true
-                                                  :table-name         "CHECKINS"
-                                                  :schema-name        "PUBLIC"
-                                                  :fingerprint        {:global {:distinct-count 618}
-                                                                       :type   {:type/DateTime {:earliest "2013-01-03T00:00:00.000Z"
-                                                                                                :latest   "2015-12-29T00:00:00.000Z"}}}})
-                                   :unit  :day-of-week}]
-                   :join-tables  [{:source-field {:field-id   true
-                                                  :field-name "VENUE_ID"}
-                                   :pk-field     {:field-id   true
-                                                  :field-name "ID"}
-                                   :table-id     (id :venues)
-                                   :table-name   "VENUES"
-                                   :schema       "PUBLIC"
-                                   :join-alias   "VENUES__via__VENUE_ID"}]}
-    :fk-field-ids #{(id :checkins :venue_id)}
-    :table-ids    #{(id :venues) (id :checkins)}}]
-  (let [expanded-form (ql/expand (wrap-inner-query (query checkins
-                                                          (ql/aggregation (ql/sum $venue_id->venues.price))
-                                                          (ql/breakout (ql/datetime-field $checkins.date :day-of-week)))))]
-    (tu/boolean-ids-and-timestamps
-     (mapv obj->map [expanded-form
-                     (resolve' expanded-form)]))))
-
-;; check that a schema invalidation error produces a reasonably-sized exception, < 50 lines.
-;; previously the entire schema was being dumped which resulted in a ~5200 line exception (#5978)
-(expect
-  (-> (qp/process-query
-        {:database (data/id)
-         :type     :query
-         :query    {:source-table (data/id :venues)
-                    :filter       [:and nil]}})
-      u/pprint-to-str
-      str/split-lines
-      count
-      (< 50)))
diff --git a/test/metabase/query_processor/middleware/add_dimension_projections_test.clj b/test/metabase/query_processor/middleware/add_dimension_projections_test.clj
index b2b3df2c2cc6225d3a5c145345a65513542ab43f..002619a76c934f2047e0e30a4df48472ae368ea9 100644
--- a/test/metabase/query_processor/middleware/add_dimension_projections_test.clj
+++ b/test/metabase/query_processor/middleware/add_dimension_projections_test.clj
@@ -1,11 +1,8 @@
 (ns metabase.query-processor.middleware.add-dimension-projections-test
   "Tests for the Query Processor cache."
   (:require [expectations :refer :all]
-            [metabase.query-processor.middleware.add-dimension-projections :refer :all :as add-dim-projections]
             [metabase.query-processor.interface :as i]
-            [metabase.test.util :as tu]
-            [toucan.db :as db]
-            [metabase.query-processor.middleware.expand :as ql]))
+            [metabase.query-processor.middleware.add-dimension-projections :as add-dim-projections :refer :all]))
 
 (def ^:private col-defaults
   {:description     nil
diff --git a/test/metabase/query_processor/middleware/annotate_and_sort_test.clj b/test/metabase/query_processor/middleware/annotate_and_sort_test.clj
index 3e33c3d700b12350fdf2815a43b5334bd1f2225f..1a33672532080ad1ebef088c8228c4d8790f6917 100644
--- a/test/metabase/query_processor/middleware/annotate_and_sort_test.clj
+++ b/test/metabase/query_processor/middleware/annotate_and_sort_test.clj
@@ -11,3 +11,9 @@
                                                                            [3 nil]
                                                                            [4   5]
                                                                            [6   7]]})))
+
+;; make sure that `infer-column-types` defaults `base_type` to `type/*` if there are no non-nil
+;; values when we peek.
+(expect
+  [{:name "a", :display_name "A", :base_type :type/*}]
+  (:cols (#'annotate-and-sort/infer-column-types {:columns [:a], :rows [[nil]]})))
diff --git a/test/metabase/query_processor/middleware/annotate_test.clj b/test/metabase/query_processor/middleware/annotate_test.clj
index e847074df8ec0607eeda180161bad6222941380a..8d22d4de845ad3b812bc3a4095ec4119c4986834 100644
--- a/test/metabase/query_processor/middleware/annotate_test.clj
+++ b/test/metabase/query_processor/middleware/annotate_test.clj
@@ -66,7 +66,7 @@
   (map
    (u/rpartial select-keys [:field-id :field-name :source])
    (annotate/collect-fields
-     {:aggregation  [{:aggregation-type :count, :custom-name nil}]
+     {:aggregation  [[:count]]
       :breakout     [(qpi/map->FieldLiteral {:field-name "text", :base-type :type/Text, :datetime-unit nil})]
       :source-query {:source-table       {:schema "public", :name "messages", :id 1}
                      :fields-is-implicit true
diff --git a/test/metabase/query_processor/middleware/binning_test.clj b/test/metabase/query_processor/middleware/binning_test.clj
index 47025bd027632bf9ea6714c0eb32b94803e72d9a..9447b83a6a6676eb404b0564bf269dfacd1a07f4 100644
--- a/test/metabase/query_processor/middleware/binning_test.clj
+++ b/test/metabase/query_processor/middleware/binning_test.clj
@@ -1,7 +1,7 @@
 (ns metabase.query-processor.middleware.binning-test
   (:require [expectations :refer [expect]]
             [metabase.query-processor.middleware
-             [binning :as binning :refer :all]
+             [binning :as binning]
              [expand :as ql]]))
 
 (expect
@@ -70,19 +70,12 @@
                                 (ql/between (ql/field-id 1) 600 700)]}))
 
 (expect
-  [[0.0 1000.0 125.0 8]
-   [200N 1600N 200 8]
-   [0.0 1200.0 200 8]
-   [0.0 1005.0 15.0 67]]
-  [((juxt :min-value :max-value :bin-width :num-bins)
-         (nicer-breakout {:field-id 1 :min-value 100 :max-value 1000
-                          :strategy :num-bins :num-bins 8}))
-   ((juxt :min-value :max-value :bin-width :num-bins)
-         (nicer-breakout {:field-id 1 :min-value 200 :max-value 1600
-                          :strategy :num-bins :num-bins 8}))
-   ((juxt :min-value :max-value :bin-width :num-bins)
-         (nicer-breakout {:field-id 1 :min-value 9 :max-value 1002
-                          :strategy :num-bins :num-bins 8}))
-   ((juxt :min-value :max-value :bin-width :num-bins)
-         (nicer-breakout {:field-id 1 :min-value 9 :max-value 1002
-                          :strategy :bin-width :bin-width 15.0}))])
+  [[ 0.0 1000.0 125.0  8]
+   [200N  1600N   200  8]
+   [ 0.0 1200.0   200  8]
+   [ 0.0 1005.0  15.0 67]]
+  (for [breakout [{:field-id 1, :min-value 100, :max-value 1000, :strategy :num-bins,  :num-bins     8}
+                  {:field-id 1, :min-value 200, :max-value 1600, :strategy :num-bins,  :num-bins     8}
+                  {:field-id 1, :min-value   9, :max-value 1002, :strategy :num-bins,  :num-bins     8}
+                  {:field-id 1, :min-value   9, :max-value 1002, :strategy :bin-width, :bin-width 15.0}]]
+    ((juxt :min-value :max-value :bin-width :num-bins) (binning/nicer-breakout breakout))))
diff --git a/test/metabase/query_processor/middleware/expand_macros_test.clj b/test/metabase/query_processor/middleware/expand_macros_test.clj
index dccea28fb9080f2c8bc80d537b5e45529edd7bff..d5f295bcaedb1e324e708dce6eac743cb94a72a7 100644
--- a/test/metabase/query_processor/middleware/expand_macros_test.clj
+++ b/test/metabase/query_processor/middleware/expand_macros_test.clj
@@ -9,9 +9,7 @@
              [metric :refer [Metric]]
              [segment :refer [Segment]]
              [table :refer [Table]]]
-            [metabase.query-processor.middleware
-             [expand :as ql]
-             [expand-macros :as expand-macros :refer :all]]
+            [metabase.query-processor.middleware.expand-macros :as expand-macros :refer :all]
             [metabase.test.data :as data]
             [metabase.test.data.datasets :as datasets]
             [toucan.util.test :as tt]))
@@ -20,149 +18,148 @@
 (expect
   {:database 1
    :type     :query
-   :query    {:aggregation ["rows"]
-              :filter      ["AND" [">" 4 1]]
-              :breakout    [17]}}
+   :query    {:filter   [:> [:field-id 4] 1]
+              :breakout [[:field-id 17]]}}
   (#'expand-macros/expand-metrics-and-segments {:database 1
                                                 :type     :query
-                                                :query    {:aggregation ["rows"]
-                                                           :filter      ["AND" [">" 4 1]]
-                                                           :breakout    [17]}}))
+                                                :query    {:filter   [:> [:field-id 4] 1]
+                                                           :breakout [[:field-id 17]]}}))
 
 ;; just segments
 (expect
   {:database 1
    :type     :query
-   :query    {:aggregation ["rows"]
-              :filter      ["AND" ["AND" ["=" 5 "abc"]]
-                            ["OR" ["AND" ["IS_NULL" 7]]
-                             [">" 4 1]]]
-              :breakout    [17]}}
+   :query    {:filter   [:and
+                         [:= [:field-id 5] "abc"]
+                         [:or
+                          [:is-null [:field-id 7]]
+                          [:> [:field-id 4] 1]]]
+              :breakout [[:field-id 17]]}}
   (tt/with-temp* [Database [{database-id :id}]
                   Table    [{table-id :id}     {:db_id database-id}]
                   Segment  [{segment-1-id :id} {:table_id   table-id
-                                                :definition {:filter ["AND" ["=" 5 "abc"]]}}]
+                                                :definition {:filter [:and [:= [:field-id 5] "abc"]]}}]
                   Segment  [{segment-2-id :id} {:table_id   table-id
-                                                :definition {:filter ["AND" ["IS_NULL" 7]]}}]]
+                                                :definition {:filter [:and [:is-null [:field-id 7]]]}}]]
     (#'expand-macros/expand-metrics-and-segments {:database 1
                                                   :type     :query
-                                                  :query    {:aggregation ["rows"]
-                                                             :filter      ["AND" ["SEGMENT" segment-1-id] ["OR" ["SEGMENT" segment-2-id] [">" 4 1]]]
-                                                             :breakout    [17]}})))
+                                                  :query    {:filter   [:and [:segment segment-1-id] [:or
+                                                                                                      [:segment segment-2-id]
+                                                                                                      [:> [:field-id 4] 1]]]
+                                                             :breakout [[:field-id 17]]}})))
 
-;; Does expansion work if "AND" isn't capitalized? (MBQL is case-insensitive!) (#5706, #5530)
+;; Does expansion work if :and isn't capitalized? (MBQL is case-insensitive!) (#5706, #5530)
 (expect
   {:database 1
    :type     :query
-   :query    {:aggregation ["rows"]
-              :filter      ["and"
-                            ["=" 5 "abc"]
-                            ["IS_NULL" 7]]
-              :breakout    [17]}}
+   :query    {:filter   [:and
+                         [:= [:field-id 5] "abc"]
+                         [:is-null [:field-id 7]]]
+              :breakout [[:field-id 17]]}}
   (tt/with-temp* [Database [{database-id :id}]
                   Table    [{table-id :id}     {:db_id database-id}]
                   Segment  [{segment-1-id :id} {:table_id   table-id
-                                                :definition {:filter ["=" 5 "abc"]}}]
+                                                :definition {:filter [:= [:field-id 5] "abc"]}}]
                   Segment  [{segment-2-id :id} {:table_id   table-id
-                                                :definition {:filter ["IS_NULL" 7]}}]]
+                                                :definition {:filter [:is-null [:field-id 7]]}}]]
     (#'expand-macros/expand-metrics-and-segments {:database 1
                                                   :type     :query
-                                                  :query    {:aggregation ["rows"]
-                                                             :filter      ["and"
-                                                                           ["SEGMENT" segment-1-id]
-                                                                           ["SEGMENT" segment-2-id]]
-                                                             :breakout    [17]}})))
+                                                  :query    {:filter   [:and
+                                                                        [:segment segment-1-id]
+                                                                        [:segment segment-2-id]]
+                                                             :breakout [[:field-id 17]]}})))
 
 ;; just a metric (w/out nested segments)
 (expect
   {:database 1
    :type     :query
-   :query    {:aggregation ["count"]
+   :query    {:aggregation [[:count]]
               :filter      [:and
-                            ["AND" [">" 4 1]]
-                            ["AND" ["=" 5 "abc"]]]
-              :breakout    [17]
-              :order_by    [[1 "ASC"]]}}
+                            [:> [:field-id 4] 1]
+                            [:= [:field-id 5] "abc"]]
+              :breakout    [[:field-id 17]]
+              :order-by    [[:asc [:field-id 1]]]}}
   (tt/with-temp* [Database [{database-id :id}]
                   Table    [{table-id :id}    {:db_id database-id}]
                   Metric   [{metric-1-id :id} {:table_id   table-id
-                                               :definition {:aggregation ["count"]
-                                                            :filter      ["AND" ["=" 5 "abc"]]}}]]
+                                               :definition {:aggregation [[:count]]
+                                                            :filter      [:and [:= [:field-id 5] "abc"]]}}]]
     (#'expand-macros/expand-metrics-and-segments {:database 1
                                                   :type     :query
-                                                  :query    {:aggregation ["METRIC" metric-1-id]
-                                                             :filter      ["AND" [">" 4 1]]
-                                                             :breakout    [17]
-                                                             :order_by    [[1 "ASC"]]}})))
+                                                  :query    {:aggregation [[:metric metric-1-id]]
+                                                             :filter      [:and [:> [:field-id 4] 1]]
+                                                             :breakout    [[:field-id 17]]
+                                                             :order-by    [[:asc [:field-id 1]]]}})))
 
 ;; check that when the original filter is empty we simply use our metric filter definition instead
 (expect
   {:database 1
    :type     :query
-   :query    {:aggregation ["count"]
-              :filter      ["AND" ["=" 5 "abc"]]
-              :breakout    [17]
-              :order_by    [[1 "ASC"]]}}
+   :query    {:aggregation [[:count]]
+              :filter      [:= [:field-id 5] "abc"]
+              :breakout    [[:field-id 17]]
+              :order-by    [[:asc [:field-id 1]]]}}
   (tt/with-temp* [Database [{database-id :id}]
                   Table    [{table-id :id}    {:db_id database-id}]
                   Metric   [{metric-1-id :id} {:table_id   table-id
-                                               :definition {:aggregation ["count"]
-                                                            :filter      ["AND" ["=" 5 "abc"]]}}]]
+                                               :definition {:aggregation [[:count]]
+                                                            :filter      [:and [:= [:field-id 5] "abc"]]}}]]
     (#'expand-macros/expand-metrics-and-segments {:database 1
                                                   :type     :query
-                                                  :query    {:aggregation ["METRIC" metric-1-id]
-                                                             :filter      []
-                                                             :breakout    [17]
-                                                             :order_by    [[1 "ASC"]]}})))
+                                                  :query    {:aggregation [[:metric metric-1-id]]
+                                                             :breakout    [[:field-id 17]]
+                                                             :order-by    [[:asc [:field-id 1]]]}})))
 
 ;; metric w/ no filter definition
 (expect
   {:database 1
    :type     :query
-   :query    {:aggregation ["count"]
-              :filter      ["AND" ["=" 5 "abc"]]
-              :breakout    [17]
-              :order_by    [[1 "ASC"]]}}
+   :query    {:aggregation [[:count]]
+              :filter      [:= [:field-id 5] "abc"]
+              :breakout    [[:field-id 17]]
+              :order-by    [[:asc [:field-id 1]]]}}
   (tt/with-temp* [Database [{database-id :id}]
                   Table    [{table-id :id}    {:db_id database-id}]
                   Metric   [{metric-1-id :id} {:table_id   table-id
-                                               :definition {:aggregation ["count"]}}]]
+                                               :definition {:aggregation [[:count]]}}]]
     (#'expand-macros/expand-metrics-and-segments {:database 1
                                                   :type     :query
-                                                  :query    {:aggregation ["METRIC" metric-1-id]
-                                                             :filter      ["AND" ["=" 5 "abc"]]
-                                                             :breakout    [17]
-                                                             :order_by    [[1 "ASC"]]}})))
+                                                  :query    {:aggregation [[:metric metric-1-id]]
+                                                             :filter      [:= [:field-id 5] "abc"]
+                                                             :breakout    [[:field-id 17]]
+                                                             :order-by    [[:asc [:field-id 1]]]}})))
 
 ;; a metric w/ nested segments
 (expect
   {:database 1
    :type     :query
-   :query    {:aggregation ["sum" 18]
+   :query    {:aggregation [[:sum [:field-id 18]]]
               :filter      [:and
-                            ["AND"
-                             [">" 4 1]
-                             ["AND" ["IS_NULL" 7]]]
-                            ["AND"
-                             ["=" 5 "abc"]
-                             ["AND" ["BETWEEN" 9 0 25]]]]
-              :breakout    [17]
-              :order_by    [[1 "ASC"]]}}
+                            [:> [:field-id 4] 1]
+                            [:is-null [:field-id 7]]
+                            [:= [:field-id 5] "abc"]
+                            [:between [:field-id 9] 0 25]]
+              :breakout    [[:field-id 17]]
+              :order-by    [[:asc [:field-id 1]]]}}
   (tt/with-temp* [Database [{database-id :id}]
                   Table    [{table-id :id}     {:db_id database-id}]
                   Segment  [{segment-1-id :id} {:table_id   table-id
-                                                :definition {:filter ["AND" ["BETWEEN" 9 0 25]]}}]
+                                                :definition {:filter [:and [:between [:field-id 9] 0 25]]}}]
                   Segment  [{segment-2-id :id} {:table_id   table-id
-                                                :definition {:filter ["AND" ["IS_NULL" 7]]}}]
-                  Metric   [{metric-1-id :id}  {:table_id    table-id
-                                                :definition  {:aggregation ["sum" 18]
-                                                              :filter      ["AND" ["=" 5 "abc"] ["SEGMENT" segment-1-id]]}}]]
+                                                :definition {:filter [:and [:is-null [:field-id 7]]]}}]
+                  Metric   [{metric-1-id :id}  {:table_id   table-id
+                                                :definition {:aggregation [[:sum [:field-id 18]]]
+                                                             :filter      [:and
+                                                                           [:= [:field-id 5] "abc"]
+                                                                           [:segment segment-1-id]]}}]]
     (#'expand-macros/expand-metrics-and-segments {:database 1
                                                   :type     :query
-                                                  :query    {:aggregation ["METRIC" metric-1-id]
-                                                             :filter      ["AND" [">" 4 1] ["SEGMENT" segment-2-id]]
-                                                             :breakout    [17]
-                                                             :order_by    [[1 "ASC"]]}})))
+                                                  :query    {:aggregation [[:metric metric-1-id]]
+                                                             :filter      [:and
+                                                                           [:> [:field-id 4] 1]
+                                                                           [:segment segment-2-id]]
+                                                             :breakout    [[:field-id 17]]
+                                                             :order-by    [[:asc [:field-id 1]]]}})))
 
 ;; Check that a metric w/ multiple aggregation syntax (nested vector) still works correctly
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :expression-aggregations)
@@ -177,8 +174,8 @@
               {:database (data/id)
                :type     :query
                :query    {:source-table (data/id :venues)
-                          :aggregation  [["METRIC" (u/get-id metric)]]
-                          :breakout     [(ql/breakout (ql/field-id (data/id :venues :price)))]}})))))
+                          :aggregation  [[:metric (u/get-id metric)]]
+                          :breakout     [[:field-id (data/id :venues :price)]]}})))))
 
 ;; make sure that we don't try to expand GA "metrics" (#6104)
 (expect
@@ -191,5 +188,12 @@
 
 ;; make sure expansion works with multiple GA "metrics" (#7399)
 (expect
-  {:query {:aggregation [[:METRIC :ga:users] [:METRIC :ga:1dayUsers]]}}
-  (#'expand-macros/expand-metrics-and-segments {:query {:aggregation [[:METRIC :ga:users] [:METRIC :ga:1dayUsers]]}}))
+  {:query {:aggregation [[:metric :ga:users] [:metric :ga:1dayUsers]]}}
+  (#'expand-macros/expand-metrics-and-segments {:query {:aggregation [[:metric :ga:users] [:metric :ga:1dayUsers]]}}))
+
+;; make sure we can name a :metric (ick)
+(expect
+  (tt/with-temp Metric [metric {:definition {:aggregation [[:sum [:field-id 20]]]}}]
+    (#'expand-macros/expand-metrics-and-segments
+     {:query {:aggregation [[:named [:metric (u/get-id metric)] "My Cool Metric"]]
+              :breakout    [[:field-id 10]]}})))
diff --git a/test/metabase/query_processor/middleware/fetch_source_query_test.clj b/test/metabase/query_processor/middleware/fetch_source_query_test.clj
index 0b9d4936ddb4710ab1a7eee87503386b201feafa..2eb3b6a050a73673fdafa91408be50fb83740196 100644
--- a/test/metabase/query_processor/middleware/fetch_source_query_test.clj
+++ b/test/metabase/query_processor/middleware/fetch_source_query_test.clj
@@ -38,7 +38,7 @@
    :query    {:aggregation  [:count]
               :breakout     [[:field-literal :price :type/Integer]]
               :source-query {:native        (format "SELECT * FROM %s" (data/format-name "venues"))
-                             :template_tags nil}}}
+                             :template-tags nil}}}
   (tt/with-temp Card [card {:dataset_query {:database (data/id)
                                             :type     :native
                                             :native   {:query (format "SELECT * FROM %s" (data/format-name "venues"))}}}]
@@ -76,21 +76,22 @@
                        :query    {:source-table (str "card__" (u/get-id card))}})))
 
 (expect
-  (default-expanded-results
-   {:source-query {:source-table {:schema "PUBLIC" :name "CHECKINS" :id (data/id :checkins)}, :join-tables nil}
-    :filter {:filter-type :between,
-             :field {:field-name "date", :base-type :type/Date},
-             :min-val {:value (tcoerce/to-timestamp (du/str->date-time "2015-01-01"))
-                       :field {:field {:field-name "date", :base-type :type/Date}, :unit :default}},
-             :max-val {:value (tcoerce/to-timestamp (du/str->date-time "2015-02-01"))
-                       :field {:field {:field-name "date", :base-type :type/Date}, :unit :default}}}})
+  (let [date-field-literal {:field-name "date", :base-type :type/Date, :binning-strategy nil, :binning-param nil, :fingerprint nil}]
+    (default-expanded-results
+     {:source-query {:source-table {:schema "PUBLIC" :name "CHECKINS" :id (data/id :checkins)}, :join-tables nil}
+      :filter       {:filter-type :between,
+                     :field       date-field-literal
+                     :min-val     {:value (tcoerce/to-timestamp (du/str->date-time "2015-01-01"))
+                                   :field {:field date-field-literal, :unit :default}},
+                     :max-val     {:value (tcoerce/to-timestamp (du/str->date-time "2015-02-01"))
+                                   :field {:field date-field-literal, :unit :default}}}}))
   (tt/with-temp Card [card {:dataset_query {:database (data/id)
                                             :type     :query
                                             :query    {:source-table (data/id :checkins)}}}]
     (expand-and-scrub {:database database/virtual-id
                        :type     :query
                        :query    {:source-table (str "card__" (u/get-id card))
-                                  :filter ["BETWEEN" ["field-id" ["field-literal" "date" "type/Date"]] "2015-01-01" "2015-02-01"]}})))
+                                  :filter       ["BETWEEN" ["field-id" ["field-literal" "date" "type/Date"]] "2015-01-01" "2015-02-01"]}})))
 
 ;; make sure that nested nested queries work as expected
 (expect
diff --git a/test/metabase/query_processor/middleware/format_rows_test.clj b/test/metabase/query_processor/middleware/format_rows_test.clj
index 7653a264916b0ff5293b13a4b2b6b2b8d46eb54a..190a1a6f051de08cd14d7b60f22801bb7de29cd8 100644
--- a/test/metabase/query_processor/middleware/format_rows_test.clj
+++ b/test/metabase/query_processor/middleware/format_rows_test.clj
@@ -1,6 +1,5 @@
 (ns metabase.query-processor.middleware.format-rows-test
   (:require [metabase.query-processor-test :as qpt]
-            [metabase.query-processor.middleware.expand :as ql]
             [metabase.test
              [data :as data]
              [util :as tu]]
@@ -22,9 +21,9 @@
      [4 "Simcha Yan" "2014-01-01T00:00:00.000Z" "08:30:00.000Z"]
      [5 "Quentin Sören" "2014-10-03T00:00:00.000Z" "17:30:00.000Z"]])
   (->> (data/with-db (data/get-or-create-database! defs/test-data-with-time)
-         (data/run-query users
-           (ql/order-by (ql/asc $id))
-           (ql/limit 5)))
+         (data/run-mbql-query users
+           {:order-by [[:asc $id]]
+            :limit    5}))
        qpt/rows))
 
 (qpt/expect-with-non-timeseries-dbs-except #{:oracle :mongo :redshift :presto :sparksql}
@@ -51,7 +50,7 @@
      [5 "Quentin Sören" "2014-10-03T00:00:00.000Z" "17:30:00.000Z"]])
   (tu/with-temporary-setting-values [report-timezone "America/Los_Angeles"]
     (->> (data/with-db (data/get-or-create-database! defs/test-data-with-time)
-           (data/run-query users
-             (ql/order-by (ql/asc $id))
-             (ql/limit 5)))
+           (data/run-mbql-query users
+             {:order-by [[:asc $id]]
+              :limit    5}))
          qpt/rows)))
diff --git a/test/metabase/query_processor/middleware/limit_test.clj b/test/metabase/query_processor/middleware/limit_test.clj
index a17c4c583c2806077f19584648252dfb3a62ba7d..7548f7b18ff2ac3c327f8417f3a510a9c389d870 100644
--- a/test/metabase/query_processor/middleware/limit_test.clj
+++ b/test/metabase/query_processor/middleware/limit_test.clj
@@ -4,7 +4,8 @@
             [metabase.query-processor.interface :as i]
             [metabase.query-processor.middleware.limit :as limit]))
 
-;;; ------------------------------------------------------------ LIMIT-MAX-RESULT-ROWS ------------------------------------------------------------
+;;; --------------------------------------------- LIMIT-MAX-RESULT-ROWS ----------------------------------------------
+
 ;; Apply limit-max-result-rows to an infinite sequence and make sure it gets capped at `i/absolute-max-results`
 (expect
   i/absolute-max-results
@@ -16,16 +17,16 @@
 (expect
   1234
   (->> ((limit/limit identity) {:constraints {:max-results 1234}
-                                :query       {:aggregation [{:aggregation-type :count}]}
+                                :query       {:aggregation [[:count]]}
                                 :rows        (repeat [:ok])})
        :rows
        count))
 
-;; Apply a max-results-bare-rows limit specifically on :rows type query
+;; Apply a max-results-bare-rows limit specifically on no-aggregation query
 (expect
   [46 46]
   (let [res ((limit/limit identity) {:constraints {:max-results 46}
-                                     :query       {:aggregation [{:aggregation-type :rows}]}
+                                     :query       {}
                                      :rows        (repeat [:ok])})]
     [(->> res :rows count)
      (->> res :query :limit)]))
diff --git a/test/metabase/query_processor/middleware/parameters/mbql_test.clj b/test/metabase/query_processor/middleware/parameters/mbql_test.clj
index 9dfd8caa46fc060ffbd86840358022adc95f49f2..6eecc9d1eb4ca648084666b434a4c550b919c020 100644
--- a/test/metabase/query_processor/middleware/parameters/mbql_test.clj
+++ b/test/metabase/query_processor/middleware/parameters/mbql_test.clj
@@ -3,118 +3,115 @@
   (:require [expectations :refer :all]
             [metabase
              [query-processor :as qp]
-             [query-processor-test :refer [first-row format-rows-by non-timeseries-engines rows]]
-             [util :as u]]
-            [metabase.query-processor.middleware.expand :as ql]
-            [metabase.query-processor.middleware.parameters.mbql :as mbql-params :refer :all]
+             [query-processor-test :refer [first-row format-rows-by non-timeseries-engines rows]]]
+            [metabase.mbql.normalize :as normalize]
+            [metabase.query-processor.middleware.parameters.mbql :as mbql-params]
             [metabase.test.data :as data]
             [metabase.test.data.datasets :as datasets]
             [metabase.util.date :as du]))
 
 (defn- expand-parameters [query]
-  (expand (dissoc query :parameters) (:parameters query)))
+  (let [query (normalize/normalize query)]
+    (mbql-params/expand (dissoc query :parameters) (:parameters query))))
 
 
 ;; adding a simple parameter
 (expect
   {:database   1
    :type       :query
-   :query      {:filter   [:= ["field-id" (data/id :venues :name)] "Cam's Toucannery"]
-                :breakout [17]}}
+   :query      {:filter   [:= [:field-id (data/id :venues :name)] "Cam's Toucannery"]
+                :breakout [[:field-id 17]]}}
   (expand-parameters {:database   1
                       :type       :query
-                      :query      {:breakout [17]}
+                      :query      {:breakout [[:field-id 17]]}
                       :parameters [{:hash   "abc123"
                                     :name   "foo"
                                     :type   "id"
-                                    :target ["dimension" ["field-id" (data/id :venues :name)]]
+                                    :target [:dimension [:field-id (data/id :venues :name)]]
                                     :value  "Cam's Toucannery"}]}))
 
 ;; multiple filters are conjoined by an "AND"
 (expect
   {:database 1
    :type     :query
-   :query    {:filter   ["AND"
-                         ["AND"
-                          ["AND"
-                           ["=" (data/id :venues :id) 12]]
-                          [:= ["field-id" (data/id :venues :name)] "Cam's Toucannery"]]
-                         [:= ["field-id" (data/id :venues :id)] 999]]
-              :breakout [17]}}
+   :query    {:filter   [:and
+                         [:= [:field-id (data/id :venues :id)] 12]
+                         [:= [:field-id (data/id :venues :name)] "Cam's Toucannery"]
+                         [:= [:field-id (data/id :venues :id)] 999]]
+              :breakout [[:field-id 17]]}}
   (expand-parameters {:database   1
                       :type       :query
-                      :query      {:filter   ["AND" ["=" (data/id :venues :id) 12]]
-                                   :breakout [17]}
+                      :query      {:filter   ["AND" [:= (data/id :venues :id) 12]]
+                                   :breakout [[:field-id 17]]}
                       :parameters [{:hash   "abc123"
                                     :name   "foo"
-                                    :type   "id"
-                                    :target ["dimension" ["field-id" (data/id :venues :name)]]
+                                    :type   :id
+                                    :target [:dimension [:field-id (data/id :venues :name)]]
                                     :value  "Cam's Toucannery"}
                                    {:hash   "def456"
                                     :name   "bar"
-                                    :type   "category"
-                                    :target ["dimension" ["field-id" (data/id :venues :id)]]
+                                    :type   :category
+                                    :target [:dimension [:field-id (data/id :venues :id)]]
                                     :value  999}]}))
 
 ;; date range parameters
 (expect
   {:database   1
    :type       :query
-   :query      {:filter   ["TIME_INTERVAL" ["field-id" (data/id :users :last_login)] -30 "day" {:include-current false}]
-                :breakout [17]}}
+   :query      {:filter   [:time-interval [:field-id (data/id :users :last_login)] -30 :day {:include-current false}]
+                :breakout [[:field-id 17]]}}
   (expand-parameters {:database   1
                       :type       :query
-                      :query      {:breakout [17]}
+                      :query      {:breakout [[:field-id 17]]}
                       :parameters [{:hash   "abc123"
                                     :name   "foo"
-                                    :type   "date"
-                                    :target ["dimension" ["field-id" (data/id :users :last_login)]]
+                                    :type   :date
+                                    :target [:dimension [:field-id (data/id :users :last_login)]]
                                     :value  "past30days"}]}))
 
 (expect
   {:database   1
    :type       :query
-   :query      {:filter   ["TIME_INTERVAL" ["field-id" (data/id :users :last_login)] -30 "day" {:include-current true}]
-                :breakout [17]}}
+   :query      {:filter   [:time-interval [:field-id (data/id :users :last_login)] -30 :day {:include-current true}]
+                :breakout [[:field-id 17]]}}
   (expand-parameters {:database   1
                       :type       :query
-                      :query      {:breakout [17]}
+                      :query      {:breakout [[:field-id 17]]}
                       :parameters [{:hash   "abc123"
                                     :name   "foo"
-                                    :type   "date"
-                                    :target ["dimension" ["field-id" (data/id :users :last_login)]]
+                                    :type   :date
+                                    :target [:dimension [:field-id (data/id :users :last_login)]]
                                     :value  "past30days~"}]}))
 
 (expect
   {:database   1
    :type       :query
-   :query      {:filter   ["=" ["field-id" (data/id :users :last_login)] ["relative_datetime" -1 "day"]]
-                :breakout [17]}}
+   :query      {:filter   [:= [:field-id (data/id :users :last_login)] [:relative-datetime -1 :day]]
+                :breakout [[:field-id 17]]}}
   (expand-parameters {:database   1
                       :type       :query
-                      :query      {:breakout [17]}
+                      :query      {:breakout [[:field-id 17]]}
                       :parameters [{:hash   "abc123"
                                     :name   "foo"
                                     :type   "date"
-                                    :target ["dimension" ["field-id" (data/id :users :last_login)]]
+                                    :target [:dimension [:field-id (data/id :users :last_login)]]
                                     :value  "yesterday"}]}))
 
 (expect
   {:database   1
    :type       :query
-   :query      {:filter   ["BETWEEN" ["field-id" (data/id :users :last_login)] "2014-05-10" "2014-05-16"]
-                :breakout [17]}}
+   :query      {:filter   [:between [:field-id (data/id :users :last_login)] "2014-05-10" "2014-05-16"]
+                :breakout [[:field-id 17]]}}
   (expand-parameters {:database   1
                       :type       :query
-                      :query      {:breakout [17]}
+                      :query      {:breakout [[:field-id 17]]}
                       :parameters [{:hash   "abc123"
                                     :name   "foo"
                                     :type   "date"
-                                    :target ["dimension" ["field-id" (data/id :users :last_login)]]
+                                    :target [:dimension [:field-id (data/id :users :last_login)]]
                                     :value  "2014-05-10~2014-05-16"}]}))
 
 
-
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                                END-TO-END TESTS                                                |
 ;;; +----------------------------------------------------------------------------------------------------------------+
@@ -129,12 +126,12 @@
     (format-rows-by [int]
       (qp/process-query {:database   (data/id)
                          :type       :query
-                         :query      (data/query checkins
-                                       (ql/aggregation (ql/count)))
+                         :query      {:source-table (data/id :checkins)
+                                      :aggregation  [[:count]]}
                          :parameters [{:hash   "abc123"
                                        :name   "foo"
                                        :type   "date"
-                                       :target ["dimension" ["field-id" (data/id :checkins :date)]]
+                                       :target [:dimension [:field-id (data/id :checkins :date)]]
                                        :value  "2015-04-01~2015-05-01"}]}))))
 
 ;; check that IDs work correctly (passed in as numbers)
@@ -144,12 +141,12 @@
     (format-rows-by [int]
       (qp/process-query {:database   (data/id)
                          :type       :query
-                         :query      (data/query checkins
-                                       (ql/aggregation (ql/count)))
+                         :query      {:source-table (data/id :checkins)
+                                      :aggregation  [[:count]]}
                          :parameters [{:hash   "abc123"
                                        :name   "foo"
-                                       :type   "number"
-                                       :target ["dimension" ["field-id" (data/id :checkins :id)]]
+                                       :type   :number
+                                       :target [:dimension [:field-id (data/id :checkins :id)]]
                                        :value  100}]}))))
 
 ;; check that IDs work correctly (passed in as strings, as the frontend is wont to do; should get converted)
@@ -159,23 +156,22 @@
     (format-rows-by [int]
       (qp/process-query {:database   (data/id)
                          :type       :query
-                         :query      (data/query checkins
-                                       (ql/aggregation (ql/count)))
+                         :query      {:source-table (data/id :checkins)
+                                      :aggregation  [[:count]]}
                          :parameters [{:hash   "abc123"
                                        :name   "foo"
-                                       :type   "number"
-                                       :target ["dimension" ["field-id" (data/id :checkins :id)]]
+                                       :type   :number
+                                       :target [:dimension [:field-id (data/id :checkins :id)]]
                                        :value  "100"}]}))))
 
 ;; test that we can injuect a basic `WHERE id = 9` type param (`id` type)
 (datasets/expect-with-engines params-test-engines
   [[9 "Nils Gotam"]]
   (format-rows-by [int str]
-    (let [inner-query (data/query users)
-          outer-query (-> (data/wrap-inner-query inner-query)
+    (let [outer-query (-> (data/mbql-query users)
                           (assoc :parameters [{:name   "id"
                                                :type   "id"
-                                               :target ["field-id" (data/id :users :id)]
+                                               :target [:field-id (data/id :users :id)]
                                                :value  9}]))]
       (rows (qp/process-query outer-query)))))
 
@@ -183,12 +179,11 @@
 (datasets/expect-with-engines params-test-engines
   [[6]]
   (format-rows-by [int]
-    (let [inner-query (data/query venues
-                        (ql/aggregation (ql/count)))
-          outer-query (-> (data/wrap-inner-query inner-query)
+    (let [outer-query (-> (data/mbql-query venues
+                           {:aggregation [[:count]]})
                           (assoc :parameters [{:name   "price"
-                                               :type   "category"
-                                               :target ["field-id" (data/id :venues :price)]
+                                               :type   :category
+                                               :target [:field-id (data/id :venues :price)]
                                                :value  4}]))]
       (rows (qp/process-query outer-query)))))
 
@@ -199,12 +194,11 @@
 (datasets/expect-with-engines params-test-engines
   [[19]]
   (format-rows-by [int]
-    (let [inner-query (data/query venues
-                        (ql/aggregation (ql/count)))
-          outer-query (-> (data/wrap-inner-query inner-query)
+    (let [outer-query (-> (data/mbql-query venues
+                           {:aggregation [[:count]]})
                           (assoc :parameters [{:name   "price"
-                                               :type   "category"
-                                               :target ["field-id" (data/id :venues :price)]
+                                               :type   :category
+                                               :target [:field-id (data/id :venues :price)]
                                                :value  [3 4]}]))]
       (rows (qp/process-query outer-query)))))
 
@@ -217,12 +211,11 @@
                 "FROM \"PUBLIC\".\"VENUES\" "
                 "WHERE (\"PUBLIC\".\"VENUES\".\"PRICE\" = 3 OR \"PUBLIC\".\"VENUES\".\"PRICE\" = 4)")
    :params nil}
-  (let [inner-query (data/query venues
-                      (ql/aggregation (ql/count)))
-        outer-query (-> (data/wrap-inner-query inner-query)
+  (let [outer-query (-> (data/mbql-query venues
+                          {:aggregation [[:count]]})
                         (assoc :parameters [{:name   "price"
-                                             :type   "category"
-                                             :target ["field-id" (data/id :venues :price)]
+                                             :type   :category
+                                             :target [:field-id (data/id :venues :price)]
                                              :value  [3 4]}]))]
     (-> (qp/process-query outer-query)
         :data :native_form)))
@@ -237,21 +230,20 @@
             (du/->Timestamp #inst "2014-06-30")
             (du/->Timestamp #inst "2015-06-01")
             (du/->Timestamp #inst "2015-06-30")]}
-  (let [inner-query (data/query checkins
-                      (ql/aggregation (ql/count)))
-        outer-query (-> (data/wrap-inner-query inner-query)
+  (let [outer-query (-> (data/mbql-query checkins
+                          {:aggregation [[:count]]})
                         (assoc :parameters [{:name   "date"
                                              :type   "date/month"
-                                             :target ["field-id" (data/id :checkins :date)]
+                                             :target [:field-id (data/id :checkins :date)]
                                              :value  ["2014-06" "2015-06"]}]))]
     (-> (qp/process-query outer-query)
         :data :native_form)))
 
 ;; make sure that "ID" type params get converted to numbers when appropriate
 (expect
-  [:= ["field-id" (data/id :venues :id)] 1]
-  (#'mbql-params/build-filter-clause {:type   "id"
-                                      :target ["dimension" ["field-id" (data/id :venues :id)]]
+  [:= [:field-id (data/id :venues :id)] 1]
+  (#'mbql-params/build-filter-clause {:type   :id
+                                      :target [:dimension [:field-id (data/id :venues :id)]]
                                       :slug   "venue_id"
                                       :value  "1"
                                       :name   "Venue ID"}))
diff --git a/test/metabase/query_processor/middleware/parameters/sql_test.clj b/test/metabase/query_processor/middleware/parameters/sql_test.clj
index bb775402c848cce48776b47684b51bf133edecf1..bddb98f46a76e756b7b6003ecd50541c5cb25ca5 100644
--- a/test/metabase/query_processor/middleware/parameters/sql_test.clj
+++ b/test/metabase/query_processor/middleware/parameters/sql_test.clj
@@ -6,24 +6,26 @@
              [driver :as driver]
              [query-processor :as qp]
              [query-processor-test :as qpt :refer [first-row format-rows-by]]]
+            [metabase.mbql.normalize :as normalize]
             [metabase.models.database :refer [Database]]
-            [metabase.util.date :as du]
-            [metabase.query-processor.middleware.parameters.sql :as sql :refer :all]
+            [metabase.query-processor.interface :as qp.i]
+            [metabase.query-processor.middleware.parameters.sql :as sql]
             [metabase.test
              [data :as data]
              [util :as tu]]
             [metabase.test.data
              [datasets :as datasets]
              [generic-sql :as generic-sql]]
+            [metabase.util.date :as du]
             [toucan.db :as db]))
 
-;;; ------------------------------------------ basic parser tests ------------------------------------------
+;;; ----------------------------------------------- basic parser tests -----------------------------------------------
 
 (defn- parse-template
   ([sql]
    (parse-template sql {}))
   ([sql param-key->value]
-   (binding [metabase.query-processor.middleware.parameters.sql/*driver* (driver/engine->driver :h2)]
+   (binding [qp.i/*driver* (driver/engine->driver :h2)]
      (#'sql/parse-template sql param-key->value))))
 
 (expect
@@ -34,12 +36,12 @@
 (expect
   {:query "select * from foo where bar=?"
    :params ["foo"]}
-  (parse-template "select * from foo where bar={{baz}}" {:baz "foo"}))
+  (parse-template "select * from foo where bar={{baz}}" {"baz" "foo"}))
 
 (expect
   {:query "select * from foo where bar = ?"
    :params ["foo"]}
-  (parse-template "select * from foo [[where bar = {{baz}} ]]" {:baz "foo"}))
+  (parse-template "select * from foo [[where bar = {{baz}} ]]" {"baz" "foo"}))
 
 ;; Multiple optional clauses, all present
 (expect
@@ -49,7 +51,7 @@
                        "[[and bar2 = {{baz}}]] "
                        "[[and bar3 = {{baz}}]] "
                        "[[and bar4 = {{baz}}]]")
-                  {:baz "foo"}))
+                  {"baz" "foo"}))
 
 ;; Multiple optional clauses, none present
 (expect
@@ -59,13 +61,13 @@
                        "[[and bar2 = {{none}}]] "
                        "[[and bar3 = {{none}}]] "
                        "[[and bar4 = {{none}}]]")
-                  {:baz "foo"}))
+                  {"baz" "foo"}))
 
 (expect
   {:query "select * from foobars  where foobars.id in (string_to_array(?, ',')::integer[])"
    :params ["foo"]}
   (parse-template "select * from foobars [[ where foobars.id in (string_to_array({{foobar_id}}, ',')::integer[]) ]]"
-                  {:foobar_id "foo"}))
+                  {"foobar_id" "foo"}))
 
 (expect
   {:query (str "SELECT [test_data.checkins.venue_id] AS [venue_id], "
@@ -89,25 +91,26 @@
 ;; Testing that invalid/unterminated template params/clauses throw an exception
 (expect
   java.lang.IllegalArgumentException
-  (parse-template "select * from foo [[where bar = {{baz}} " {:baz "foo"}))
+  (parse-template "select * from foo [[where bar = {{baz}} " {"baz" "foo"}))
 
 (expect
   java.lang.IllegalArgumentException
-  (parse-template "select * from foo [[where bar = {{baz]]" {:baz "foo"}))
+  (parse-template "select * from foo [[where bar = {{baz]]" {"baz" "foo"}))
 
 (expect
   java.lang.IllegalArgumentException
-  (parse-template "select * from foo {{bar}} {{baz" {:bar "foo" :baz "foo"}))
+  (parse-template "select * from foo {{bar}} {{baz" {"bar" "foo", "baz" "foo"}))
 
 (expect
   java.lang.IllegalArgumentException
-  (parse-template "select * from foo [[clause 1 {{bar}}]] [[clause 2" {:bar "foo"}))
+  (parse-template "select * from foo [[clause 1 {{bar}}]] [[clause 2" {"bar" "foo"}))
+
 
 ;;; ------------------------------------------ simple substitution -- {{x}} ------------------------------------------
 
 (defn- substitute {:style/indent 1} [sql params]
   ;; apparently you can still bind private dynamic vars
-  (binding [metabase.query-processor.middleware.parameters.sql/*driver* (driver/engine->driver :h2)]
+  (binding [qp.i/*driver* (driver/engine->driver :h2)]
     ((resolve 'metabase.query-processor.middleware.parameters.sql/expand-query-params)
      {:query sql}
      (into {} (for [[k v] params]
@@ -117,7 +120,7 @@
   {:query  "SELECT * FROM bird_facts WHERE toucans_are_cool = TRUE"
    :params []}
   (substitute "SELECT * FROM bird_facts WHERE toucans_are_cool = {{toucans_are_cool}}"
-    {:toucans_are_cool true}))
+    {"toucans_are_cool" true}))
 
 (expect Exception
   (substitute "SELECT * FROM bird_facts WHERE toucans_are_cool = {{toucans_are_cool}}"
@@ -127,11 +130,11 @@
   {:query  "SELECT * FROM bird_facts WHERE toucans_are_cool = TRUE AND bird_type = ?"
    :params ["toucan"]}
   (substitute "SELECT * FROM bird_facts WHERE toucans_are_cool = {{toucans_are_cool}} AND bird_type = {{bird_type}}"
-    {:toucans_are_cool true, :bird_type "toucan"}))
+    {"toucans_are_cool" true, "bird_type" "toucan"}))
 
 (expect Exception
   (substitute "SELECT * FROM bird_facts WHERE toucans_are_cool = {{toucans_are_cool}} AND bird_type = {{bird_type}}"
-    {:toucans_are_cool true}))
+    {"toucans_are_cool" true}))
 
 ;;; ---------------------------------- optional substitution -- [[ ... {{x}} ... ]] ----------------------------------
 
@@ -139,44 +142,44 @@
   {:query  "SELECT * FROM bird_facts WHERE toucans_are_cool = TRUE"
    :params []}
   (substitute "SELECT * FROM bird_facts [[WHERE toucans_are_cool = {{toucans_are_cool}}]]"
-    {:toucans_are_cool true}))
+    {"toucans_are_cool" true}))
 
 (expect
   {:query  "SELECT * FROM bird_facts WHERE toucans_are_cool = TRUE"
    :params []}
   (substitute "SELECT * FROM bird_facts [[WHERE toucans_are_cool = {{ toucans_are_cool }}]]"
-    {:toucans_are_cool true}))
+    {"toucans_are_cool" true}))
 
 (expect
   {:query  "SELECT * FROM bird_facts WHERE toucans_are_cool = TRUE"
    :params []}
   (substitute "SELECT * FROM bird_facts [[WHERE toucans_are_cool = {{toucans_are_cool }}]]"
-    {:toucans_are_cool true}))
+    {"toucans_are_cool" true}))
 
 (expect
   {:query  "SELECT * FROM bird_facts WHERE toucans_are_cool = TRUE"
    :params []}
   (substitute "SELECT * FROM bird_facts [[WHERE toucans_are_cool = {{ toucans_are_cool}}]]"
-    {:toucans_are_cool true}))
+    {"toucans_are_cool" true}))
 
 (expect
   {:query  "SELECT * FROM bird_facts WHERE toucans_are_cool = TRUE"
    :params []}
   (substitute "SELECT * FROM bird_facts [[WHERE toucans_are_cool = {{toucans_are_cool_2}}]]"
-    {:toucans_are_cool_2 true}))
+    {"toucans_are_cool_2" true}))
 
 (expect
   {:query  "SELECT * FROM bird_facts WHERE toucans_are_cool = TRUE AND bird_type = 'toucan'"
    :params []}
   (substitute "SELECT * FROM bird_facts [[WHERE toucans_are_cool = {{toucans_are_cool}} AND bird_type = 'toucan']]"
-    {:toucans_are_cool true}))
+    {"toucans_are_cool" true}))
 
 ;; Two parameters in an optional
 (expect
   {:query  "SELECT * FROM bird_facts WHERE toucans_are_cool = TRUE AND bird_type = ?"
    :params ["toucan"]}
   (substitute "SELECT * FROM bird_facts [[WHERE toucans_are_cool = {{toucans_are_cool}} AND bird_type = {{bird_type}}]]"
-    {:toucans_are_cool true, :bird_type "toucan"}))
+    {"toucans_are_cool" true, "bird_type" "toucan"}))
 
 (expect
   {:query  "SELECT * FROM bird_facts"
@@ -188,38 +191,38 @@
   {:query  "SELECT * FROM toucanneries WHERE TRUE AND num_toucans > 5"
    :params []}
   (substitute "SELECT * FROM toucanneries WHERE TRUE [[AND num_toucans > {{num_toucans}}]]"
-    {:num_toucans 5}))
+    {"num_toucans" 5}))
 
 ;; make sure nil gets substituted in as `NULL`
 (expect
   {:query  "SELECT * FROM toucanneries WHERE TRUE AND num_toucans > NULL"
    :params []}
   (substitute "SELECT * FROM toucanneries WHERE TRUE [[AND num_toucans > {{num_toucans}}]]"
-    {:num_toucans nil}))
+    {"num_toucans" nil}))
 
 (expect
   {:query  "SELECT * FROM toucanneries WHERE TRUE AND num_toucans > TRUE"
    :params []}
   (substitute "SELECT * FROM toucanneries WHERE TRUE [[AND num_toucans > {{num_toucans}}]]"
-    {:num_toucans true}))
+    {"num_toucans" true}))
 
 (expect
   {:query  "SELECT * FROM toucanneries WHERE TRUE AND num_toucans > FALSE"
    :params []}
   (substitute "SELECT * FROM toucanneries WHERE TRUE [[AND num_toucans > {{num_toucans}}]]"
-    {:num_toucans false}))
+    {"num_toucans" false}))
 
 (expect
   {:query  "SELECT * FROM toucanneries WHERE TRUE AND num_toucans > ?"
    :params ["abc"]}
   (substitute "SELECT * FROM toucanneries WHERE TRUE [[AND num_toucans > {{num_toucans}}]]"
-    {:num_toucans "abc"}))
+    {"num_toucans" "abc"}))
 
 (expect
   {:query  "SELECT * FROM toucanneries WHERE TRUE AND num_toucans > ?"
    :params ["yo' mama"]}
   (substitute "SELECT * FROM toucanneries WHERE TRUE [[AND num_toucans > {{num_toucans}}]]"
-    {:num_toucans "yo' mama"}))
+    {"num_toucans" "yo' mama"}))
 
 (expect
   {:query  "SELECT * FROM toucanneries WHERE TRUE"
@@ -231,19 +234,19 @@
   {:query  "SELECT * FROM toucanneries WHERE TRUE AND num_toucans > 2 AND total_birds > 5"
    :params []}
   (substitute "SELECT * FROM toucanneries WHERE TRUE [[AND num_toucans > {{num_toucans}}]] [[AND total_birds > {{total_birds}}]]"
-    {:num_toucans 2, :total_birds 5}))
+    {"num_toucans" 2, "total_birds" 5}))
 
 (expect
   {:query  "SELECT * FROM toucanneries WHERE TRUE  AND total_birds > 5"
    :params []}
   (substitute "SELECT * FROM toucanneries WHERE TRUE [[AND num_toucans > {{num_toucans}}]] [[AND total_birds > {{total_birds}}]]"
-    {:total_birds 5}))
+    {"total_birds" 5}))
 
 (expect
   {:query  "SELECT * FROM toucanneries WHERE TRUE AND num_toucans > 3"
    :params []}
   (substitute "SELECT * FROM toucanneries WHERE TRUE [[AND num_toucans > {{num_toucans}}]] [[AND total_birds > {{total_birds}}]]"
-    {:num_toucans 3}))
+    {"num_toucans" 3}))
 
 (expect
   {:query  "SELECT * FROM toucanneries WHERE TRUE"
@@ -255,25 +258,25 @@
   {:query  "SELECT * FROM toucanneries WHERE bird_type = ? AND num_toucans > 2 AND total_birds > 5"
    :params ["toucan"]}
   (substitute "SELECT * FROM toucanneries WHERE bird_type = {{bird_type}} [[AND num_toucans > {{num_toucans}}]] [[AND total_birds > {{total_birds}}]]"
-    {:bird_type "toucan", :num_toucans 2, :total_birds 5}))
+    {"bird_type" "toucan", "num_toucans" 2, "total_birds" 5}))
 
 (expect
   Exception
   (substitute "SELECT * FROM toucanneries WHERE bird_type = {{bird_type}} [[AND num_toucans > {{num_toucans}}]] [[AND total_birds > {{total_birds}}]]"
-    {:num_toucans 2, :total_birds 5}))
+    {"num_toucans" 2, "total_birds" 5}))
 
 (expect
   {:query  "SELECT * FROM toucanneries WHERE TRUE AND num_toucans > 5 AND num_toucans < 5"
    :params []}
   (substitute "SELECT * FROM toucanneries WHERE TRUE [[AND num_toucans > {{num_toucans}}]] [[AND num_toucans < {{num_toucans}}]]"
-    {:num_toucans 5}))
+    {"num_toucans" 5}))
 
 ;; Make sure that substiutions still work if the subsitution contains brackets inside it (#3657)
 (expect
   {:query  "select * from foobars  where foobars.id in (string_to_array(100, ',')::integer[])"
    :params []}
   (substitute "select * from foobars [[ where foobars.id in (string_to_array({{foobar_id}}, ',')::integer[]) ]]"
-    {:foobar_id 100}))
+    {"foobar_id" 100}))
 
 
 ;;; -------------------------------------------- tests for value-for-tag ---------------------------------------------
@@ -281,228 +284,234 @@
 ;; variable -- specified
 (expect
   "2"
-  (#'sql/value-for-tag {:name "id", :display_name "ID", :type "text", :required true, :default "100"}
-                       [{:type "category", :target ["variable" ["template-tag" "id"]], :value "2"}]))
+  (#'sql/value-for-tag {:name "id", :display-name "ID", :type :text, :required true, :default "100"}
+                       [{:type :category, :target [:variable [:template-tag "id"]], :value "2"}]))
 
 ;; variable -- unspecified
 (expect
   #metabase.query_processor.middleware.parameters.sql.NoValue{}
-  (#'sql/value-for-tag {:name "id", :display_name "ID", :type "text"} nil))
+  (#'sql/value-for-tag {:name "id", :display-name "ID", :type :text} nil))
 
 ;; variable -- default
 (expect
   "100"
-  (#'sql/value-for-tag {:name "id", :display_name "ID", :type "text", :required true, :default "100"} nil))
+  (#'sql/value-for-tag {:name "id", :display-name "ID", :type :text, :required true, :default "100"} nil))
 
 ;; dimension -- specified
 (expect
   {:field {:name      "DATE"
            :parent_id nil
-           :table_id  (data/id :checkins)}
-   :param {:type   "date/range"
-           :target ["dimension" ["template-tag" "checkin_date"]]
+           :table_id  (data/id :checkins)
+           :base_type :type/Date}
+   :param {:type   :date/range
+           :target [:dimension [:template-tag "checkin_date"]]
            :value  "2015-04-01~2015-05-01"}}
-  (into {} (#'sql/value-for-tag {:name "checkin_date", :display_name "Checkin Date", :type "dimension", :dimension ["field-id" (data/id :checkins :date)]}
-                                [{:type "date/range", :target ["dimension" ["template-tag" "checkin_date"]], :value "2015-04-01~2015-05-01"}])))
+  (into {} (#'sql/value-for-tag {:name "checkin_date", :display-name "Checkin Date", :type :dimension, :dimension [:field-id (data/id :checkins :date)]}
+                                [{:type :date/range, :target [:dimension [:template-tag "checkin_date"]], :value "2015-04-01~2015-05-01"}])))
 
 ;; dimension -- unspecified
 (expect
   {:field {:name      "DATE"
            :parent_id nil
-           :table_id  (data/id :checkins)}
+           :table_id  (data/id :checkins)
+           :base_type :type/Date}
    :param nil}
-  (into {} (#'sql/value-for-tag {:name "checkin_date", :display_name "Checkin Date", :type "dimension", :dimension ["field-id" (data/id :checkins :date)]}
+  (into {} (#'sql/value-for-tag {:name "checkin_date", :display-name "Checkin Date", :type :dimension, :dimension [:field-id (data/id :checkins :date)]}
                                 nil)))
 
 ;; multiple values for the same tag should return a vector with multiple params instead of a single param
 (expect
   {:field {:name      "DATE"
            :parent_id nil
-           :table_id  (data/id :checkins)}
-   :param [{:type   "date/range"
-            :target ["dimension" ["template-tag" "checkin_date"]]
+           :table_id  (data/id :checkins)
+           :base_type :type/Date}
+   :param [{:type   :date/range
+            :target [:dimension [:template-tag "checkin_date"]]
             :value  "2015-01-01~2016-09-01"}
-           {:type   "date/single"
-            :target ["dimension" ["template-tag" "checkin_date"]]
+           {:type   :date/single
+            :target [:dimension [:template-tag "checkin_date"]]
             :value  "2015-07-01"}]}
-  (into {} (#'sql/value-for-tag {:name "checkin_date", :display_name "Checkin Date", :type "dimension", :dimension ["field-id" (data/id :checkins :date)]}
-                                [{:type "date/range",  :target ["dimension" ["template-tag" "checkin_date"]], :value "2015-01-01~2016-09-01"}
-                                 {:type "date/single", :target ["dimension" ["template-tag" "checkin_date"]], :value "2015-07-01"}])))
+  (into {} (#'sql/value-for-tag {:name "checkin_date", :display-name "Checkin Date", :type :dimension, :dimension [:field-id (data/id :checkins :date)]}
+                                [{:type :date/range,  :target [:dimension [:template-tag "checkin_date"]], :value "2015-01-01~2016-09-01"}
+                                 {:type :date/single, :target [:dimension [:template-tag "checkin_date"]], :value "2015-07-01"}])))
 
 
 ;;; ------------------------------------------- expansion tests: variables -------------------------------------------
 
 (defn- expand* [query]
   (qpt/with-h2-db-timezone
-    (-> (expand (assoc query :driver (driver/engine->driver :h2)))
+    (-> (sql/expand (assoc (normalize/normalize query) :driver (driver/engine->driver :h2)))
         :native
-        (select-keys [:query :params :template_tags]))))
+        (select-keys [:query :params :template-tags]))))
 
 ;; unspecified optional param
 (expect
   {:query         "SELECT * FROM orders ;"
    :params        []
-   :template_tags {:id {:name "id", :display_name "ID", :type "number"}}}
+   :template-tags {"id" {:name "id", :display-name "ID", :type :number}}}
   (expand* {:native     {:query         "SELECT * FROM orders [[WHERE id = {{id}}]];"
-                         :template_tags {:id {:name "id", :display_name "ID", :type "number"}}}
+                         :template-tags {"id" {:name "id", :display-name "ID", :type :number}}}
             :parameters []}))
 
 ;; unspecified *required* param
 (expect
   Exception
-  (expand {:native     {:query "SELECT * FROM orders [[WHERE id = {{id}}]];"
-                        :template_tags {:id {:name "id", :display_name "ID", :type "number", :required true}}}
-           :parameters []}))
+  (sql/expand {:native     {:query "SELECT * FROM orders [[WHERE id = {{id}}]];"
+                            :template-tags {"id" {:name "id", :display-name "ID", :type :number, :required true}}}
+               :parameters []}))
 
 ;; default value
 (expect
   {:query         "SELECT * FROM orders WHERE id = 100;"
    :params        []
-   :template_tags {:id {:name "id", :display_name "ID", :type "number", :required true, :default "100"}}}
+   :template-tags {"id" {:name "id", :display-name "ID", :type :number, :required true, :default "100"}}}
   (expand* {:native     {:query         "SELECT * FROM orders WHERE id = {{id}};"
-                         :template_tags {:id {:name "id", :display_name "ID", :type "number", :required true, :default "100"}}}
+                         :template-tags {"id" {:name "id", :display-name "ID", :type :number, :required true, :default "100"}}}
             :parameters []}))
 
 ;; specified param (numbers)
 (expect
   {:query         "SELECT * FROM orders WHERE id = 2;"
    :params        []
-   :template_tags {:id {:name "id", :display_name "ID", :type "number", :required true, :default "100"}}}
+   :template-tags {"id" {:name "id", :display-name "ID", :type :number, :required true, :default "100"}}}
   (expand* {:native     {:query         "SELECT * FROM orders WHERE id = {{id}};"
-                         :template_tags {:id {:name "id", :display_name "ID", :type "number", :required true, :default "100"}}}
-            :parameters [{:type "category", :target ["variable" ["template-tag" "id"]], :value "2"}]}))
+                         :template-tags {"id" {:name "id", :display-name "ID", :type :number, :required true, :default "100"}}}
+            :parameters [{:type "category", :target [:variable [:template-tag "id"]], :value "2"}]}))
 
 ;; specified param (date/single)
 (expect
   {:query         "SELECT * FROM orders WHERE created_at > ?;"
    :params        [#inst "2016-07-19T00:00:00.000000000-00:00"]
-   :template_tags {:created_at {:name "created_at", :display_name "Created At", :type "date"}}}
+   :template-tags {"created_at" {:name "created_at", :display-name "Created At", :type :date}}}
   (expand* {:native     {:query         "SELECT * FROM orders WHERE created_at > {{created_at}};"
-                         :template_tags {:created_at {:name "created_at", :display_name "Created At", :type "date"}}}
-            :parameters [{:type "date/single", :target ["variable" ["template-tag" "created_at"]], :value "2016-07-19"}]}))
+                         :template-tags {"created_at" {:name "created_at", :display-name "Created At", :type "date"}}}
+            :parameters [{:type :date/single, :target [:variable [:template-tag "created_at"]], :value "2016-07-19"}]}))
 
 ;; specified param (text)
 (expect
   {:query         "SELECT * FROM products WHERE category = ?;"
    :params        ["Gizmo"]
-   :template_tags {:category {:name "category", :display_name "Category", :type "text"}}}
+   :template-tags {"category" {:name "category", :display-name "Category", :type :text}}}
   (expand* {:native     {:query         "SELECT * FROM products WHERE category = {{category}};"
-                         :template_tags {:category {:name "category", :display_name "Category", :type "text"}}}
-            :parameters [{:type "category", :target ["variable" ["template-tag" "category"]], :value "Gizmo"}]}))
+                         :template-tags {"category" {:name "category", :display-name "Category", :type :text}}}
+            :parameters [{:type "category", :target [:variable [:template-tag "category"]], :value "Gizmo"}]}))
 
 
 ;;; ------------------------------------------ expansion tests: dimensions -------------------------------------------
 
 (defn- expand-with-dimension-param [dimension-param]
   (with-redefs [t/now (constantly (t/date-time 2016 06 07 12 0 0))]
-    (-> {:native     {:query "SELECT * FROM checkins WHERE {{date}};"
-                      :template_tags {:date {:name "date", :display_name "Checkin Date", :type "dimension", :dimension ["field-id" (data/id :checkins :date)]}}}
+    (-> {:native     {:query         "SELECT * FROM checkins WHERE {{date}};"
+                      :template-tags {"date" {:name         "date"
+                                              :display-name "Checkin Date"
+                                              :type         :dimension
+                                              :dimension    [:field-id (data/id :checkins :date)]}}}
          :parameters (when dimension-param
-                       [(merge {:target ["dimension" ["template-tag" "date"]]}
+                       [(merge {:target [:dimension [:template-tag "date"]]}
                                dimension-param)])}
         expand*
-        (dissoc :template_tags))))
+        (dissoc :template-tags))))
 
 ;; dimension (date/single)
 (expect
   {:query  "SELECT * FROM checkins WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) = ?;"
    :params [#inst "2016-07-01T00:00:00.000000000-00:00"]}
-  (expand-with-dimension-param {:type "date/single", :value "2016-07-01"}))
+  (expand-with-dimension-param {:type :date/single, :value "2016-07-01"}))
 
 ;; dimension (date/range)
 (expect
   {:query  "SELECT * FROM checkins WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) BETWEEN ? AND ?;"
    :params [#inst "2016-07-01T00:00:00.000000000-00:00"
             #inst "2016-08-01T00:00:00.000000000-00:00"]}
-  (expand-with-dimension-param {:type "date/range", :value "2016-07-01~2016-08-01"}))
+  (expand-with-dimension-param {:type :date/range, :value "2016-07-01~2016-08-01"}))
 
 ;; dimension (date/month-year)
 (expect
   {:query  "SELECT * FROM checkins WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) BETWEEN ? AND ?;"
    :params [#inst "2016-07-01T00:00:00.000000000-00:00"
             #inst "2016-07-31T00:00:00.000000000-00:00"]}
-  (expand-with-dimension-param {:type "date/month-year", :value "2016-07"}))
+  (expand-with-dimension-param {:type :date/month-year, :value "2016-07"}))
 
 ;; dimension (date/quarter-year)
 (expect
   {:query  "SELECT * FROM checkins WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) BETWEEN ? AND ?;"
    :params [#inst "2016-01-01T00:00:00.000000000-00:00"
             #inst "2016-03-31T00:00:00.000000000-00:00"]}
-  (expand-with-dimension-param {:type "date/quarter-year", :value "Q1-2016"}))
+  (expand-with-dimension-param {:type :date/quarter-year, :value "Q1-2016"}))
 
 ;; dimension (date/all-options, before)
 (expect
   {:query  "SELECT * FROM checkins WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) < ?;"
    :params [#inst "2016-07-01T00:00:00.000000000-00:00"]}
-  (expand-with-dimension-param {:type "date/all-options", :value "~2016-07-01"}))
+  (expand-with-dimension-param {:type :date/all-options, :value "~2016-07-01"}))
 
 ;; dimension (date/all-options, after)
 (expect
   {:query  "SELECT * FROM checkins WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) > ?;"
    :params [#inst "2016-07-01T00:00:00.000000000-00:00"]}
-  (expand-with-dimension-param {:type "date/all-options", :value "2016-07-01~"}))
+  (expand-with-dimension-param {:type :date/all-options, :value "2016-07-01~"}))
 
 ;; relative date -- "yesterday"
 (expect
   {:query  "SELECT * FROM checkins WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) = ?;"
    :params [#inst "2016-06-06T00:00:00.000000000-00:00"]}
-  (expand-with-dimension-param {:type "date/range", :value "yesterday"}))
+  (expand-with-dimension-param {:type :date/range, :value "yesterday"}))
 
 ;; relative date -- "past7days"
 (expect
   {:query  "SELECT * FROM checkins WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) BETWEEN ? AND ?;"
    :params [#inst "2016-05-31T00:00:00.000000000-00:00"
             #inst "2016-06-06T00:00:00.000000000-00:00"]}
-  (expand-with-dimension-param {:type "date/range", :value "past7days"}))
+  (expand-with-dimension-param {:type :date/range, :value "past7days"}))
 
 ;; relative date -- "past30days"
 (expect
   {:query  "SELECT * FROM checkins WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) BETWEEN ? AND ?;"
    :params [#inst "2016-05-08T00:00:00.000000000-00:00"
             #inst "2016-06-06T00:00:00.000000000-00:00"]}
-  (expand-with-dimension-param {:type "date/range", :value "past30days"}))
+  (expand-with-dimension-param {:type :date/range, :value "past30days"}))
 
 ;; relative date -- "thisweek"
 (expect
   {:query  "SELECT * FROM checkins WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) BETWEEN ? AND ?;"
    :params [#inst "2016-06-05T00:00:00.000000000-00:00"
             #inst "2016-06-11T00:00:00.000000000-00:00"]}
-  (expand-with-dimension-param {:type "date/range", :value "thisweek"}))
+  (expand-with-dimension-param {:type :date/range, :value "thisweek"}))
 
 ;; relative date -- "thismonth"
 (expect
   {:query  "SELECT * FROM checkins WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) BETWEEN ? AND ?;"
    :params [#inst "2016-06-01T00:00:00.000000000-00:00"
             #inst "2016-06-30T00:00:00.000000000-00:00"]}
-  (expand-with-dimension-param {:type "date/range", :value "thismonth"}))
+  (expand-with-dimension-param {:type :date/range, :value "thismonth"}))
 
 ;; relative date -- "thisyear"
 (expect
   {:query  "SELECT * FROM checkins WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) BETWEEN ? AND ?;"
    :params [#inst "2016-01-01T00:00:00.000000000-00:00"
             #inst "2016-12-31T00:00:00.000000000-00:00"]}
-  (expand-with-dimension-param {:type "date/range", :value "thisyear"}))
+  (expand-with-dimension-param {:type :date/range, :value "thisyear"}))
 
 ;; relative date -- "lastweek"
 (expect
   {:query  "SELECT * FROM checkins WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) BETWEEN ? AND ?;"
    :params [#inst "2016-05-29T00:00:00.000000000-00:00"
             #inst "2016-06-04T00:00:00.000000000-00:00"]}
-  (expand-with-dimension-param {:type "date/range", :value "lastweek"}))
+  (expand-with-dimension-param {:type :date/range, :value "lastweek"}))
 
 ;; relative date -- "lastmonth"
 (expect
   {:query  "SELECT * FROM checkins WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) BETWEEN ? AND ?;"
    :params [#inst "2016-05-01T00:00:00.000000000-00:00"
             #inst "2016-05-31T00:00:00.000000000-00:00"]}
-  (expand-with-dimension-param {:type "date/range", :value "lastmonth"}))
+  (expand-with-dimension-param {:type :date/range, :value "lastmonth"}))
 
 ;; relative date -- "lastyear"
 (expect
   {:query  "SELECT * FROM checkins WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) BETWEEN ? AND ?;"
    :params [#inst "2015-01-01T00:00:00.000000000-00:00"
             #inst "2015-12-31T00:00:00.000000000-00:00"]}
-  (expand-with-dimension-param {:type "date/range", :value "lastyear"}))
+  (expand-with-dimension-param {:type :date/range, :value "lastyear"}))
 
 ;; dimension with no value -- just replace with an always true clause (e.g. "WHERE 1 = 1")
 (expect
@@ -514,13 +523,13 @@
 (expect
   {:query  "SELECT * FROM checkins WHERE \"PUBLIC\".\"CHECKINS\".\"DATE\" = 100;"
    :params []}
-  (expand-with-dimension-param {:type "number", :value "100"}))
+  (expand-with-dimension-param {:type :number, :value "100"}))
 
 ;; dimension -- text
 (expect
   {:query  "SELECT * FROM checkins WHERE \"PUBLIC\".\"CHECKINS\".\"DATE\" = ?;"
    :params ["100"]}
-  (expand-with-dimension-param {:type "text", :value "100"}))
+  (expand-with-dimension-param {:type :text, :value "100"}))
 
 
 ;;; -------------------------------------------- "REAL" END-TO-END-TESTS ---------------------------------------------
@@ -554,8 +563,13 @@
     (format-rows-by [int]
       (process-native
         :native     {:query         (format "SELECT COUNT(*) FROM %s WHERE {{checkin_date}}" (checkins-identifier))
-                     :template_tags {:checkin_date {:name "checkin_date", :display_name "Checkin Date", :type "dimension", :dimension ["field-id" (data/id :checkins :date)]}}}
-        :parameters [{:type "date/range", :target ["dimension" ["template-tag" "checkin_date"]], :value "2015-04-01~2015-05-01"}]))))
+                     :template-tags {"checkin_date" {:name         "checkin_date"
+                                                     :display-name "Checkin Date"
+                                                     :type         :dimension
+                                                     :dimension    [:field-id (data/id :checkins :date)]}}}
+        :parameters [{:type   :date/range
+                      :target [:dimension [:template-tag "checkin_date"]]
+                      :value  "2015-04-01~2015-05-01"}]))))
 
 ;; no parameter -- should give us a query with "WHERE 1 = 1"
 (datasets/expect-with-engines sql-parameters-engines
@@ -564,7 +578,10 @@
     (format-rows-by [int]
       (process-native
         :native     {:query         (format "SELECT COUNT(*) FROM %s WHERE {{checkin_date}}" (checkins-identifier))
-                     :template_tags {:checkin_date {:name "checkin_date", :display_name "Checkin Date", :type "dimension", :dimension ["field-id" (data/id :checkins :date)]}}}
+                     :template-tags {"checkin_date" {:name         "checkin_date"
+                                                     :display-name "Checkin Date"
+                                                     :type         :dimension
+                                                     :dimension    [:field-id (data/id :checkins :date)]}}}
         :parameters []))))
 
 ;; test that relative dates work correctly. It should be enough to try just one type of relative date here,
@@ -575,8 +592,11 @@
     (format-rows-by [int]
       (process-native
         :native     {:query         (format "SELECT COUNT(*) FROM %s WHERE {{checkin_date}}" (checkins-identifier))
-                     :template_tags {:checkin_date {:name "checkin_date", :display_name "Checkin Date", :type "dimension", :dimension ["field-id" (data/id :checkins :date)]}}}
-        :parameters [{:type "date/relative", :target ["dimension" ["template-tag" "checkin_date"]], :value "thismonth"}]))))
+                     :template-tags {"checkin_date" {:name         "checkin_date"
+                                                     :display-name "Checkin Date"
+                                                     :type         :dimension
+                                                     :dimension    [:field-id (data/id :checkins :date)]}}}
+        :parameters [{:type :date/relative, :target [:dimension [:template-tag "checkin_date"]], :value "thismonth"}]))))
 
 
 ;; test that multiple filters applied to the same variable combine into `AND` clauses (#3539)
@@ -585,10 +605,13 @@
   (first-row
     (format-rows-by [int]
       (process-native
-       :native     {:query         (format "SELECT COUNT(*) FROM %s WHERE {{checkin_date}}" (checkins-identifier))
-                    :template_tags {:checkin_date {:name "checkin_date", :display_name "Checkin Date", :type "dimension", :dimension ["field-id" (data/id :checkins :date)]}}}
-       :parameters [{:type "date/range",  :target ["dimension" ["template-tag" "checkin_date"]], :value "2015-01-01~2016-09-01"}
-                    {:type "date/single", :target ["dimension" ["template-tag" "checkin_date"]], :value "2015-07-01"}]))))
+        :native     {:query         (format "SELECT COUNT(*) FROM %s WHERE {{checkin_date}}" (checkins-identifier))
+                     :template-tags {"checkin_date" {:name         "checkin_date"
+                                                     :display-name "Checkin Date"
+                                                     :type         :dimension
+                                                     :dimension    [:field-id (data/id :checkins :date)]}}}
+        :parameters [{:type :date/range, :target [:dimension [:template-tag "checkin_date"]], :value "2015-01-01~2016-09-01"}
+                     {:type :date/single, :target [:dimension [:template-tag "checkin_date"]], :value "2015-07-01"}]))))
 
 ;; Test that native dates are parsed with the report timezone (when supported)
 (datasets/expect-with-engines (disj sql-parameters-engines :sqlite)
@@ -611,8 +634,8 @@
                                       "SELECT cast({{date}} as date) from dual"
                                       :else
                                       "SELECT cast({{date}} as date)")
-                     :template_tags {:date {:name "date" :display_name "Date" :type "date" }}}
-        :parameters [{:type "date/single" :target ["variable" ["template-tag" "date"]] :value "2018-04-18"}]))))
+                     :template-tags {"date" {:name "date" :display-name "Date" :type :date}}}
+        :parameters [{:type :date/single :target [:variable [:template-tag "date"]] :value "2018-04-18"}]))))
 
 
 ;;; -------------------------------------------- SQL PARAMETERS 2.0 TESTS --------------------------------------------
@@ -621,53 +644,66 @@
 
 (expect
   {:query         "SELECT count(*) FROM CHECKINS WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) BETWEEN ? AND ?"
-   :template_tags {:created_at {:name "created_at", :display_name "Created At", :type "dimension", :dimension ["field-id" (data/id :checkins :date)]}},
+   :template-tags {"created_at" {:name         "created_at"
+                                :display-name "Created At"
+                                :type         :dimension
+                                :dimension    [:field-id (data/id :checkins :date)]}},
    :params        [#inst "2017-03-01T00:00:00.000000000-00:00"
                    #inst "2017-03-31T00:00:00.000000000-00:00"]}
   (expand* {:native     {:query         "SELECT count(*) FROM CHECKINS WHERE {{created_at}}"
-                         :template_tags {:created_at {:name "created_at", :display_name "Created At",
-                                                      :type "dimension", :dimension ["field-id" (data/id :checkins :date)]}}}
-            :parameters [{:type "date/month-year", :target ["dimension" ["template-tag" "created_at"]], :value "2017-03"}]}))
+                         :template-tags {"created_at" {:name         "created_at"
+                                                      :display-name "Created At"
+                                                      :type         :dimension
+                                                      :dimension    [:field-id (data/id :checkins :date)]}}}
+            :parameters [{:type :date/month-year, :target [:dimension [:template-tag "created_at"]], :value "2017-03"}]}))
 
 (expect
   {:query         "SELECT count(*) FROM ORDERS"
-   :template_tags {:price {:name "price", :display_name "Price", :type "number", :required false}}
+   :template-tags {"price" {:name "price", :display-name "Price", :type :number, :required false}}
    :params        []}
   (expand* {:native {:query         "SELECT count(*) FROM ORDERS [[WHERE price > {{price}}]]"
-                     :template_tags {:price {:name "price", :display_name "Price", :type "number", :required false}}}}))
+                     :template-tags {"price" {:name "price", :display-name "Price", :type :number, :required false}}}}))
 
 (expect
   {:query         "SELECT count(*) FROM ORDERS WHERE price > 100"
-   :template_tags {:price {:name "price", :display_name "Price", :type "number", :required false}}
+   :template-tags {"price" {:name "price", :display-name "Price", :type :number, :required false}}
    :params        []}
-  (expand* {:native      {:query         "SELECT count(*) FROM ORDERS [[WHERE price > {{price}}]]"
-                          :template_tags {:price {:name "price", :display_name "Price", :type "number", :required false}}}
-            :parameters   [{:type "category", :target ["variable" ["template-tag" "price"]], :value "100"}]}))
+  (expand* {:native     {:query         "SELECT count(*) FROM ORDERS [[WHERE price > {{price}}]]"
+                         :template-tags {"price" {:name "price", :display-name "Price", :type :number, :required false}}}
+            :parameters [{:type "category", :target [:variable [:template-tag "price"]], :value "100"}]}))
 
 (expect
   {:query         "SELECT count(*) FROM PRODUCTS WHERE TITLE LIKE ?"
-   :template_tags {:x {:name "x", :display_name "X", :type "text", :required true, :default "%Toucan%"}}
+   :template-tags {"x" {:name "x", :display-name "X", :type :text, :required true, :default "%Toucan%"}}
    :params        ["%Toucan%"]}
   (expand* {:native     {:query         "SELECT count(*) FROM PRODUCTS WHERE TITLE LIKE {{x}}",
-                         :template_tags {:x {:name "x", :display_name "X", :type "text", :required true, :default "%Toucan%"}}},
-            :parameters [{:type "category", :target ["variable" ["template-tag" "x"]]}]}))
+                         :template-tags {"x" {:name         "x"
+                                              :display-name "X"
+                                              :type         :text
+                                              :required     true
+                                              :default      "%Toucan%"}}},
+            :parameters [{:type "category", :target [:variable [:template-tag "x"]]}]}))
 
 ;; make sure that you can use the same parameter multiple times (#4659)
 (expect
   {:query         "SELECT count(*) FROM products WHERE title LIKE ? AND subtitle LIKE ?"
-   :template_tags {:x {:name "x", :display_name "X", :type "text", :required true, :default "%Toucan%"}}
+   :template-tags {"x" {:name "x", :display-name "X", :type :text, :required true, :default "%Toucan%"}}
    :params        ["%Toucan%" "%Toucan%"]}
   (expand* {:native     {:query         "SELECT count(*) FROM products WHERE title LIKE {{x}} AND subtitle LIKE {{x}}",
-                         :template_tags {:x {:name "x", :display_name "X", :type "text", :required true, :default "%Toucan%"}}},
-            :parameters [{:type "category", :target ["variable" ["template-tag" "x"]]}]}))
+                         :template-tags {"x" {:name         "x"
+                                              :display-name "X"
+                                              :type         :text
+                                              :required     true
+                                              :default      "%Toucan%"}}},
+            :parameters [{:type "category", :target [:variable [:template-tag "x"]]}]}))
 
 (expect
   {:query         "SELECT * FROM ORDERS WHERE true  AND ID = ? OR USER_ID = ?"
-   :template_tags {:id {:name "id", :display_name "ID", :type "text"}}
+   :template-tags {"id" {:name "id", :display-name "ID", :type :text}}
    :params        ["2" "2"]}
   (expand* {:native     {:query         "SELECT * FROM ORDERS WHERE true [[ AND ID = {{id}} OR USER_ID = {{id}} ]]"
-                         :template_tags {:id {:name "id", :display_name "ID", :type "text"}}}
-            :parameters [{:type "category", :target ["variable" ["template-tag" "id"]], :value "2"}]}))
+                         :template-tags {"id" {:name "id", :display-name "ID", :type :text}}}
+            :parameters [{:type "category", :target [:variable [:template-tag "id"]], :value "2"}]}))
 
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
@@ -680,10 +716,10 @@
                        "FROM CHECKINS "
                        "WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) BETWEEN ? AND ? "
                        "GROUP BY \"DATE\"")
-   :template_tags {:checkin_date {:name         "checkin_date"
-                                  :display_name "Checkin Date"
-                                  :type         "dimension"
-                                  :dimension    ["field-id" (data/id :checkins :date)]}}
+   :template-tags {"checkin_date" {:name         "checkin_date"
+                                   :display-name "Checkin Date"
+                                   :type         :dimension
+                                   :dimension    [:field-id (data/id :checkins :date)]}}
    :params        [#inst "2017-10-31T00:00:00.000000000-00:00"
                    #inst "2017-11-04T00:00:00.000000000-00:00"]}
   (with-redefs [t/now (constantly (t/date-time 2017 11 05 12 0 0))]
@@ -691,42 +727,42 @@
                                                "FROM CHECKINS "
                                                "WHERE {{checkin_date}} "
                                                "GROUP BY \"DATE\"")
-                           :template_tags {:checkin_date {:name         "checkin_date"
-                                                          :display_name "Checkin Date"
-                                                          :type         "dimension"
-                                                          :dimension    ["field-id" (data/id :checkins :date)]}}}
-              :parameters [{:type   "date/range"
-                            :target ["dimension" ["template-tag" "checkin_date"]]
+                           :template-tags {"checkin_date" {:name         "checkin_date"
+                                                           :display-name "Checkin Date"
+                                                           :type         :dimension
+                                                           :dimension    [:field-id (data/id :checkins :date)]}}}
+              :parameters [{:type   :date/range
+                            :target [:dimension [:template-tag "checkin_date"]]
                             :value  "past5days"}]})))
 
 ;; Make sure defaults values get picked up for field filter clauses
 (expect
-  {:field {:name "DATE", :parent_id nil, :table_id (data/id :checkins)}
-   :param {:type   "date/all-options"
-           :target ["dimension" ["template-tag" "checkin_date"]]
+  {:field {:name "DATE", :parent_id nil, :table_id (data/id :checkins), :base_type :type/Date}
+   :param {:type   :date/all-options
+           :target [:dimension [:template-tag "checkin_date"]]
            :value  "past5days"}}
   (#'sql/dimension-value-for-tag {:name         "checkin_date"
-                                  :display_name "Checkin Date"
-                                  :type         "dimension"
+                                  :display-name "Checkin Date"
+                                  :type         :dimension
                                   :dimension    [:field-id (data/id :checkins :date)]
                                   :default      "past5days"
-                                  :widget_type  "date/all-options"}
+                                  :widget-type  :date/all-options}
                                  nil))
 
 ;; Make sure we can specify the type of a default value for a "Dimension" (Field Filter) by setting the
-;; `:widget_type` key. Check that it works correctly with relative dates...
+;; `:widget-type` key. Check that it works correctly with relative dates...
 (expect
   {:query         (str "SELECT count(*) AS \"count\", \"DATE\" "
                        "FROM CHECKINS "
                        "WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) BETWEEN ? AND ? "
                        "GROUP BY \"DATE\"")
-   :template_tags {:checkin_date
+   :template-tags {"checkin_date"
                    {:name         "checkin_date"
-                    :display_name "Checkin Date"
-                    :type         "dimension"
-                    :dimension    ["field-id" (data/id :checkins :date)]
+                    :display-name "Checkin Date"
+                    :type         :dimension
+                    :dimension    [:field-id (data/id :checkins :date)]
                     :default      "past5days"
-                    :widget_type  "date/all-options"}}
+                    :widget-type  :date/all-options}}
    :params        [#inst "2017-10-31T00:00:00.000000000-00:00"
                    #inst "2017-11-04T00:00:00.000000000-00:00"]}
   (with-redefs [t/now (constantly (t/date-time 2017 11 05 12 0 0))]
@@ -734,12 +770,12 @@
                                            "FROM CHECKINS "
                                            "WHERE {{checkin_date}} "
                                            "GROUP BY \"DATE\"")
-                       :template_tags {:checkin_date {:name         "checkin_date"
-                                                      :display_name "Checkin Date"
-                                                      :type         "dimension"
-                                                      :dimension    ["field-id" (data/id :checkins :date)]
-                                                      :default      "past5days"
-                                                      :widget_type  "date/all-options"}}}})))
+                       :template-tags {"checkin_date" {:name         "checkin_date"
+                                                       :display-name "Checkin Date"
+                                                       :type         :dimension
+                                                       :dimension    [:field-id (data/id :checkins :date)]
+                                                       :default      "past5days"
+                                                       :widget-type  :date/all-options}}}})))
 
 ;; Check that it works with absolute dates as well
 (expect
@@ -747,23 +783,23 @@
                        "FROM CHECKINS "
                        "WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) = ? "
                        "GROUP BY \"DATE\"")
-   :template_tags {:checkin_date {:name         "checkin_date"
-                                  :display_name "Checkin Date"
-                                  :type         "dimension"
-                                  :dimension    ["field-id" (data/id :checkins :date)]
-                                  :default      "2017-11-14"
-                                  :widget_type  "date/all-options"}}
+   :template-tags {"checkin_date" {:name         "checkin_date"
+                                   :display-name "Checkin Date"
+                                   :type         :dimension
+                                   :dimension    [:field-id (data/id :checkins :date)]
+                                   :default      "2017-11-14"
+                                   :widget-type  :date/all-options}}
    :params        [#inst "2017-11-14T00:00:00.000000000-00:00"]}
   (expand* {:native {:query         (str "SELECT count(*) AS \"count\", \"DATE\" "
                                          "FROM CHECKINS "
                                          "WHERE {{checkin_date}} "
                                          "GROUP BY \"DATE\"")
-                     :template_tags {:checkin_date {:name         "checkin_date"
-                                                    :display_name "Checkin Date"
-                                                    :type         "dimension"
-                                                    :dimension    ["field-id" (data/id :checkins :date)]
-                                                    :default      "2017-11-14"
-                                                    :widget_type  "date/all-options"}}}}))
+                     :template-tags {"checkin_date" {:name         "checkin_date"
+                                                     :display-name "Checkin Date"
+                                                     :type         :dimension
+                                                     :dimension    [:field-id (data/id :checkins :date)]
+                                                     :default      "2017-11-14"
+                                                     :widget-type  :date/all-options}}}}))
 
 
 ;;; ------------------------------- Multiple Value Support (comma-separated or array) --------------------------------
@@ -775,11 +811,11 @@
         {:database   (data/id)
          :type       "native"
          :native     {:query         "SELECT * FROM USERS [[where id IN ({{ids_list}})]]"
-                      :template_tags {:ids_list {:name         "ids_list"
-                                                 :display_name "Ids list"
-                                                 :type         "number"}}}
+                      :template-tags {"ids_list" {:name         "ids_list"
+                                                  :display-name "Ids list"
+                                                  :type         :number}}}
          :parameters [{:type   "category"
-                       :target ["variable" ["template-tag" "ids_list"]]
+                       :target [:variable [:template-tag "ids_list"]]
                        :value  "1,2,3"}]})
       :data :native_form :query))
 
@@ -787,31 +823,31 @@
 ;; make sure you can now also pass multiple values in by passing an array of values
 (expect
   {:query         "SELECT * FROM CATEGORIES where name IN (?, ?, ?)"
-   :template_tags {:names_list {:name "names_list", :display_name "Names List", :type "text"}}
+   :template-tags {"names_list" {:name "names_list", :display-name "Names List", :type :text}}
    :params        ["BBQ" "Bakery" "Bar"]}
   (expand*
    {:native     {:query         "SELECT * FROM CATEGORIES [[where name IN ({{names_list}})]]"
-                 :template_tags {:names_list {:name         "names_list"
-                                              :display_name "Names List"
-                                              :type         "text"}}}
+                 :template-tags {"names_list" {:name         "names_list"
+                                               :display-name "Names List"
+                                               :type         :text}}}
     :parameters [{:type   "category"
-                  :target ["variable" ["template-tag" "names_list"]]
+                  :target [:variable [:template-tag "names_list"]]
                   :value  ["BBQ", "Bakery", "Bar"]}]}))
 
 ;; Make sure arrays of values also work for 'field filter' params
 (expect
   {:query         "SELECT * FROM CATEGORIES WHERE \"PUBLIC\".\"USERS\".\"ID\" IN (?, ?, ?)",
-   :template_tags {:names_list {:name         "names_list"
-                                :display_name "Names List"
-                                :type         "dimension"
-                                :dimension    ["field-id" (data/id :users :id)]}}
+   :template-tags {"names_list" {:name         "names_list"
+                                 :display-name "Names List"
+                                 :type         :dimension
+                                 :dimension    [:field-id (data/id :users :id)]}}
    :params        ["BBQ" "Bakery" "Bar"]}
   (expand*
    {:native     {:query         "SELECT * FROM CATEGORIES WHERE {{names_list}}"
-                 :template_tags {:names_list {:name         "names_list"
-                                              :display_name "Names List"
-                                              :type         "dimension"
-                                              :dimension    ["field-id" (data/id :users :id)]}}}
-    :parameters [{:type   "text"
-                  :target ["dimension" ["template-tag" "names_list"]]
+                 :template-tags {"names_list" {:name         "names_list"
+                                               :display-name "Names List"
+                                               :type         :dimension
+                                               :dimension    [:field-id (data/id :users :id)]}}}
+    :parameters [{:type   :text
+                  :target [:dimension [:template-tag "names_list"]]
                   :value  ["BBQ", "Bakery", "Bar"]}]}))
diff --git a/test/metabase/query_processor/middleware/resolve_test.clj b/test/metabase/query_processor/middleware/resolve_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..df54d410271863ae0948dec67414db608ecfa943
--- /dev/null
+++ b/test/metabase/query_processor/middleware/resolve_test.clj
@@ -0,0 +1,66 @@
+(ns metabase.query-processor.middleware.resolve-test
+  (:require [expectations :refer :all]
+            [metabase.models.field :refer [Field]]
+            [metabase.query-processor.middleware
+             [expand :as ql]
+             [resolve :as resolve]]
+            [metabase.test
+             [data :as data]
+             [util :as tu]]
+            [metabase.test.data.dataset-definitions :as defs]
+            [metabase.util :as u]))
+
+(defn- resolved? [field]
+  (not (#'resolve/unresolved-field-id field)))
+
+;; Resolving already resolved fields is a noop
+(expect
+  {:resolved-before? true
+   :fields-same?     true}
+  (data/with-db (data/get-or-create-database! defs/test-data)
+    (let [fields [(Field (u/get-id (data/id :venues :name)))
+                  (Field (u/get-id (data/id :venues :category_id)))]]
+      {:resolved-before? (every? resolved? fields)
+       :fields-same?     (= fields (resolve/resolve-fields-if-needed fields))})))
+
+;; Resolving placholders will return resolved fields
+(expect
+  {:resolved-before? false
+   :resolved-after?  true}
+  (data/with-db (data/get-or-create-database! defs/test-data)
+    (let [field-placeholders [(ql/field-id (data/id :venues :name))
+                              (ql/field-id (data/id :venues :category_id))]]
+      {:resolved-before? (every? resolved? field-placeholders)
+       :resolved-after?  (every? resolved? (resolve/resolve-fields-if-needed field-placeholders))})))
+
+;; Resolving a mixed list of placeholders and fields will only resolve the unresolved-fields
+(expect
+  {:resolved-fields-count   1
+   :unresolved-fields-count 1
+   :all-resolved?           true
+   :resolved-field-same?    true}
+  (data/with-db (data/get-or-create-database! defs/test-data)
+    (let [resolved-field   (Field (u/get-id (data/id :venues :category_id)))
+          both-field-types [(ql/field-id (data/id :venues :name))
+                            resolved-field]
+          result           (resolve/resolve-fields-if-needed both-field-types)]
+      {:resolved-fields-count   (count (filter resolved? both-field-types))
+       :unresolved-fields-count (count (remove resolved? both-field-types))
+       :all-resolved?           (every? resolved? result)
+       :resolved-field-same?    (= resolved-field (second result))})))
+
+;; Resolving the fields should include any relevant dimensions along with the field
+(expect
+  {:field-resolved-before? false
+   :field-resolved-after?  true
+   :dimension-values       [{:dimension-id true, :field-id true, :dimension-name "Foo",
+                              :human-readable-field-id true, :dimension-type :external,
+                              :created-at true, :updated-at true}]}
+  (data/with-db (data/get-or-create-database! defs/test-data)
+    (data/with-data
+      (data/create-venue-category-fk-remapping "Foo")
+      (let [field-with-dimension (ql/field-id (data/id :venues :category_id))
+            result (resolve/resolve-fields-if-needed [field-with-dimension])]
+        {:field-resolved-before? (resolved? field-with-dimension)
+         :field-resolved-after?  (first (map resolved? result))
+         :dimension-values       (tu/boolean-ids-and-timestamps (map :dimensions result))}))))
diff --git a/test/metabase/query_processor/middleware/results_metadata_test.clj b/test/metabase/query_processor/middleware/results_metadata_test.clj
index 301942ad7b933d765b69439bd70d4824425d4fc2..b399450cf62e1c245ad3b981b43a4bdcef54c7b8 100644
--- a/test/metabase/query_processor/middleware/results_metadata_test.clj
+++ b/test/metabase/query_processor/middleware/results_metadata_test.clj
@@ -10,8 +10,12 @@
              [permissions :as perms]
              [permissions-group :as group]]
             [metabase.query-processor.middleware.results-metadata :as results-metadata]
-            [metabase.test.data :as data]
+            [metabase.query-processor.util :as qputil]
+            [metabase.test
+             [data :as data]
+             [util :as tu]]
             [metabase.test.data.users :as users]
+            [metabase.test.mock.util :as mutil]
             [toucan.db :as db]
             [toucan.util.test :as tt]))
 
@@ -23,19 +27,36 @@
 (defn- card-metadata [card]
   (db/select-one-field :result_metadata Card :id (u/get-id card)))
 
+(defn- round-all-decimals'
+  "Defaults `tu/round-all-decimals` to 2 digits"
+  [data]
+  (tu/round-all-decimals 2 data))
+
+(def ^:private default-card-results
+  [{:name         "ID",      :display_name "ID", :base_type "type/Integer",
+    :special_type "type/PK", :fingerprint  (:id mutil/venue-fingerprints)}
+   {:name         "NAME",      :display_name "Name", :base_type "type/Text",
+    :special_type "type/Name", :fingerprint  (:name mutil/venue-fingerprints)}
+   {:name         "PRICE", :display_name "Price", :base_type "type/Integer",
+    :special_type nil,     :fingerprint  (:price mutil/venue-fingerprints)}
+   {:name         "CATEGORY_ID", :display_name "Category ID", :base_type "type/Integer",
+    :special_type nil,           :fingerprint  (:category_id mutil/venue-fingerprints)}
+   {:name         "LATITUDE",      :display_name "Latitude", :base_type "type/Float",
+    :special_type "type/Latitude", :fingerprint  (:latitude mutil/venue-fingerprints)}
+   {:name         "LONGITUDE",      :display_name "Longitude", :base_type "type/Float"
+    :special_type "type/Longitude", :fingerprint  (:longitude mutil/venue-fingerprints)}])
+
+(def ^:private default-card-results-native
+  (update-in default-card-results [3 :fingerprint] assoc :type {:type/Number {:min 2.0, :max 74.0, :avg 29.98}}))
+
 ;; test that Card result metadata is saved after running a Card
 (expect
-  [{:name "ID",          :display_name "ID",          :base_type "type/Integer"}
-   {:name "NAME",        :display_name "Name",        :base_type "type/Text"}
-   {:name "PRICE",       :display_name "Price",       :base_type "type/Integer"}
-   {:name "CATEGORY_ID", :display_name "Category ID", :base_type "type/Integer"}
-   {:name "LATITUDE",    :display_name "Latitude",    :base_type "type/Float"}
-   {:name "LONGITUDE",   :display_name "Longitude",   :base_type "type/Float"}]
+  default-card-results-native
   (tt/with-temp Card [card]
     (qp/process-query (assoc (native-query "SELECT ID, NAME, PRICE, CATEGORY_ID, LATITUDE, LONGITUDE FROM VENUES")
                         :info {:card-id    (u/get-id card)
-                               :query-hash (byte-array 0)}))
-    (card-metadata card)))
+                               :query-hash (qputil/query-hash {})}))
+    (round-all-decimals' (card-metadata card))))
 
 ;; check that using a Card as your source doesn't overwrite the results metadata...
 (expect
@@ -73,17 +94,17 @@
 ;; make sure that queries come back with metadata
 (expect
   {:checksum java.lang.String
-   :columns [{:base_type :type/Integer, :display_name "ID",          :name "ID"}
-             {:base_type :type/Text,    :display_name "Name",        :name "NAME"}
-             {:base_type :type/Integer, :display_name "Price",       :name "PRICE"}
-             {:base_type :type/Integer, :display_name "Category ID", :name "CATEGORY_ID"}
-             {:base_type :type/Float,   :display_name "Latitude",    :name "LATITUDE"}
-             {:base_type :type/Float,   :display_name "Longitude",   :name "LONGITUDE"}]}
+   :columns  (map (fn [col]
+                    (-> col
+                        (update :special_type keyword)
+                        (update :base_type keyword)))
+                  default-card-results-native)}
   (-> (qp/process-query {:database (data/id)
                          :type     :native
                          :native   {:query "SELECT ID, NAME, PRICE, CATEGORY_ID, LATITUDE, LONGITUDE FROM VENUES"}})
       (get-in [:data :results_metadata])
-      (update :checksum class)))
+      (update :checksum class)
+      round-all-decimals'))
 
 ;; make sure that a Card where a DateTime column is broken out by year advertises that column as Text, since you can't
 ;; do datetime breakouts on years
@@ -91,11 +112,16 @@
   [{:base_type    "type/Text"
     :display_name "Date"
     :name         "DATE"
-    :unit         nil}
+    :unit         nil
+    :special_type nil
+    :fingerprint  {:global {:distinct-count 618}, :type {:type/DateTime {:earliest "2013-01-03T00:00:00.000Z"
+                                                                         :latest   "2015-12-29T00:00:00.000Z"}}}}
    {:base_type    "type/Integer"
     :display_name "count"
     :name         "count"
-    :special_type "type/Number"}]
+    :special_type "type/Quantity"
+    :fingerprint  {:global {:distinct-count 3},
+                   :type   {:type/Number {:min 235.0, :max 498.0, :avg 333.33}}}}]
   (tt/with-temp Card [card]
     (qp/process-query {:database (data/id)
                        :type     :query
@@ -103,5 +129,5 @@
                                   :aggregation  [[:count]]
                                   :breakout     [[:datetime-field [:field-id (data/id :checkins :date)] :year]]}
                        :info     {:card-id    (u/get-id card)
-                                  :query-hash (byte-array 0)}})
-    (card-metadata card)))
+                                  :query-hash (qputil/query-hash {})}})
+    (round-all-decimals' (card-metadata card))))
diff --git a/test/metabase/query_processor/qp_middleware_test.clj b/test/metabase/query_processor/qp_middleware_test.clj
index 46673f5c309ca4c853364b72e2a0d02ef32523e9..4153a73a87f7098ba3baff63bb9a4e2536028b5a 100644
--- a/test/metabase/query_processor/qp_middleware_test.clj
+++ b/test/metabase/query_processor/qp_middleware_test.clj
@@ -62,7 +62,7 @@
    :status    :completed
    :data      {:rows           [[1] [1] [1] [1] [1]]
                :rows_truncated 5}}
-  ;; NOTE: the default behavior is to treat the query as :rows type aggregation and use :max-results-bare-rows
+  ;; NOTE: the default behavior is to treat the query as no aggregation and use :max-results-bare-rows
   ((add-row-count-and-status/add-row-count-and-status (constantly {:rows [[1] [1] [1] [1] [1]]}))
     {:constraints {:max-results           10
                    :max-results-bare-rows 5}}))
@@ -71,9 +71,9 @@
   {:row_count      5
    :status         :completed
    :data           {:rows [[1] [1] [1] [1] [1]]}}
-  ;; when we aren't a :rows query the then we use :max-results for our limit
+  ;; when we aren't a no-aggregation query the then we use :max-results for our limit
   ((add-row-count-and-status/add-row-count-and-status (constantly {:rows [[1] [1] [1] [1] [1]]}))
-    {:query       {:aggregation {:aggregation-type :count}}
+    {:query       {:aggregation [[:count]]}
      :constraints {:max-results           10
                    :max-results-bare-rows 5}}))
 
diff --git a/test/metabase/query_processor/util_test.clj b/test/metabase/query_processor/util_test.clj
index 1bb0c29a9fc28564ba54cdb65c574fd007af6a84..fa714b08c74d58b2b85c4964dfb54c2b9f6f4092 100644
--- a/test/metabase/query_processor/util_test.clj
+++ b/test/metabase/query_processor/util_test.clj
@@ -9,15 +9,15 @@
 (expect true  (qputil/mbql-query? {:type "query"}))
 
 ;; query-without-aggregations-or-limits?
-(expect false (qputil/query-without-aggregations-or-limits? {:query {:aggregation [{:aggregation-type :count}]}}))
-(expect true  (qputil/query-without-aggregations-or-limits? {:query {:aggregation [{:aggregation-type :rows}]}}))
-(expect false (qputil/query-without-aggregations-or-limits? {:query {:aggregation [{:aggregation-type :count}]
+(expect false (qputil/query-without-aggregations-or-limits? {:query {:aggregation [[:count]]}}))
+(expect true  (qputil/query-without-aggregations-or-limits? {:query {}}))
+(expect false (qputil/query-without-aggregations-or-limits? {:query {:aggregation [[:count]]
                                                                      :limit       10}}))
-(expect false (qputil/query-without-aggregations-or-limits? {:query {:aggregation [{:aggregation-type :count}]
+(expect false (qputil/query-without-aggregations-or-limits? {:query {:aggregation [[:count]]
                                                                      :page        1}}))
 
 
-;;; ------------------------------------------------------------ Tests for qputil/query-hash ------------------------------------------------------------
+;;; ------------------------------------------ Tests for qputil/query-hash -------------------------------------------
 
 (defn- array= {:style/indent 0}
   ([a b]
@@ -88,7 +88,8 @@
     (qputil/query-hash {:query :abc})
     (qputil/query-hash {:query :abc, :parameters ["ABC"]})))
 
-;; similarly, the presence of a `nil` value for `:constraints` should produce the same hash as not including the key at all
+;; similarly, the presence of a `nil` value for `:constraints` should produce the same hash as not including the key
+;; at all
 (expect
   (array=
     (qputil/query-hash {:query :abc})
@@ -107,63 +108,89 @@
                         :native      {:query "SELECT pg_sleep(15), 2 AS two"}})))
 
 
-;;; ------------------------------------------------------------ Tests for get-normalized and get-in-normalized ------------------------------------------------------------
+(defrecord ^:private TestRecord1 [x])
+(defrecord ^:private TestRecord2 [x])
 
-(expect 2 (qputil/get-normalized {"num_toucans" 2} :num-toucans))
-(expect 2 (qputil/get-normalized {"NUM_TOUCANS" 2} :num-toucans))
-(expect 2 (qputil/get-normalized {"num-toucans" 2} :num-toucans))
-(expect 2 (qputil/get-normalized {:num_toucans 2}  :num-toucans))
-(expect 2 (qputil/get-normalized {:NUM_TOUCANS 2}  :num-toucans))
-(expect 2 (qputil/get-normalized {:num-toucans 2}  :num-toucans))
+(def ^:private test-tree
+  {:a {:aa (TestRecord1. 1)
+       :ab (TestRecord2. 1)}
+   :b (TestRecord1. 1)
+   :c (TestRecord2. 1)
+   :d [1 2 3 4]})
 
+;; Test that we can change only the items matching the `instance?` predicate
 (expect
-  false
-  (qputil/get-normalized {:case-sensitive false} :case-sensitive))
+  (-> test-tree
+      (update-in [:a :aa :x] inc)
+      (update-in [:b :x] inc))
+  (qputil/postwalk-pred #(instance? TestRecord1 %)
+                        #(update % :x inc)
+                        test-tree))
 
+;; If nothing matches, the original tree should be returned
 (expect
-  false
-  (qputil/get-normalized {:case-sensitive false} :case-sensitive true))
+  test-tree
+  (qputil/postwalk-pred set?
+                        #(set (map inc %))
+                        test-tree))
 
+;; We should be able to collect items matching the predicate
 (expect
-  true
-  (qputil/get-normalized {:explodes-database false} :case-sensitive true))
+  [(TestRecord1. 1) (TestRecord1. 1)]
+  (qputil/postwalk-collect #(instance? TestRecord1 %)
+                           identity
+                           test-tree))
 
+;; Not finding any of the items should just return an empty seq
 (expect
-  nil
-  (qputil/get-normalized nil :num-toucans))
+  []
+  (qputil/postwalk-collect set?
+                           identity
+                           test-tree))
 
-(expect 2 (qputil/get-in-normalized {"BIRDS" {"NUM_TOUCANS" 2}} [:birds :num-toucans]))
-(expect 2 (qputil/get-in-normalized {"birds" {"num_toucans" 2}} [:birds :num-toucans]))
-(expect 2 (qputil/get-in-normalized {"birds" {"num-toucans" 2}} [:birds :num-toucans]))
-(expect 2 (qputil/get-in-normalized {:BIRDS  {:NUM_TOUCANS 2}}  [:birds :num-toucans]))
-(expect 2 (qputil/get-in-normalized {:birds  {:num_toucans 2}}  [:birds :num-toucans]))
-(expect 2 (qputil/get-in-normalized {:birds  {:num-toucans 2}}  [:birds :num-toucans]))
+(def ^:private test-inner-map
+  {:test {:value 10}})
 
+;; get-in-query should work for a nested query
 (expect
-  2
-  (qputil/get-in-normalized {:num-toucans 2} [:num-toucans]))
+  10
+  (qputil/get-in-query {:query {:source-query test-inner-map}} [:test :value]))
 
+;; Not currently supported, but get-in-query should work for a double nested query
 (expect
-  nil
-  (qputil/get-in-normalized nil [:birds :num-toucans]))
+  10
+  (qputil/get-in-query {:query {:source-query {:source-query test-inner-map}}} [:test :value]))
 
+;; get-in-query should also work with non-nested queries
 (expect
   10
-  (qputil/get-in-normalized
-   {"dataset_query" {"query" {"source_table" 10}}}
-   [:dataset-query :query :source-table]))
+  (qputil/get-in-query {:query test-inner-map} [:test :value]))
 
-(expect {} (qputil/dissoc-normalized {"NUM_TOUCANS" 3} :num-toucans))
-(expect {} (qputil/dissoc-normalized {"num_toucans" 3} :num-toucans))
-(expect {} (qputil/dissoc-normalized {"num-toucans" 3} :num-toucans))
-(expect {} (qputil/dissoc-normalized {:NUM_TOUCANS 3}  :num-toucans))
-(expect {} (qputil/dissoc-normalized {:num_toucans 3}  :num-toucans))
-(expect {} (qputil/dissoc-normalized {:num-toucans 3}  :num-toucans))
+;; Not providing a `not-found` value should just return nil
+(expect
+  nil
+  (qputil/get-in-query {} [:test]))
+
+;; Providing a `not-found` value should return that
+(let [not-found (gensym)]
+  (expect
+    not-found
+    (qputil/get-in-query {} [:test] not-found)))
+
+(def ^:private updated-test-map
+  {:test {:value 11}})
 
+;; assoc-in-query works with a non-nested query
 (expect
-  {}
-  (qputil/dissoc-normalized {:num-toucans 3, "NUM_TOUCANS" 3, "num_toucans" 3} :num-toucans))
+  {:query updated-test-map}
+  (qputil/assoc-in-query {:query test-inner-map} [:test :value] 11))
 
+;; assoc-in-query works with a nested query
 (expect
-  nil
-  (qputil/dissoc-normalized nil :num-toucans))
+  {:query {:source-query updated-test-map}}
+  (qputil/assoc-in-query {:query {:source-query test-inner-map}} [:test :value] 11))
+
+;; Not supported yet, but assoc-in-query should do the right thing with a double nested query
+(expect
+  {:query {:source-query {:source-query updated-test-map}}}
+  (qputil/assoc-in-query {:query {:source-query {:source-query test-inner-map}}} [:test :value] 11))
diff --git a/test/metabase/query_processor_test.clj b/test/metabase/query_processor_test.clj
index cf013509dd781e233168f07152a37ae97a74f95f..bba015a33133b3ae33075920f87e51bf336ce28a 100644
--- a/test/metabase/query_processor_test.clj
+++ b/test/metabase/query_processor_test.clj
@@ -32,11 +32,13 @@
   (set/difference datasets/all-valid-engines timeseries-engines))
 
 (defn non-timeseries-engines-with-feature
-  "Set of engines that support a given FEATURE."
-  [feature]
-  (set (for [engine non-timeseries-engines
-             :when  (contains? (driver/features (driver/engine->driver engine)) feature)]
-         engine)))
+  "Set of engines that support a given `feature`. If additional features are given, it will ensure all features are
+  supported."
+  [feature & more-features]
+  (let [features (set (cons feature more-features))]
+    (set (for [engine non-timeseries-engines
+               :when  (set/subset? features (driver/features (driver/engine->driver engine)))]
+           engine))))
 
 (defn non-timeseries-engines-without-feature
   "Return a set of all non-timeseries engines (e.g., everything except Druid) that DO NOT support `feature`."
@@ -141,7 +143,7 @@
                   :base_type    (data/id-field-type)
                   :name         (data/format-name "id")
                   :display_name "ID"
-                  :fingerprint  {:global {:distinct-count 15}, :type {:type/Number {:min 1, :max 15, :avg 8.0}}}}
+                  :fingerprint  nil}
      :name       {:special_type :type/Name
                   :base_type    (data/expected-base-type->actual :type/Text)
                   :name         (data/format-name "name")
@@ -178,7 +180,7 @@
                    :base_type    (data/id-field-type)
                    :name         (data/format-name "id")
                    :display_name "ID"
-                   :fingerprint  {:global {:distinct-count 100}, :type {:type/Number {:min 1, :max 100, :avg 50.5}}}}
+                   :fingerprint  nil}
      :category_id {:extra_info   (if (data/fks-supported?)
                                    {:target_table_id (data/id :categories)}
                                    {})
@@ -189,12 +191,14 @@
                    :base_type    (data/expected-base-type->actual :type/Integer)
                    :name         (data/format-name "category_id")
                    :display_name "Category ID"
-                   :fingerprint  {:global {:distinct-count 28}, :type {:type/Number {:min 2, :max 74, :avg 29.98}}}}
+                   :fingerprint  (if (data/fks-supported?)
+                                   {:global {:distinct-count 28}}
+                                   {:global {:distinct-count 28}, :type {:type/Number {:min 2.0, :max 74.0, :avg 29.98}}})}
      :price       {:special_type :type/Category
                    :base_type    (data/expected-base-type->actual :type/Integer)
                    :name         (data/format-name "price")
                    :display_name "Price"
-                   :fingerprint  {:global {:distinct-count 4}, :type {:type/Number {:min 1, :max 4, :avg 2.03}}}}
+                   :fingerprint  {:global {:distinct-count 4}, :type {:type/Number {:min 1.0, :max 4.0, :avg 2.03}}}}
      :longitude   {:special_type :type/Longitude
                    :base_type    (data/expected-base-type->actual :type/Float)
                    :name         (data/format-name "longitude")
@@ -238,7 +242,9 @@
                 :base_type    (data/expected-base-type->actual :type/Integer)
                 :name         (data/format-name "venue_id")
                 :display_name "Venue ID"
-                :fingerprint  {:global {:distinct-count 100}, :type {:type/Number {:min 1, :max 100, :avg 51.965}}}}
+                :fingerprint  (if (data/fks-supported?)
+                                {:global {:distinct-count 100}}
+                                {:global {:distinct-count 100}, :type {:type/Number {:min 1.0, :max 100.0, :avg 51.965}}})}
      :user_id  {:extra_info   (if (data/fks-supported?) {:target_table_id (data/id :users)}
                                   {})
                 :target       (target-field (users-col :id))
@@ -248,7 +254,9 @@
                 :base_type    (data/expected-base-type->actual :type/Integer)
                 :name         (data/format-name "user_id")
                 :display_name "User ID"
-                :fingerprint  {:global {:distinct-count 15}, :type {:type/Number {:min 1, :max 15, :avg 7.929}}}})))
+                :fingerprint  (if (data/fks-supported?)
+                                {:global {:distinct-count 15}}
+                                {:global {:distinct-count 15}, :type {:type/Number {:min 1.0, :max 15.0, :avg 7.929}}})})))
 
 
 ;;; #### aggregate columns
diff --git a/test/metabase/query_processor_test/aggregation_test.clj b/test/metabase/query_processor_test/aggregation_test.clj
index 7c024623d7dcf9c3eb96f49958dbc0e4b00b6d71..c075a42e5ba377ff3b5258d125c8cfd128439f70 100644
--- a/test/metabase/query_processor_test/aggregation_test.clj
+++ b/test/metabase/query_processor_test/aggregation_test.clj
@@ -3,79 +3,79 @@
   (:require [metabase
              [query-processor-test :refer :all]
              [util :as u]]
-            [metabase.query-processor.middleware.expand :as ql]
-            [metabase.test.data :as data]
-            [metabase.test.data.datasets :as datasets]
-            [metabase.test.util :as tu]))
+            [metabase.test
+             [data :as data]
+             [util :as tu]]
+            [metabase.test.data.datasets :as datasets]))
 
 ;;; ---------------------------------------------- "COUNT" AGGREGATION -----------------------------------------------
 
 (qp-expect-with-all-engines
-    {:rows        [[100]]
-     :columns     ["count"]
-     :cols        [(aggregate-col :count)]
-     :native_form true}
-    (->> (data/run-query venues
-           (ql/aggregation (ql/count)))
-         booleanize-native-form
-         (format-rows-by [int])))
+  {:rows        [[100]]
+   :columns     ["count"]
+   :cols        [(aggregate-col :count)]
+   :native_form true}
+  (->> (data/run-mbql-query venues
+         {:aggregation [[:count]]})
+       booleanize-native-form
+       (format-rows-by [int])))
 
 
 ;;; ----------------------------------------------- "SUM" AGGREGATION ------------------------------------------------
 (qp-expect-with-all-engines
-    {:rows        [[203]]
-     :columns     ["sum"]
-     :cols        [(aggregate-col :sum (venues-col :price))]
-     :native_form true}
-    (->> (data/run-query venues
-           (ql/aggregation (ql/sum $price)))
-         booleanize-native-form
-         (format-rows-by [int])))
+  {:rows        [[203]]
+   :columns     ["sum"]
+   :cols        [(aggregate-col :sum (venues-col :price))]
+   :native_form true}
+  (->> (data/run-mbql-query venues
+         {:aggregation [[:sum $price]]})
+       booleanize-native-form
+       (format-rows-by [int])))
 
 
 ;;; ----------------------------------------------- "AVG" AGGREGATION ------------------------------------------------
 (qp-expect-with-all-engines
-    {:rows        [[35.5059]]
-     :columns     ["avg"]
-     :cols        [(aggregate-col :avg (venues-col :latitude))]
-     :native_form true}
-    (->> (data/run-query venues
-           (ql/aggregation (ql/avg $latitude)))
-         booleanize-native-form
-         (format-rows-by [(partial u/round-to-decimals 4)])))
+  {:rows        [[35.5059]]
+   :columns     ["avg"]
+   :cols        [(aggregate-col :avg (venues-col :latitude))]
+   :native_form true}
+  (->> (data/run-mbql-query venues
+         {:aggregation [[:avg $latitude]]})
+       booleanize-native-form
+       (format-rows-by [(partial u/round-to-decimals 4)])))
 
 
 ;;; ------------------------------------------ "DISTINCT COUNT" AGGREGATION ------------------------------------------
 (qp-expect-with-all-engines
-    {:rows        [[15]]
-     :columns     ["count"]
-     :cols        [(aggregate-col :count)]
-     :native_form true}
-    (->> (data/run-query checkins
-           (ql/aggregation (ql/distinct $user_id)))
-         booleanize-native-form
-         (format-rows-by [int])))
+  {:rows        [[15]]
+   :columns     ["count"]
+   :cols        [(aggregate-col :count)]
+   :native_form true}
+  (->> (data/run-mbql-query checkins
+         {:aggregation [[:distinct $user_id]]})
+       booleanize-native-form
+       (format-rows-by [int])))
 
 
 ;;; ------------------------------------------------- NO AGGREGATION -------------------------------------------------
 ;; Test that no aggregation (formerly known as a 'rows' aggregation in MBQL '95) just returns rows as-is.
 (qp-expect-with-all-engines
-    {:rows        [[ 1 "Red Medicine"                  4 10.0646 -165.374 3]
-                   [ 2 "Stout Burgers & Beers"        11 34.0996 -118.329 2]
-                   [ 3 "The Apple Pan"                11 34.0406 -118.428 2]
-                   [ 4 "Wurstküche"                   29 33.9997 -118.465 2]
-                   [ 5 "Brite Spot Family Restaurant" 20 34.0778 -118.261 2]
-                   [ 6 "The 101 Coffee Shop"          20 34.1054 -118.324 2]
-                   [ 7 "Don Day Korean Restaurant"    44 34.0689 -118.305 2]
-                   [ 8 "25°"                          11 34.1015 -118.342 2]
-                   [ 9 "Krua Siri"                    71 34.1018 -118.301 1]
-                   [10 "Fred 62"                      20 34.1046 -118.292 2]]
-     :columns     (venues-columns)
-     :cols        (venues-cols)
-     :native_form true}
-    (-> (data/run-query venues
-           (ql/limit 10)
-           (ql/order-by (ql/asc $id)))
+  {:rows        [[ 1 "Red Medicine"                  4 10.0646 -165.374 3]
+                 [ 2 "Stout Burgers & Beers"        11 34.0996 -118.329 2]
+                 [ 3 "The Apple Pan"                11 34.0406 -118.428 2]
+                 [ 4 "Wurstküche"                   29 33.9997 -118.465 2]
+                 [ 5 "Brite Spot Family Restaurant" 20 34.0778 -118.261 2]
+                 [ 6 "The 101 Coffee Shop"          20 34.1054 -118.324 2]
+                 [ 7 "Don Day Korean Restaurant"    44 34.0689 -118.305 2]
+                 [ 8 "25°"                          11 34.1015 -118.342 2]
+                 [ 9 "Krua Siri"                    71 34.1018 -118.301 1]
+                 [10 "Fred 62"                      20 34.1046 -118.292 2]]
+   :columns     (venues-columns)
+   :cols        (venues-cols)
+   :native_form true}
+    (-> (data/run-mbql-query venues
+          {:limit    10
+           :order-by [[:asc $id]]})
         booleanize-native-form
         formatted-venues-rows
         tu/round-fingerprint-cols))
@@ -88,8 +88,8 @@
    :cols        [(aggregate-col :stddev (venues-col :latitude))]
    :rows        [[3.4]]
    :native_form true}
-  (-> (data/run-query venues
-        (ql/aggregation (ql/stddev $latitude)))
+  (-> (data/run-mbql-query venues
+        {:aggregation [[:stddev $latitude]]})
       booleanize-native-form
       (update-in [:data :rows] (fn [[[v]]]
                                  [[(u/round-to-decimals 1 v)]]))))
@@ -98,8 +98,8 @@
 (datasets/expect-with-engines (non-timeseries-engines-without-feature :standard-deviation-aggregations)
   {:status :failed
    :error  "standard-deviation-aggregations is not supported by this driver."}
-  (select-keys (data/run-query venues
-                 (ql/aggregation (ql/stddev $latitude)))
+  (select-keys (data/run-mbql-query venues
+                 {:aggregation [[:stddev $latitude]]})
                [:status :error]))
 
 
@@ -107,29 +107,33 @@
 ;;; |                                                   MIN & MAX                                                    |
 ;;; +----------------------------------------------------------------------------------------------------------------+
 
-(expect-with-non-timeseries-dbs [1] (first-row
-                                      (format-rows-by [int]
-                                        (data/run-query venues
-                                          (ql/aggregation (ql/min $price))))))
+(expect-with-non-timeseries-dbs
+  [1]
+  (first-row
+    (format-rows-by [int]
+      (data/run-mbql-query venues
+        {:aggregation [[:min $price]]}))))
 
-(expect-with-non-timeseries-dbs [4] (first-row
-                                      (format-rows-by [int]
-                                        (data/run-query venues
-                                          (ql/aggregation (ql/max $price))))))
+(expect-with-non-timeseries-dbs
+  [4]
+  (first-row
+    (format-rows-by [int]
+      (data/run-mbql-query venues
+        {:aggregation [[:max $price]]}))))
 
 (expect-with-non-timeseries-dbs
   [[1 34.0071] [2 33.7701] [3 10.0646] [4 33.983]]
   (format-rows-by [int (partial u/round-to-decimals 4)]
-    (rows (data/run-query venues
-            (ql/aggregation (ql/min $latitude))
-            (ql/breakout $price)))))
+    (rows (data/run-mbql-query venues
+            {:aggregation [[:min $latitude]]
+             :breakout    [$price]}))))
 
 (expect-with-non-timeseries-dbs
   [[1 37.8078] [2 40.7794] [3 40.7262] [4 40.7677]]
   (format-rows-by [int (partial u/round-to-decimals 4)]
-    (rows (data/run-query venues
-            (ql/aggregation (ql/max $latitude))
-            (ql/breakout $price)))))
+    (rows (data/run-mbql-query venues
+            {:aggregation [[:max $latitude]]
+             :breakout    [$price]}))))
 
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
@@ -140,15 +144,15 @@
 (expect-with-non-timeseries-dbs
   [[100 203]]
   (format-rows-by [int int]
-    (rows (data/run-query venues
-            (ql/aggregation (ql/count) (ql/sum $price))))))
+    (rows (data/run-mbql-query venues
+            {:aggregation [[:count] [:sum $price]]}))))
 
 ;; how about with *three* aggregations?
 (expect-with-non-timeseries-dbs
   [[2 100 203]]
   (format-rows-by [int int int]
-    (rows (data/run-query venues
-            (ql/aggregation (ql/avg $price) (ql/count) (ql/sum $price))))))
+    (rows (data/run-mbql-query venues
+            {:aggregation [[:avg $price] [:count] [:sum $price]]}))))
 
 ;; make sure that multiple aggregations of the same type have the correct metadata (#4003)
 ;;
@@ -160,8 +164,8 @@
      :display_name    "Count 2"
      :name            "count_2"
      :preview_display true)]
-  (-> (data/run-query venues
-        (ql/aggregation (ql/count) (ql/count)))
+  (-> (data/run-mbql-query venues
+        {:aggregation [[:count] [:count]]})
       :data :cols))
 
 
@@ -173,8 +177,8 @@
    :columns     ["sum"]
    :cols        [(aggregate-col :sum (users-col :id))]
    :native_form true}
-  (->> (data/run-query users
-         (ql/aggregation (ql/cum-sum $id)))
+  (->> (data/run-mbql-query users
+         {:aggregation [[:cum-sum $id]]})
        booleanize-native-form
        (format-rows-by [int])))
 
@@ -201,9 +205,9 @@
    :cols        [(breakout-col (users-col :id))
                  (aggregate-col :sum (users-col :id))]
    :native_form true}
-  (->> (data/run-query users
-         (ql/aggregation (ql/cum-sum $id))
-         (ql/breakout $id))
+  (->> (data/run-mbql-query users
+         {:aggregation [[:cum-sum $id]]
+          :breakout [$id]})
        booleanize-native-form
        (format-rows-by [int int])))
 
@@ -230,9 +234,9 @@
    :cols        [(breakout-col (users-col :name))
                  (aggregate-col :sum (users-col :id))]
    :native_form true}
-  (->> (data/run-query users
-         (ql/aggregation (ql/cum-sum $id))
-         (ql/breakout $name))
+  (->> (data/run-mbql-query users
+         {:aggregation [[:cum-sum $id]]
+          :breakout    [$name]})
        booleanize-native-form
        (format-rows-by [str int])
        tu/round-fingerprint-cols))
@@ -249,9 +253,9 @@
                  [3 4681]
                  [4 5050]]
    :native_form true}
-  (->> (data/run-query venues
-         (ql/aggregation (ql/cum-sum $id))
-         (ql/breakout $price))
+  (->> (data/run-mbql-query venues
+         {:aggregation [[:cum-sum $id]]
+          :breakout    [$price]})
        booleanize-native-form
        (format-rows-by [int int])))
 
@@ -265,12 +269,12 @@
 
 ;;; cum_count w/o breakout should be treated the same as count
 (qp-expect-with-all-engines
-    {:rows    [[15]]
-     :columns ["count"]
-     :cols    [(cumulative-count-col users-col :id)]
-     :native_form true}
-  (->> (data/run-query users
-                  (ql/aggregation (ql/cum-count)))
+  {:rows        [[15]]
+   :columns     ["count"]
+   :cols        [(cumulative-count-col users-col :id)]
+   :native_form true}
+  (->> (data/run-mbql-query users
+         {:aggregation [[:cum-count $id]]})
        booleanize-native-form
        (format-rows-by [int])))
 
@@ -296,9 +300,9 @@
    :cols        [(breakout-col (users-col :name))
                  (cumulative-count-col users-col :id)]
    :native_form true}
-  (->> (data/run-query users
-         (ql/aggregation (ql/cum-count))
-         (ql/breakout $name))
+  (->> (data/run-mbql-query users
+         {:aggregation [[:cum-count $id]]
+          :breakout    [$name]})
        booleanize-native-form
        (format-rows-by [str int])
        tu/round-fingerprint-cols))
@@ -315,8 +319,8 @@
                  [3 94]
                  [4 100]]
    :native_form true}
-  (->> (data/run-query venues
-         (ql/aggregation (ql/cum-count))
-         (ql/breakout $price))
+  (->> (data/run-mbql-query venues
+         {:aggregation [[:cum-count $id]]
+          :breakout    [$price]})
        booleanize-native-form
        (format-rows-by [int int])))
diff --git a/test/metabase/query_processor_test/breakout_test.clj b/test/metabase/query_processor_test/breakout_test.clj
index cdca9fa82415ed980cb3b9e5995c0838ca0075d5..cd7fd767ae9bd9d9ceb0af728e932466a9872dc7 100644
--- a/test/metabase/query_processor_test/breakout_test.clj
+++ b/test/metabase/query_processor_test/breakout_test.clj
@@ -2,116 +2,117 @@
   "Tests for the `:breakout` clause."
   (:require [cheshire.core :as json]
             [metabase
+             [query-processor :as qp]
              [query-processor-test :refer :all]
              [util :as u]]
             [metabase.models
+             [card :refer [Card]]
              [dimension :refer [Dimension]]
              [field :refer [Field]]
              [field-values :refer [FieldValues]]]
-            [metabase.query-processor.middleware
-             [add-dimension-projections :as add-dim-projections]
-             [expand :as ql]]
+            [metabase.query-processor.middleware.add-dimension-projections :as add-dim-projections]
             [metabase.test
              [data :as data]
              [util :as tu]]
             [metabase.test.data
              [dataset-definitions :as defs]
              [datasets :as datasets]]
-            [toucan.db :as db]))
+            [toucan.db :as db]
+            [toucan.util.test :as tt]))
 
 ;;; single column
 (qp-expect-with-all-engines
-  {:rows    [[1 31] [2 70] [3 75] [4 77] [5 69] [6 70] [7 76] [8 81] [9 68] [10 78] [11 74] [12 59] [13 76] [14 62] [15 34]]
-   :columns [(data/format-name "user_id")
-             "count"]
-   :cols    [(breakout-col (checkins-col :user_id))
-             (aggregate-col :count)]
+  {:rows        [[1 31] [2 70] [3 75] [4 77] [5 69] [6 70] [7 76] [8 81] [9 68] [10 78] [11 74] [12 59] [13 76] [14 62] [15 34]]
+   :columns     [(data/format-name "user_id")
+                 "count"]
+   :cols        [(breakout-col (checkins-col :user_id))
+                 (aggregate-col :count)]
    :native_form true}
-  (->> (data/run-query checkins
-         (ql/aggregation (ql/count))
-         (ql/breakout $user_id)
-         (ql/order-by (ql/asc $user_id)))
+  (->> (data/run-mbql-query checkins
+         {:aggregation [[:count]]
+          :breakout    [$user_id]
+          :order-by    [[:asc $user_id]]})
        booleanize-native-form
        (format-rows-by [int int])))
 
 ;;; BREAKOUT w/o AGGREGATION
 ;; This should act as a "distinct values" query and return ordered results
 (qp-expect-with-all-engines
-  {:cols    [(breakout-col (checkins-col :user_id))]
-   :columns [(data/format-name "user_id")]
-   :rows    [[1] [2] [3] [4] [5] [6] [7] [8] [9] [10]]
+  {:cols        [(breakout-col (checkins-col :user_id))]
+   :columns     [(data/format-name "user_id")]
+   :rows        [[1] [2] [3] [4] [5] [6] [7] [8] [9] [10]]
    :native_form true}
-  (->> (data/run-query checkins
-         (ql/breakout $user_id)
-         (ql/limit 10))
+  (->> (data/run-mbql-query checkins
+         {:breakout [$user_id]
+          :limit    10})
        booleanize-native-form
        (format-rows-by [int])))
 
 
 ;;; "BREAKOUT" - MULTIPLE COLUMNS W/ IMPLICT "ORDER_BY"
-;; Fields should be implicitly ordered :ASC for all the fields in `breakout` that are not specified in `order_by`
+;; Fields should be implicitly ordered :ASC for all the fields in `breakout` that are not specified in `order-by`
 (qp-expect-with-all-engines
-  {:rows    [[1 1 1] [1 5 1] [1 7 1] [1 10 1] [1 13 1] [1 16 1] [1 26 1] [1 31 1] [1 35 1] [1 36 1]]
-   :columns [(data/format-name "user_id")
-             (data/format-name "venue_id")
-             "count"]
-   :cols    [(breakout-col (checkins-col :user_id))
-             (breakout-col (checkins-col :venue_id))
-             (aggregate-col :count)]
+  {:rows        [[1 1 1] [1 5 1] [1 7 1] [1 10 1] [1 13 1] [1 16 1] [1 26 1] [1 31 1] [1 35 1] [1 36 1]]
+   :columns     [(data/format-name "user_id")
+                 (data/format-name "venue_id")
+                 "count"]
+   :cols        [(breakout-col (checkins-col :user_id))
+                 (breakout-col (checkins-col :venue_id))
+                 (aggregate-col :count)]
    :native_form true}
-  (->> (data/run-query checkins
-         (ql/aggregation (ql/count))
-         (ql/breakout $user_id $venue_id)
-         (ql/limit 10))
+  (->> (data/run-mbql-query checkins
+         {:aggregation [[:count]]
+          :breakout    [$user_id $venue_id]
+          :limit       10})
        booleanize-native-form
        (format-rows-by [int int int])))
 
 ;;; "BREAKOUT" - MULTIPLE COLUMNS W/ EXPLICIT "ORDER_BY"
-;; `breakout` should not implicitly order by any fields specified in `order_by`
+;; `breakout` should not implicitly order by any fields specified in `order-by`
 (qp-expect-with-all-engines
-  {:rows    [[15 2 1] [15 3 1] [15 7 1] [15 14 1] [15 16 1] [15 18 1] [15 22 1] [15 23 2] [15 24 1] [15 27 1]]
-   :columns [(data/format-name "user_id")
-             (data/format-name "venue_id")
-             "count"]
-   :cols    [(breakout-col (checkins-col :user_id))
-             (breakout-col (checkins-col :venue_id))
-             (aggregate-col :count)]
+  {:rows        [[15 2 1] [15 3 1] [15 7 1] [15 14 1] [15 16 1] [15 18 1] [15 22 1] [15 23 2] [15 24 1] [15 27 1]]
+   :columns     [(data/format-name "user_id")
+                 (data/format-name "venue_id")
+                 "count"]
+   :cols        [(breakout-col (checkins-col :user_id))
+                 (breakout-col (checkins-col :venue_id))
+                 (aggregate-col :count)]
    :native_form true}
-  (->> (data/run-query checkins
-         (ql/aggregation (ql/count))
-         (ql/breakout $user_id $venue_id)
-         (ql/order-by (ql/desc $user_id))
-         (ql/limit 10))
+  (->> (data/run-mbql-query checkins
+         {:aggregation [[:count]]
+          :breakout    [$user_id $venue_id]
+          :order-by    [[:desc $user_id]]
+          :limit       10})
        booleanize-native-form
        (format-rows-by [int int int])))
 
 (qp-expect-with-all-engines
-  {:rows  [[2 8 "Artisan"]
-           [3 2 "Asian"]
-           [4 2 "BBQ"]
-           [5 7 "Bakery"]
-           [6 2 "Bar"]]
-   :columns [(data/format-name "category_id")
-             "count"
-             "Foo"]
-   :cols    [(assoc (breakout-col (venues-col :category_id))
-               :remapped_to "Foo")
-             (aggregate-col :count)
-             (#'add-dim-projections/create-remapped-col "Foo" (data/format-name "category_id"))]
+  {:rows        [[2 8 "Artisan"]
+                 [3 2 "Asian"]
+                 [4 2 "BBQ"]
+                 [5 7 "Bakery"]
+                 [6 2 "Bar"]]
+   :columns     [(data/format-name "category_id")
+                 "count"
+                 "Foo"]
+   :cols        [(assoc (breakout-col (venues-col :category_id))
+                   :remapped_to "Foo")
+                 (aggregate-col :count)
+                 (#'add-dim-projections/create-remapped-col "Foo" (data/format-name "category_id"))]
    :native_form true}
   (data/with-data
     (fn []
       (let [venue-names (defs/field-values defs/test-data-map "categories" "name")]
         [(db/insert! Dimension {:field_id (data/id :venues :category_id)
-                                :name "Foo"
-                                :type :internal})
-         (db/insert! FieldValues {:field_id (data/id :venues :category_id)
-                                  :values (json/generate-string (range 0 (count venue-names)))
+                                :name     "Foo"
+                                :type     :internal})
+         (db/insert! FieldValues {:field_id              (data/id :venues :category_id)
+                                  :values                (json/generate-string (range 0 (count venue-names)))
                                   :human_readable_values (json/generate-string venue-names)})]))
-    (->> (data/run-query venues
-           (ql/aggregation (ql/count))
-           (ql/breakout $category_id)
-           (ql/limit 5))
+    (->> (data/run-mbql-query venues
+           {:aggregation [[:count]]
+            :breakout    [$category_id]
+            :limit       5})
          booleanize-native-form
          (format-rows-by [int int str]))))
 
@@ -120,85 +121,86 @@
    ["American" "American" "American" "American" "American" "American" "American" "American" "Artisan" "Artisan"]]
   (data/with-data
     (fn []
-      [(db/insert! Dimension {:field_id (data/id :venues :category_id)
-                              :name "Foo"
-                              :type :external
+      [(db/insert! Dimension {:field_id                (data/id :venues :category_id)
+                              :name                    "Foo"
+                              :type                    :external
                               :human_readable_field_id (data/id :categories :name)})])
-    [(->> (data/run-query venues
-             (ql/order-by (ql/desc $category_id))
-             (ql/limit 10))
+    [(->> (data/run-mbql-query venues
+            {:order-by [[:desc $category_id]]
+             :limit    10})
            rows
            (map last))
-     (->> (data/run-query venues
-             (ql/order-by (ql/asc $category_id))
-             (ql/limit 10))
+     (->> (data/run-mbql-query venues
+            {:order-by [[:asc $category_id]]
+             :limit    10})
            rows
            (map last))]))
 
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :binning)
   [[10.0 1] [32.0 4] [34.0 57] [36.0 29] [40.0 9]]
   (format-rows-by [(partial u/round-to-decimals 1) int]
-    (rows (data/run-query venues
-            (ql/aggregation (ql/count))
-            (ql/breakout (ql/binning-strategy $latitude :num-bins 20))))))
+    (rows (data/run-mbql-query venues
+            {:aggregation [[:count]]
+             :breakout    [[:binning-strategy $latitude :num-bins 20]]}))))
 
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :binning)
- [[0.0 1] [20.0 90] [40.0 9]]
+  [[0.0 1] [20.0 90] [40.0 9]]
   (format-rows-by [(partial u/round-to-decimals 1) int]
-    (rows (data/run-query venues
-            (ql/aggregation (ql/count))
-            (ql/breakout (ql/binning-strategy $latitude :num-bins 3))))))
+    (rows (data/run-mbql-query venues
+            {:aggregation [[:count]]
+             :breakout    [[:binning-strategy $latitude :num-bins 3]]}))))
 
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :binning)
-   [[10.0 -170.0 1] [32.0 -120.0 4] [34.0 -120.0 57] [36.0 -125.0 29] [40.0 -75.0 9]]
+  [[10.0 -170.0 1] [32.0 -120.0 4] [34.0 -120.0 57] [36.0 -125.0 29] [40.0 -75.0 9]]
   (format-rows-by [(partial u/round-to-decimals 1) (partial u/round-to-decimals 1) int]
-    (rows (data/run-query venues
-            (ql/aggregation (ql/count))
-            (ql/breakout (ql/binning-strategy $latitude :num-bins 20)
-                         (ql/binning-strategy $longitude :num-bins 20))))))
+    (rows (data/run-mbql-query venues
+            {:aggregation [[:count]]
+             :breakout    [[:binning-strategy $latitude :num-bins 20]
+                           [:binning-strategy $longitude :num-bins 20]]}))))
 
 ;; Currently defaults to 8 bins when the number of bins isn't
 ;; specified
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :binning)
   [[10.0 1] [30.0 90] [40.0 9]]
   (format-rows-by [(partial u/round-to-decimals 1) int]
-    (rows (data/run-query venues
-            (ql/aggregation (ql/count))
-            (ql/breakout (ql/binning-strategy $latitude :default))))))
+    (rows (data/run-mbql-query venues
+            {:aggregation [[:count]]
+             :breakout    [[:binning-strategy $latitude :default]]}))))
 
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :binning)
   [[10.0 1] [30.0 61] [35.0 29] [40.0 9]]
   (tu/with-temporary-setting-values [breakout-bin-width 5.0]
     (format-rows-by [(partial u/round-to-decimals 1) int]
-      (rows (data/run-query venues
-              (ql/aggregation (ql/count))
-              (ql/breakout (ql/binning-strategy $latitude :default)))))))
+      (rows (data/run-mbql-query venues
+              {:aggregation [[:count]]
+               :breakout    [[:binning-strategy $latitude :default]]})))))
 
 ;; Testing bin-width
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :binning)
   [[10.0 1] [33.0 4] [34.0 57] [37.0 29] [40.0 9]]
   (format-rows-by [(partial u/round-to-decimals 1) int]
-    (rows (data/run-query venues
-            (ql/aggregation (ql/count))
-            (ql/breakout (ql/binning-strategy $latitude :bin-width 1))))))
+    (rows (data/run-mbql-query venues
+            {:aggregation [[:count]]
+             :breakout    [[:binning-strategy $latitude :bin-width 1]]}))))
 
 ;; Testing bin-width using a float
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :binning)
   [[10.0 1] [32.5 61] [37.5 29] [40.0 9]]
   (format-rows-by [(partial u/round-to-decimals 1) int]
-    (rows (data/run-query venues
-            (ql/aggregation (ql/count))
-            (ql/breakout (ql/binning-strategy $latitude :bin-width 2.5))))))
+    (rows (data/run-mbql-query venues
+            {:aggregation [[:count]]
+             :breakout    [[:binning-strategy $latitude :bin-width 2.5]]}))))
 
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :binning)
   [[33.0 4] [34.0 57]]
   (tu/with-temporary-setting-values [breakout-bin-width 1.0]
     (format-rows-by [(partial u/round-to-decimals 1) int]
-      (rows (data/run-query venues
-              (ql/aggregation (ql/count))
-              (ql/filter (ql/and (ql/< $latitude 35)
-                                 (ql/> $latitude 20)))
-              (ql/breakout (ql/binning-strategy $latitude :default)))))))
+      (rows (data/run-mbql-query venues
+              {:aggregation [[:count]]
+               :filter      [:and
+                             [:< $latitude 35]
+                             [:> $latitude 20]]
+               :breakout    [[:binning-strategy $latitude :default]]})))))
 
 (defn- round-binning-decimals [result]
   (let [round-to-decimal #(u/round-to-decimals 4 %)]
@@ -211,24 +213,24 @@
 ;;Validate binning info is returned with the binning-strategy
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :binning)
   (assoc (breakout-col (venues-col :latitude))
-         :binning_info {:binning_strategy :bin-width, :bin_width 10.0,
-                        :num_bins         4,          :min_value 10.0
-                        :max_value        50.0})
-  (-> (data/run-query venues
-        (ql/aggregation (ql/count))
-        (ql/breakout (ql/binning-strategy $latitude :default)))
+    :binning_info {:binning_strategy :bin-width, :bin_width 10.0,
+                   :num_bins         4,          :min_value 10.0
+                   :max_value        50.0})
+  (-> (data/run-mbql-query venues
+        {:aggregation          [[:count]]
+         :breakout [[:binning-strategy $latitude :default]]})
       tu/round-fingerprint-cols
       (get-in [:data :cols])
       first))
 
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :binning)
   (assoc (breakout-col (venues-col :latitude))
-         :binning_info {:binning_strategy :num-bins, :bin_width 7.5,
-                        :num_bins         5,         :min_value 7.5,
-                        :max_value        45.0})
-  (-> (data/run-query venues
-                      (ql/aggregation (ql/count))
-                      (ql/breakout (ql/binning-strategy $latitude :num-bins 5)))
+    :binning_info {:binning_strategy :num-bins, :bin_width 7.5,
+                   :num_bins         5,         :min_value 7.5,
+                   :max_value        45.0})
+  (-> (data/run-mbql-query venues
+        {:aggregation          [[:count]]
+         :breakout [[:binning-strategy $latitude :num-bins 5]]})
       tu/round-fingerprint-cols
       (get-in [:data :cols])
       first))
@@ -236,12 +238,44 @@
 ;;Validate binning info is returned with the binning-strategy
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :binning)
   {:status :failed
-   :class Exception
-   :error (format "Unable to bin field '%s' with id '%s' without a min/max value"
-                  (:name (Field (data/id :venues :latitude)))
-                  (data/id :venues :latitude))}
+   :class  Exception
+   :error  (format "Unable to bin field '%s' with id '%s' without a min/max value"
+                   (:name (Field (data/id :venues :latitude)))
+                   (data/id :venues :latitude))}
   (tu/with-temp-vals-in-db Field (data/id :venues :latitude) {:fingerprint {:type {:type/Number {:min nil, :max nil}}}}
-    (-> (data/run-query venues
-                        (ql/aggregation (ql/count))
-                        (ql/breakout (ql/binning-strategy $latitude :default)))
+    (-> (data/run-mbql-query venues
+          {:aggregation          [[:count]]
+           :breakout [[:binning-strategy $latitude :default]]})
         (select-keys [:status :class :error]))))
+
+(defn- field->result-metadata [field]
+  (select-keys field [:name :display_name :description :base_type :special_type :unit :fingerprint]))
+
+(defn- nested-venues-query [card-or-card-id]
+  {:database metabase.models.database/virtual-id
+   :type :query
+   :query {:source-table (str "card__" (u/get-id card-or-card-id))
+           :aggregation  [:count]
+           :breakout     [[:binning-strategy [:field-literal (data/format-name :latitude) :type/Float] :num-bins 20]]}})
+
+;; Binning should be allowed on nested queries that have result metadata
+(datasets/expect-with-engines (non-timeseries-engines-with-feature :binning :nested-queries)
+  [[10.0 1] [32.0 4] [34.0 57] [36.0 29] [40.0 9]]
+  (tt/with-temp Card [card {:dataset_query   {:database (data/id)
+                                              :type     :query
+                                              :query    {:source-query {:source-table (data/id :venues)}}}
+                            :result_metadata (mapv field->result-metadata (db/select Field :table_id (data/id :venues)))}]
+    (->> (nested-venues-query card)
+         qp/process-query
+         rows
+         (format-rows-by [(partial u/round-to-decimals 1) int]))))
+
+;; Binning is not supported when there is no fingerprint to determine boundaries
+(datasets/expect-with-engines (non-timeseries-engines-with-feature :binning :nested-queries)
+  Exception
+  (tt/with-temp Card [card {:dataset_query {:database (data/id)
+                                            :type     :query
+                                            :query    {:source-query {:source-table (data/id :venues)}}}}]
+    (-> (nested-venues-query card)
+        qp/process-query
+        rows)))
diff --git a/test/metabase/query_processor_test/date_bucketing_test.clj b/test/metabase/query_processor_test/date_bucketing_test.clj
index fa7b19e624aa27fadbb8d2e88a8ff81382143ecd..5dfe82975d25440c99ccb34201971914b64d7d1b 100644
--- a/test/metabase/query_processor_test/date_bucketing_test.clj
+++ b/test/metabase/query_processor_test/date_bucketing_test.clj
@@ -7,7 +7,6 @@
              [driver :as driver]
              [query-processor-test :refer :all]
              [util :as u]]
-            [metabase.query-processor.middleware.expand :as ql]
             [metabase.test
              [data :as data]
              [util :as tu]]
@@ -51,10 +50,10 @@
   "Returns 10 sad toucan incidents grouped by `UNIT`"
   ([unit]
    (->> (data/with-db (data/get-or-create-database! defs/sad-toucan-incidents)
-          (data/run-query incidents
-            (ql/aggregation (ql/count))
-            (ql/breakout (ql/datetime-field $timestamp unit))
-            (ql/limit 10)))
+          (data/run-mbql-query incidents
+            {:aggregation [[:count]]
+             :breakout    [[:datetime-field $timestamp unit]]
+             :limit       10}))
         rows (format-rows-by [->long-if-number int])))
   ([unit tz]
    (tu/with-temporary-setting-values [report-timezone (.getID tz)]
@@ -315,13 +314,13 @@
   "Find the number of sad toucan events between `start-date-str` and `end-date-str`"
   [start-date-str end-date-str]
   (-> (data/with-db (data/get-or-create-database! defs/sad-toucan-incidents)
-        (data/run-query incidents
-          (ql/aggregation (ql/count))
-          (ql/breakout (ql/datetime-field $timestamp :day))
-          (ql/filter
-           (ql/between (ql/datetime-field $timestamp :default)
-                       start-date-str
-                       end-date-str))))
+        (data/run-mbql-query incidents
+          {:aggregation [[:count]]
+           :breakout    [[:datetime-field $timestamp :day]]
+           :filter      [:between
+                         [:datetime-field $timestamp :default]
+                         start-date-str
+                         end-date-str]}))
       rows
       first
       second
@@ -804,10 +803,11 @@
 
 (defn- count-of-grouping [db field-grouping & relative-datetime-args]
   (-> (data/with-temp-db [_ db]
-        (data/run-query checkins
-          (ql/aggregation (ql/count))
-          (ql/filter (ql/= (ql/datetime-field $timestamp field-grouping)
-                           (apply ql/relative-datetime relative-datetime-args)))))
+        (data/run-mbql-query checkins
+          {:aggregation [[:count]]
+           :filter      [:=
+                         [:datetime-field $timestamp field-grouping]
+                         (cons :relative-datetime relative-datetime-args)]}))
       first-row first int))
 
 ;; HACK - Don't run these tests against BigQuery because the databases need to be loaded every time the tests are ran
@@ -835,17 +835,17 @@
 (expect-with-non-timeseries-dbs-except #{:bigquery}
   1
   (-> (data/with-temp-db [_ (checkins:1-per-day)]
-        (data/run-query checkins
-          (ql/aggregation (ql/count))
-          (ql/filter (ql/time-interval $timestamp :current :day))))
+        (data/run-mbql-query checkins
+          {:aggregation [[:count]]
+           :filter      [:time-interval $timestamp :current :day]}))
       first-row first int))
 
 (expect-with-non-timeseries-dbs-except #{:bigquery}
   7
   (-> (data/with-temp-db [_ (checkins:1-per-day)]
-        (data/run-query checkins
-          (ql/aggregation (ql/count))
-          (ql/filter (ql/time-interval $timestamp :last :week))))
+        (data/run-mbql-query checkins
+          {:aggregation [[:count]]
+           :filter      [:time-interval $timestamp :last :week]}))
       first-row first int))
 
 ;; Make sure that when referencing the same field multiple times with different units we return the one that actually
@@ -854,10 +854,10 @@
 (defn- date-bucketing-unit-when-you [& {:keys [breakout-by filter-by with-interval]
                                         :or   {with-interval :current}}]
   (let [results (data/with-temp-db [_ (checkins:1-per-day)]
-                  (data/run-query checkins
-                    (ql/aggregation (ql/count))
-                    (ql/breakout (ql/datetime-field $timestamp breakout-by))
-                    (ql/filter (ql/time-interval $timestamp with-interval filter-by))))]
+                  (data/run-mbql-query checkins
+                    {:aggregation [[:count]]
+                     :breakout    [[:datetime-field $timestamp breakout-by]]
+                     :filter      [:time-interval $timestamp with-interval filter-by]}))]
     {:rows (or (-> results :row_count)
                (throw (ex-info "Query failed!" results)))
      :unit (-> results :data :cols first :unit)}))
diff --git a/test/metabase/query_processor_test/expression_aggregations_test.clj b/test/metabase/query_processor_test/expression_aggregations_test.clj
index 3380695d77a17609bdf81e8ed9c7f4121a90ff47..b83af7696e1858cdec19fa07c779a55b269fb5fd 100644
--- a/test/metabase/query_processor_test/expression_aggregations_test.clj
+++ b/test/metabase/query_processor_test/expression_aggregations_test.clj
@@ -6,7 +6,6 @@
              [query-processor-test :refer :all]
              [util :as u]]
             [metabase.models.metric :refer [Metric]]
-            [metabase.query-processor.middleware.expand :as ql]
             [metabase.test.data :as data]
             [metabase.test.data.datasets :as datasets :refer [*driver* *engine*]]
             [toucan.util.test :as tt]))
@@ -18,9 +17,9 @@
    [3 1845]
    [4 1476]]
   (format-rows-by [int int]
-    (rows (data/run-query venues
-            (ql/aggregation (ql/sum (ql/* $id $price)))
-            (ql/breakout $price)))))
+    (rows (data/run-mbql-query venues
+            {:aggregation [[:sum [:* $id $price]]]
+             :breakout    [$price]}))))
 
 ;; min, +
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :expression-aggregations)
@@ -29,9 +28,9 @@
    [3  4]
    [4 20]]
   (format-rows-by [int int]
-    (rows (data/run-query venues
-            (ql/aggregation (ql/min (ql/+ $id $price)))
-            (ql/breakout $price)))))
+    (rows (data/run-mbql-query venues
+            {:aggregation [[:min [:+ $id $price]]]
+             :breakout    [$price]}))))
 
 ;; max, /
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :expression-aggregations)
@@ -40,9 +39,9 @@
    [3 26]
    [4 20]]
   (format-rows-by [int int]
-    (rows (data/run-query venues
-            (ql/aggregation (ql/max (ql// $id $price)))
-            (ql/breakout $price)))))
+    (rows (data/run-mbql-query venues
+            {:aggregation [[:max [:/ $id $price]]]
+             :breakout    [$price]}))))
 
 ;; avg, -
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :expression-aggregations)
@@ -56,9 +55,9 @@
      [3 141]
      [4 246]])
   (format-rows-by [int int]
-    (rows (data/run-query venues
-            (ql/aggregation (ql/avg (ql/* $id $price)))
-            (ql/breakout $price)))))
+    (rows (data/run-mbql-query venues
+            {:aggregation [[:avg [:* $id $price]]]
+             :breakout    [$price]}))))
 
 ;; post-aggregation math w/ 2 args: count + sum
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :expression-aggregations)
@@ -67,10 +66,11 @@
    [3  52]
    [4  30]]
   (format-rows-by [int int]
-    (rows (data/run-query venues
-            (ql/aggregation (ql/+ (ql/count $id)
-                                  (ql/sum $price)))
-            (ql/breakout $price)))))
+    (rows (data/run-mbql-query venues
+            {:aggregation [[:+
+                            [:count $id]
+                            [:sum $price]]]
+             :breakout    [$price]}))))
 
 ;; post-aggregation math w/ 3 args: count + sum + count
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :expression-aggregations)
@@ -79,11 +79,9 @@
    [3  65]
    [4  36]]
   (format-rows-by [int int]
-    (rows (data/run-query venues
-            (ql/aggregation (ql/+ (ql/count $id)
-                                  (ql/sum $price)
-                                  (ql/count $price)))
-            (ql/breakout $price)))))
+    (rows (data/run-mbql-query venues
+            {:aggregation [[:+ [:count $id] [:sum $price] [:count $price]]]
+             :breakout    [$price]}))))
 
 ;; post-aggregation math w/ a constant: count * 10
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :expression-aggregations)
@@ -92,10 +90,9 @@
    [3 130]
    [4  60]]
   (format-rows-by [int int]
-    (rows (data/run-query venues
-            (ql/aggregation (ql/* (ql/count $id)
-                                  10))
-            (ql/breakout $price)))))
+    (rows (data/run-mbql-query venues
+            {:aggregation [[:* [:count $id] 10]]
+             :breakout    [$price]}))))
 
 ;; nested post-aggregation math: count + (count * sum)
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :expression-aggregations)
@@ -104,11 +101,11 @@
    [3  520]
    [4  150]]
   (format-rows-by [int int]
-    (rows (data/run-query venues
-            (ql/aggregation (ql/+ (ql/count $id)
-                                  (ql/* (ql/count $id)
-                                        (ql/sum $price))))
-            (ql/breakout $price)))))
+    (rows (data/run-mbql-query venues
+            {:aggregation [[:+
+                            [:count $id]
+                            [:* [:count $id] [:sum $price]]]]
+             :breakout    [$price]}))))
 
 ;; post-aggregation math w/ avg: count + avg
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :expression-aggregations)
@@ -122,10 +119,9 @@
      [3  60]
      [4  67]])
   (format-rows-by [int int]
-    (rows (data/run-query venues
-            (ql/aggregation (ql/+ (ql/count $id)
-                                  (ql/avg $id)))
-            (ql/breakout $price)))))
+    (rows (data/run-mbql-query venues
+            {:aggregation [[:+ [:count $id] [:avg $id]]]
+             :breakout    [$price]}))))
 
 ;; post aggregation math + math inside aggregations: max(venue_price) + min(venue_price - id)
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :expression-aggregations)
@@ -134,10 +130,9 @@
    [3 -74]
    [4 -73]]
   (format-rows-by [int int]
-    (rows (data/run-query venues
-            (ql/aggregation (ql/+ (ql/max $price)
-                                  (ql/min (ql/- $price $id))))
-            (ql/breakout $price)))))
+    (rows (data/run-mbql-query venues
+            {:aggregation [[:+ [:max $price] [:min [:- $price $id]]]]
+             :breakout    [$price]}))))
 
 ;; aggregation w/o field
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :expression-aggregations)
@@ -146,18 +141,18 @@
    [3 14]
    [4  7]]
   (format-rows-by [int int]
-    (rows (data/run-query venues
-            (ql/aggregation (ql/+ 1 (ql/count)))
-            (ql/breakout $price)))))
+    (rows (data/run-mbql-query venues
+            {:aggregation [[:+ 1 [:count]]]
+             :breakout    [$price]}))))
 
 ;; Sorting by an un-named aggregate expression
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :expression-aggregations)
   [[1 2] [2 2] [12 2] [4 4] [7 4] [10 4] [11 4] [8 8]]
   (format-rows-by [int int]
-    (rows (data/run-query users
-            (ql/aggregation (ql/* (ql/count) 2))
-            (ql/breakout (ql/datetime-field $last_login :month-of-year))
-            (ql/order-by (ql/asc (ql/aggregation 0)))))))
+    (rows (data/run-mbql-query users
+            {:aggregation [[:* [:count] 2]]
+             :breakout    [[:datetime-field $last_login :month-of-year]]
+             :order-by    [[:asc [:aggregation 0]]]}))))
 
 ;; aggregation with math inside the aggregation :scream_cat:
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :expression-aggregations)
@@ -166,9 +161,9 @@
    [3  52]
    [4  30]]
   (format-rows-by [int int]
-    (rows (data/run-query venues
-            (ql/aggregation (ql/sum (ql/+ $price 1)))
-            (ql/breakout $price)))))
+    (rows (data/run-mbql-query venues
+            {:aggregation [[:sum [:+ $price 1]]]
+             :breakout    [$price]}))))
 
 ;; check that we can name an expression aggregation w/ aggregation at top-level
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :expression-aggregations)
@@ -178,10 +173,10 @@
              [4  30]]
    :columns [(data/format-name "price")
              (driver/format-custom-field-name *driver* "New Price")]} ; Redshift annoyingly always lowercases column aliases
-  (format-rows-by [int int]
-    (rows+column-names (data/run-query venues
-                         (ql/aggregation (ql/named (ql/sum (ql/+ $price 1)) "New Price"))
-                         (ql/breakout $price)))))
+    (format-rows-by [int int]
+      (rows+column-names (data/run-mbql-query venues
+                           {:aggregation [[:named [:sum [:+ $price 1]] "New Price"]]
+                            :breakout    [$price]}))))
 
 ;; check that we can name an expression aggregation w/ expression at top-level
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :expression-aggregations)
@@ -192,9 +187,9 @@
    :columns [(data/format-name "price")
              (driver/format-custom-field-name *driver* "Sum-41")]}
   (format-rows-by [int int]
-    (rows+column-names (data/run-query venues
-                         (ql/aggregation (ql/named (ql/- (ql/sum $price) 41) "Sum-41"))
-                         (ql/breakout $price)))))
+    (rows+column-names (data/run-mbql-query venues
+                         {:aggregation [[:named [:- [:sum $price] 41] "Sum-41"]]
+                          :breakout    [$price]}))))
 
 ;; check that we can handle METRICS (ick) inside expression aggregation clauses
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :expression-aggregations)
@@ -205,12 +200,9 @@
                                 :definition {:aggregation [:sum [:field-id (data/id :venues :price)]]
                                              :filter      [:> [:field-id (data/id :venues :price)] 1]}}]
     (format-rows-by [int int]
-      (rows (qp/process-query
-              {:database (data/id)
-               :type     :query
-               :query    {:source-table (data/id :venues)
-                          :aggregation  [:+ ["METRIC" (u/get-id metric)] 1]
-                          :breakout     [(ql/breakout (ql/field-id (data/id :venues :price)))]}})))))
+      (rows (data/run-mbql-query venues
+              {:aggregation [:+ [:metric (u/get-id metric)] 1]
+               :breakout    [[:field-id $price]]})))))
 
 ;; check that we can handle METRICS (ick) inside a NAMED clause
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :expression-aggregations)
@@ -223,12 +215,9 @@
                                 :definition {:aggregation [:sum [:field-id (data/id :venues :price)]]
                                              :filter      [:> [:field-id (data/id :venues :price)] 1]}}]
     (format-rows-by [int int]
-      (rows+column-names (qp/process-query
-                           {:database (data/id)
-                            :type     :query
-                            :query    {:source-table (data/id :venues)
-                                       :aggregation  [[:named ["METRIC" (u/get-id metric)] "My Cool Metric"]]
-                                       :breakout     [(ql/breakout (ql/field-id (data/id :venues :price)))]}})))))
+      (rows+column-names (data/run-mbql-query venues
+                           {:aggregation  [[:named [:metric (u/get-id metric)] "My Cool Metric"]]
+                            :breakout     [[:field-id $price]]})))))
 
 ;; check that METRICS (ick) with a nested aggregation still work inside a NAMED clause
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :expression-aggregations)
@@ -245,8 +234,8 @@
                            {:database (data/id)
                             :type     :query
                             :query    {:source-table (data/id :venues)
-                                       :aggregation  [[:named ["METRIC" (u/get-id metric)] "My Cool Metric"]]
-                                       :breakout     [(ql/breakout (ql/field-id (data/id :venues :price)))]}})))))
+                                       :aggregation  [[:named [:metric (u/get-id metric)] "My Cool Metric"]]
+                                       :breakout     [[:field-id (data/id :venues :price)]]}})))))
 
 ;; check that named aggregations come back with the correct column metadata (#4002)
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :expression-aggregations)
diff --git a/test/metabase/query_processor_test/expressions_test.clj b/test/metabase/query_processor_test/expressions_test.clj
index 5e3f7ed40e695e422cbe76ca45dcb2133751134a..e40b776ca2cd655b0bbf4b8714ae43fe47487fc4 100644
--- a/test/metabase/query_processor_test/expressions_test.clj
+++ b/test/metabase/query_processor_test/expressions_test.clj
@@ -1,26 +1,11 @@
 (ns metabase.query-processor-test.expressions-test
   "Tests for expressions (calculated columns)."
-  (:require [expectations :refer :all]
-            [metabase
+  (:require [metabase
              [query-processor-test :refer :all]
              [util :as u]]
-            [metabase.query-processor.interface :as qpi]
-            [metabase.query-processor.middleware.expand :as ql]
             [metabase.test.data :as data]
             [metabase.test.data.datasets :as datasets]))
 
-;; Test the expansion of the expressions clause
-(expect
-  {:expressions {:my-cool-new-field (qpi/map->Expression {:operator :*
-                                                          :args     [{:field-id         10,  :fk-field-id        nil,
-                                                                      :datetime-unit    nil, :remapped-from      nil,
-                                                                      :remapped-to      nil, :field-display-name nil
-                                                                      :binning-strategy nil, :binning-param      nil}
-                                                                     20.0]})}}; 20 should be converted to a FLOAT
-
-  (ql/expressions {} {:my-cool-new-field (ql/* (ql/field-id 10) 20)}))
-
-
 ;; Do a basic query including an expression
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :expressions)
   [[1 "Red Medicine"                 4  10.0646 -165.374 3 5.0]
@@ -29,10 +14,10 @@
    [4 "Wurstküche"                   29 33.9997 -118.465 2 4.0]
    [5 "Brite Spot Family Restaurant" 20 34.0778 -118.261 2 4.0]]
   (format-rows-by [int str int (partial u/round-to-decimals 4) (partial u/round-to-decimals 4) int float]
-    (rows (data/run-query venues
-            (ql/expressions {:my-cool-new-field (ql/+ $price 2)})
-            (ql/limit 5)
-            (ql/order-by (ql/asc $id))))))
+    (rows (data/run-mbql-query venues
+            {:expressions {:my-cool-new-field [:+ $price 2]}
+             :limit       5
+             :order-by    [[:asc $id]]}))))
 
 ;; Make sure FLOATING POINT division is done
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :expressions)
@@ -40,10 +25,10 @@
    [2 "Stout Burgers & Beers" 11 34.0996 -118.329 2 1.0]
    [3 "The Apple Pan"         11 34.0406 -118.428 2 1.0]]
   (format-rows-by [int str int (partial u/round-to-decimals 4) (partial u/round-to-decimals 4) int float]
-    (rows (data/run-query venues
-            (ql/expressions {:my-cool-new-field (ql// $price 2)})
-            (ql/limit 3)
-            (ql/order-by (ql/asc $id))))))
+    (rows (data/run-mbql-query venues
+            {:expressions {:my-cool-new-field [:/ $price 2]}
+             :limit       3
+             :order-by    [[:asc $id]]}))))
 
 ;; Can we do NESTED EXPRESSIONS ?
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :expressions)
@@ -51,10 +36,10 @@
    [2 "Stout Burgers & Beers" 11 34.0996 -118.329 2 2.0]
    [3 "The Apple Pan"         11 34.0406 -118.428 2 2.0]]
   (format-rows-by [int str int (partial u/round-to-decimals 4) (partial u/round-to-decimals 4) int float]
-    (rows (data/run-query venues
-            (ql/expressions {:wow (ql/- (ql/* $price 2) (ql/+ $price 0))})
-            (ql/limit 3)
-            (ql/order-by (ql/asc $id))))))
+    (rows (data/run-mbql-query venues
+            {:expressions {:wow [:- [:* $price 2] [:+ $price 0]]}
+             :limit       3
+             :order-by    [[:asc $id]]}))))
 
 ;; Can we have MULTIPLE EXPRESSIONS?
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :expressions)
@@ -62,21 +47,21 @@
    [2 "Stout Burgers & Beers" 11 34.0996 -118.329 2 1.0 3.0]
    [3 "The Apple Pan"         11 34.0406 -118.428 2 1.0 3.0]]
   (format-rows-by [int str int (partial u/round-to-decimals 4) (partial u/round-to-decimals 4) int float float]
-    (rows (data/run-query venues
-            (ql/expressions {:x (ql/- $price 1)
-                             :y (ql/+ $price 1)})
-            (ql/limit 3)
-            (ql/order-by (ql/asc $id))))))
+    (rows (data/run-mbql-query venues
+            {:expressions {:x [:- $price 1]
+                           :y [:+ $price 1]}
+             :limit       3
+             :order-by    [[:asc $id]]}))))
 
 ;; Can we refer to expressions inside a FIELDS clause?
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :expressions)
   [[4] [4] [5]]
   (format-rows-by [int]
-    (rows (data/run-query venues
-            (ql/expressions {:x (ql/+ $price $id)})
-            (ql/fields (ql/expression :x))
-            (ql/limit 3)
-            (ql/order-by (ql/asc $id))))))
+    (rows (data/run-mbql-query venues
+            {:expressions {:x [:+ $price $id]}
+             :fields      [[:expression :x]]
+             :limit       3
+             :order-by    [[:asc $id]]}))))
 
 ;; Can we refer to expressions inside an ORDER BY clause?
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :expressions)
@@ -84,19 +69,19 @@
    [99  "Golden Road Brewing" 10 34.1505 -118.274 2 101.0]
    [98  "Lucky Baldwin's Pub"  7 34.1454 -118.149 2 100.0]]
   (format-rows-by [int str int (partial u/round-to-decimals 4) (partial u/round-to-decimals 4) int float]
-    (rows (data/run-query venues
-            (ql/expressions {:x (ql/+ $price $id)})
-            (ql/limit 3)
-            (ql/order-by (ql/desc (ql/expression :x)))))))
+    (rows (data/run-mbql-query venues
+            {:expressions {:x [:+ $price $id]}
+             :limit       3
+             :order-by    [[:desc [:expression :x]]]}))))
 
 ;; Can we AGGREGATE + BREAKOUT by an EXPRESSION?
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :expressions)
   [[2 22] [4 59] [6 13] [8 6]]
   (format-rows-by [int int]
-    (rows (data/run-query venues
-            (ql/expressions {:x (ql/* $price 2.0)})
-            (ql/aggregation (ql/count))
-            (ql/breakout (ql/expression :x))))))
+    (rows (data/run-mbql-query venues
+            {:expressions {:x [:* $price 2.0]}
+             :aggregation [[:count]]
+             :breakout    [[:expression :x]]}))))
 
 ;; Custom aggregation expressions should include their type
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :expressions)
@@ -105,7 +90,7 @@
           {:name (data/format-name "category_id") :base_type :type/Decimal}
           {:name (data/format-name "category_id") :base_type :type/Integer}))
   (set (map #(select-keys % [:name :base_type])
-            (-> (data/run-query venues
-                  (ql/aggregation (ql/named (ql/sum (ql/* $price -1)) "x"))
-                  (ql/breakout $category_id))
+            (-> (data/run-mbql-query venues
+                  {:aggregation [:named [:sum [:* $price -1]] "x"]
+                   :breakout    [$category_id]})
                 (get-in [:data :cols])))))
diff --git a/test/metabase/query_processor_test/field_visibility_test.clj b/test/metabase/query_processor_test/field_visibility_test.clj
index 7b872e5b2b128697bc5ee6ad1dd8d42993613540..2d646ee8ab7c574dd9caa90706533690d17ec23d 100644
--- a/test/metabase/query_processor_test/field_visibility_test.clj
+++ b/test/metabase/query_processor_test/field_visibility_test.clj
@@ -4,7 +4,6 @@
              [query-processor-test :refer :all]
              [util :as u]]
             [metabase.models.field :refer [Field]]
-            [metabase.query-processor.middleware.expand :as ql]
             [metabase.test
              [data :as data]
              [util :as tu]]
@@ -14,9 +13,9 @@
 
 ;; make sure that rows where visibility_type = details-only are included and properly marked up
 (defn- get-col-names []
-  (-> (data/run-query venues
-        (ql/order-by (ql/asc $id))
-        (ql/limit 1))
+  (-> (data/run-mbql-query venues
+        {:order-by [[:asc $id]]
+         :limit    1})
       tu/round-fingerprint-cols
       :data
       :cols
@@ -62,8 +61,8 @@
                  [15 "Rüstem Hebel"]]
    :native_form true}
   ;; Filter out the timestamps from the results since they're hard to test :/
-  (-> (data/run-query users
-        (ql/order-by (ql/asc $id)))
+  (-> (data/run-mbql-query users
+        {:order-by [[:asc $id]]})
       booleanize-native-form
       tu/round-fingerprint-cols
       (update-in [:data :rows] (partial mapv (fn [[id name last-login]]
diff --git a/test/metabase/query_processor_test/fields_test.clj b/test/metabase/query_processor_test/fields_test.clj
index 49846c308f078cf4b1fb6b0a101e8c6a9e8c0c91..14b03c8032a95a5da7eccef91a655b82f5c4d7c2 100644
--- a/test/metabase/query_processor_test/fields_test.clj
+++ b/test/metabase/query_processor_test/fields_test.clj
@@ -1,28 +1,28 @@
 (ns metabase.query-processor-test.fields-test
   "Tests for the `:fields` clause."
   (:require [metabase.query-processor-test :refer :all]
-            [metabase.query-processor.middleware.expand :as ql]
             [metabase.test.data :as data]))
 
-;; Test that we can restrict the Fields that get returned to the ones specified, and that results come back in the order of the IDs in the `fields` clause
+;; Test that we can restrict the Fields that get returned to the ones specified, and that results come back in the
+;; order of the IDs in the `fields` clause
 (qp-expect-with-all-engines
-    {:rows    [["Red Medicine"                  1]
-               ["Stout Burgers & Beers"         2]
-               ["The Apple Pan"                 3]
-               ["Wurstküche"                    4]
-               ["Brite Spot Family Restaurant"  5]
-               ["The 101 Coffee Shop"           6]
-               ["Don Day Korean Restaurant"     7]
-               ["25°"                           8]
-               ["Krua Siri"                     9]
-               ["Fred 62"                      10]]
-     :columns (->columns "name" "id")
-     :cols    [(venues-col :name)
-               (venues-col :id)]
-     :native_form true}
-  (->> (data/run-query venues
-                  (ql/fields $name $id)
-                  (ql/limit 10)
-                  (ql/order-by (ql/asc $id)))
+  {:rows        [["Red Medicine"                  1]
+                 ["Stout Burgers & Beers"         2]
+                 ["The Apple Pan"                 3]
+                 ["Wurstküche"                    4]
+                 ["Brite Spot Family Restaurant"  5]
+                 ["The 101 Coffee Shop"           6]
+                 ["Don Day Korean Restaurant"     7]
+                 ["25°"                           8]
+                 ["Krua Siri"                     9]
+                 ["Fred 62"                      10]]
+   :columns     (->columns "name" "id")
+   :cols        [(venues-col :name)
+                 (venues-col :id)]
+   :native_form true}
+    (->> (data/run-mbql-query venues
+           {:fields   [$name $id]
+            :limit    10
+            :order-by [[:asc $id]]})
        booleanize-native-form
        (format-rows-by [str int])))
diff --git a/test/metabase/query_processor_test/filter_test.clj b/test/metabase/query_processor_test/filter_test.clj
index 4ef5759b2ec1f51078f341fd6874e6c68f0f90b6..caa72380b2fac43212df333ebd68ec96719498e0 100644
--- a/test/metabase/query_processor_test/filter_test.clj
+++ b/test/metabase/query_processor_test/filter_test.clj
@@ -1,7 +1,6 @@
 (ns metabase.query-processor-test.filter-test
   "Tests for the `:filter` clause."
   (:require [metabase.query-processor-test :refer :all]
-            [metabase.query-processor.middleware.expand :as ql]
             [metabase.test.data :as data]
             [metabase.test.data.datasets :as datasets]))
 
@@ -14,21 +13,18 @@
    [77 "Sushi Nakazawa"           40 40.7318 -74.0045 4]
    [79 "Sushi Yasuda"             40 40.7514 -73.9736 4]
    [81 "Tanoshi Sushi & Sake Bar" 40 40.7677 -73.9533 4]]
-  (-> (data/run-query venues
-        (ql/filter (ql/and (ql/>  $id    50)
-                           (ql/>= $price  4)))
-        (ql/order-by (ql/asc $id)))
+  (-> (data/run-mbql-query venues
+        {:filter   [:and [:> $id 50] [:>= $price 4]]
+         :order-by [[:asc $id]]})
       rows formatted-venues-rows))
 
 ;;; FILTER -- "AND", "<", ">", "!="
 (expect-with-non-timeseries-dbs
   [[21 "PizzaHacker"          58 37.7441 -122.421 2]
    [23 "Taqueria Los Coyotes" 50 37.765  -122.42  2]]
-  (-> (data/run-query venues
-        (ql/filter (ql/and (ql/<  $id 24)
-                           (ql/>  $id 20)
-                           (ql/!= $id 22)))
-        (ql/order-by (ql/asc $id)))
+  (-> (data/run-mbql-query venues
+        {:filter   [:and [:< $id 24] [:> $id 20] [:!= $id 22]]
+         :order-by [[:asc $id]]})
       rows formatted-venues-rows))
 
 ;;; FILTER WITH A FALSE VALUE
@@ -37,9 +33,9 @@
 (expect-with-non-timeseries-dbs
   [[1]]
   (->> (data/dataset places-cam-likes
-         (data/run-query places
-           (ql/aggregation (ql/count))
-           (ql/filter (ql/= $liked false))))
+         (data/run-mbql-query places
+           {:aggregation [[:count]]
+            :filter      [:= $liked false]}))
        rows (format-rows-by [int])))
 
 (defn- ->bool [x] ; SQLite returns 0/1 for false/true;
@@ -56,9 +52,9 @@
   [[1 "Tempest" true]
    [2 "Bullit"  true]]
   (->> (data/dataset places-cam-likes
-         (data/run-query places
-           (ql/filter (ql/= $liked true))
-           (ql/order-by (ql/asc $id))))
+         (data/run-mbql-query places
+           {:filter   [:= $liked true]
+            :order-by [[:asc $id]]}))
        rows (format-rows-by [int str ->bool] :format-nil-values)))
 
 ;;; filter != false
@@ -66,18 +62,18 @@
   [[1 "Tempest" true]
    [2 "Bullit"  true]]
   (->> (data/dataset places-cam-likes
-         (data/run-query places
-           (ql/filter (ql/!= $liked false))
-           (ql/order-by (ql/asc $id))))
+         (data/run-mbql-query places
+           {:filter   [:!= $liked false]
+            :order-by [[:asc $id]]}))
        rows (format-rows-by [int str ->bool] :format-nil-values)))
 
 ;;; filter != true
 (expect-with-non-timeseries-dbs
   [[3 "The Dentist" false]]
   (->> (data/dataset places-cam-likes
-         (data/run-query places
-           (ql/filter (ql/!= $liked true))
-           (ql/order-by (ql/asc $id))))
+         (data/run-mbql-query places
+           {:filter   [:!= $liked true]
+            :order-by [[:asc $id]]}))
        rows (format-rows-by [int str ->bool] :format-nil-values)))
 
 
@@ -85,9 +81,9 @@
 (expect-with-non-timeseries-dbs
   [[21 "PizzaHacker"    58 37.7441 -122.421 2]
    [22 "Gordo Taqueria" 50 37.7822 -122.484 1]]
-  (-> (data/run-query venues
-        (ql/filter (ql/between $id 21 22))
-        (ql/order-by (ql/asc $id)))
+  (-> (data/run-mbql-query venues
+        {:filter   [:between $id 21 22]
+         :order-by [[:asc $id]]})
       rows formatted-venues-rows))
 
 ;;; FILTER -- "BETWEEN" with dates
@@ -96,9 +92,9 @@
    :columns     ["count"]
    :cols        [(aggregate-col :count)]
    :native_form true}
-  (->> (data/run-query checkins
-         (ql/aggregation (ql/count))
-         (ql/filter (ql/between $date "2015-04-01" "2015-05-01")))
+  (->> (data/run-mbql-query checkins
+         {:aggregation [[:count]]
+          :filter      [:between $date "2015-04-01" "2015-05-01"]})
        booleanize-native-form
        (format-rows-by [int])))
 
@@ -108,17 +104,16 @@
    [2 "Stout Burgers & Beers"        11 34.0996 -118.329 2]
    [3 "The Apple Pan"                11 34.0406 -118.428 2]
    [5 "Brite Spot Family Restaurant" 20 34.0778 -118.261 2]]
-  (-> (data/run-query venues
-        (ql/filter (ql/or (ql/<= $id 3)
-                          (ql/= $id 5)))
-        (ql/order-by (ql/asc $id)))
+  (-> (data/run-mbql-query venues
+        {:filter   [:or [:<= $id 3] [:= $id 5]]
+         :order-by [[:asc $id]]})
       rows formatted-venues-rows))
 
 ;;; FILTER -- "INSIDE"
 (expect-with-non-timeseries-dbs
   [[1 "Red Medicine" 4 10.0646 -165.374 3]]
-  (-> (data/run-query venues
-        (ql/filter (ql/inside $latitude $longitude 10.0649 -165.379 10.0641 -165.371)))
+  (-> (data/run-mbql-query venues
+        {:filter [:inside $latitude $longitude 10.0649 -165.379 10.0641 -165.371]})
       rows formatted-venues-rows))
 
 ;;; FILTER - `is-null` & `not-null` on datetime columns
@@ -126,28 +121,27 @@
   [1000]
   (first-row
     (format-rows-by [int]
-      (data/run-query checkins
-        (ql/aggregation (ql/count))
-        (ql/filter (ql/not-null $date))))))
+      (data/run-mbql-query checkins
+        {:aggregation [[:count]]
+         :filter      [:not-null $date]}))))
 
 ;; Creates a query that uses a field-literal. Normally our test queries will use a field placeholder, but
 ;; https://github.com/metabase/metabase/issues/7381 is only triggered by a field literal
 (expect-with-non-timeseries-dbs
   [1000]
-  (let [vec-filter #(assoc % :filter ["NOT_NULL"
-                                      ["field-id"
-                                       ["field-literal" (data/format-name "date") "type/DateTime"]]])]
-    (first-row
-      (format-rows-by [int]
-        (data/run-query checkins
-          (ql/aggregation (ql/count))
-          vec-filter)))))
+  (first-row
+    (format-rows-by [int]
+      (data/run-mbql-query checkins
+        {:aggregation [[:count]]
+         :filter      ["NOT_NULL"
+                       ["field-id"
+                        ["field-literal" (data/format-name "date") "type/DateTime"]]]}))))
 
 (expect-with-non-timeseries-dbs
   true
-  (let [result (first-row (data/run-query checkins
-                            (ql/aggregation (ql/count))
-                            (ql/filter (ql/is-null $date))))]
+  (let [result (first-row (data/run-mbql-query checkins
+                            {:aggregation [[:count]]
+                             :filter      [:is-null $date]}))]
     ;; Some DBs like Mongo don't return any results at all in this case, and there's no easy workaround
     (or (= result [0])
         (= result [0M])
@@ -162,44 +156,45 @@
 (expect-with-non-timeseries-dbs
   [[41 "Cheese Steak Shop" 18 37.7855 -122.44  1]
    [74 "Chez Jay"           2 34.0104 -118.493 2]]
-  (-> (data/run-query venues
-        (ql/filter (ql/starts-with $name "Che"))
-        (ql/order-by (ql/asc $id)))
+  (-> (data/run-mbql-query venues
+        {:filter   [:starts-with $name "Che"]
+         :order-by [[:asc $id]]})
       rows formatted-venues-rows))
 
 (datasets/expect-with-engines (non-timeseries-engines-without-feature :no-case-sensitivity-string-filter-options)
   []
-  (-> (data/run-query venues
-        (ql/filter (ql/starts-with $name "CHE"))
-        (ql/order-by (ql/asc $id)))
+  (-> (data/run-mbql-query venues
+        {:filter   [:starts-with $name "CHE"]
+         :order-by [[:asc $id]]})
       rows formatted-venues-rows))
 
 (datasets/expect-with-engines (non-timeseries-engines-without-feature :no-case-sensitivity-string-filter-options)
   [[41 "Cheese Steak Shop" 18 37.7855 -122.44  1]
    [74 "Chez Jay"           2 34.0104 -118.493 2]]
-  (-> (data/run-query venues
-        (ql/filter (ql/starts-with $name "CHE" {:case-sensitive false}))
-        (ql/order-by (ql/asc $id)))
+  (-> (data/run-mbql-query venues
+        {:filter   [:starts-with $name "CHE" {:case-sensitive false}]
+         :order-by [[:asc $id]]})
       rows formatted-venues-rows))
 
 
 ;;; --------------------------------------------------- ends-with ----------------------------------------------------
+
 (expect-with-non-timeseries-dbs
   [[ 5 "Brite Spot Family Restaurant" 20 34.0778 -118.261 2]
    [ 7 "Don Day Korean Restaurant"    44 34.0689 -118.305 2]
    [17 "Ruen Pair Thai Restaurant"    71 34.1021 -118.306 2]
    [45 "Tu Lan Restaurant"             4 37.7821 -122.41  1]
    [55 "Dal Rae Restaurant"           67 33.983  -118.096 4]]
-  (-> (data/run-query venues
-        (ql/filter (ql/ends-with $name "Restaurant"))
-        (ql/order-by (ql/asc $id)))
+  (-> (data/run-mbql-query venues
+        {:filter   [:ends-with $name "Restaurant"]
+         :order-by [[:asc $id]]})
       rows formatted-venues-rows))
 
 (datasets/expect-with-engines (non-timeseries-engines-without-feature :no-case-sensitivity-string-filter-options)
   []
-  (-> (data/run-query venues
-        (ql/filter (ql/ends-with $name "RESTAURANT"))
-        (ql/order-by (ql/asc $id)))
+  (-> (data/run-mbql-query venues
+        {:filter   [:ends-with $name "RESTAURANT"]
+         :order-by [[:asc $id]]})
       rows formatted-venues-rows))
 
 (datasets/expect-with-engines (non-timeseries-engines-without-feature :no-case-sensitivity-string-filter-options)
@@ -208,9 +203,9 @@
    [17 "Ruen Pair Thai Restaurant"    71 34.1021 -118.306 2]
    [45 "Tu Lan Restaurant"             4 37.7821 -122.41  1]
    [55 "Dal Rae Restaurant"           67 33.983  -118.096 4]]
-  (-> (data/run-query venues
-        (ql/filter (ql/ends-with $name "RESTAURANT" {:case-sensitive false}))
-        (ql/order-by (ql/asc $id)))
+  (-> (data/run-mbql-query venues
+        {:filter   [:ends-with $name "RESTAURANT" {:case-sensitive false}]
+         :order-by [[:asc $id]]})
       rows formatted-venues-rows))
 
 ;;; ---------------------------------------------------- contains ----------------------------------------------------
@@ -218,17 +213,17 @@
   [[31 "Bludso's BBQ"             5 33.8894 -118.207 2]
    [34 "Beachwood BBQ & Brewing" 10 33.7701 -118.191 2]
    [39 "Baby Blues BBQ"           5 34.0003 -118.465 2]]
-  (-> (data/run-query venues
-        (ql/filter (ql/contains $name "BBQ"))
-        (ql/order-by (ql/asc $id)))
+  (-> (data/run-mbql-query venues
+        {:filter   [:contains $name "BBQ"]
+         :order-by [[:asc $id]]})
       rows formatted-venues-rows))
 
 ;; case-insensitive
 (datasets/expect-with-engines (non-timeseries-engines-without-feature :no-case-sensitivity-string-filter-options)
   []
-  (-> (data/run-query venues
-        (ql/filter (ql/contains $name "bbq"))
-        (ql/order-by (ql/asc $id)))
+  (-> (data/run-mbql-query venues
+        {:filter   [:contains $name "bbq"]
+         :order-by [[:asc $id]]})
       rows formatted-venues-rows))
 
 ;; case-insensitive
@@ -236,9 +231,9 @@
   [[31 "Bludso's BBQ"             5 33.8894 -118.207 2]
    [34 "Beachwood BBQ & Brewing" 10 33.7701 -118.191 2]
    [39 "Baby Blues BBQ"           5 34.0003 -118.465 2]]
-  (-> (data/run-query venues
-        (ql/filter (ql/contains $name "bbq" {:case-sensitive false}))
-        (ql/order-by (ql/asc $id)))
+  (-> (data/run-mbql-query venues
+        {:filter   [:contains $name "bbq" {:case-sensitive false}]
+         :order-by [[:asc $id]]})
       rows formatted-venues-rows))
 
 
@@ -248,11 +243,13 @@
 
 (expect-with-non-timeseries-dbs
   [[81]]
-  (->> (data/run-query venues
-         (ql/aggregation (ql/count))
-         (ql/filter (ql/and (ql/!= $price 3)
-                            (ql/or (ql/= $price 1)
-                                   (ql/= $price 2)))))
+  (->> (data/run-mbql-query venues
+         {:aggregation [[:count]]
+          :filter      [:and
+                        [:!= $price 3]
+                        [:or
+                         [:= $price 1]
+                         [:= $price 2]]]})
        rows (format-rows-by [int])))
 
 
@@ -262,16 +259,16 @@
 
 (expect-with-non-timeseries-dbs
   [[81]]
-  (->> (data/run-query venues
-         (ql/aggregation (ql/count))
-         (ql/filter (ql/= $price 1 2)))
+  (->> (data/run-mbql-query venues
+         {:aggregation [[:count]]
+          :filter      [:= $price 1 2]})
        rows (format-rows-by [int])))
 
 (expect-with-non-timeseries-dbs
   [[19]]
-  (->> (data/run-query venues
-         (ql/aggregation (ql/count))
-         (ql/filter (ql/!= $price 1 2)))
+  (->> (data/run-mbql-query venues
+         {:aggregation [[:count]]
+          :filter      [:!= $price 1 2]})
        rows (format-rows-by [int])))
 
 
@@ -293,98 +290,98 @@
   [99]
   (first-row
     (format-rows-by [int]
-      (data/run-query venues
-        (ql/aggregation (ql/count))
-        (ql/filter (ql/not (ql/= $id 1)))))))
+      (data/run-mbql-query venues
+        {:aggregation [[:count]]
+         :filter      [:not [:= $id 1]]}))))
 
 ;;; !=
 (expect-with-non-timeseries-dbs
   [1]
   (first-row
     (format-rows-by [int]
-      (data/run-query venues
-        (ql/aggregation (ql/count))
-        (ql/filter (ql/not (ql/!= $id 1)))))))
+      (data/run-mbql-query venues
+        {:aggregation [[:count]]
+         :filter      [:not [:!= $id 1]]}))))
 ;;; <
 (expect-with-non-timeseries-dbs
   [61]
   (first-row
     (format-rows-by [int]
-      (data/run-query venues
-        (ql/aggregation (ql/count))
-        (ql/filter (ql/not (ql/< $id 40)))))))
+      (data/run-mbql-query venues
+        {:aggregation [[:count]]
+         :filter      [:not [:< $id 40]]}))))
 
 ;;; >
 (expect-with-non-timeseries-dbs
   [40]
   (first-row
     (format-rows-by [int]
-      (data/run-query venues
-        (ql/aggregation (ql/count))
-        (ql/filter (ql/not (ql/> $id 40)))))))
+      (data/run-mbql-query venues
+        {:aggregation [[:count]]
+         :filter      [:not [:> $id 40]]}))))
 
 ;;; <=
 (expect-with-non-timeseries-dbs
   [60]
   (first-row
     (format-rows-by [int]
-      (data/run-query venues
-        (ql/aggregation (ql/count))
-        (ql/filter (ql/not (ql/<= $id 40)))))))
+      (data/run-mbql-query venues
+        {:aggregation [[:count]]
+         :filter      [:not [:<= $id 40]]}))))
 
 ;;; >=
 (expect-with-non-timeseries-dbs
   [39]
   (first-row
     (format-rows-by [int]
-      (data/run-query venues
-        (ql/aggregation (ql/count))
-        (ql/filter (ql/not (ql/>= $id 40)))))))
+      (data/run-mbql-query venues
+        {:aggregation [[:count]]
+         :filter      [:not [:>= $id 40]]}))))
 
 ;;; is-null
 (expect-with-non-timeseries-dbs
   [100]
   (first-row
     (format-rows-by [int]
-      (data/run-query venues
-        (ql/aggregation (ql/count))
-        (ql/filter (ql/not (ql/is-null $id)))))))
+      (data/run-mbql-query venues
+        {:aggregation [[:count]]
+         :filter      [:not [:is-null $id]]}))))
 
 ;;; between
 (expect-with-non-timeseries-dbs
   [89]
   (first-row
     (format-rows-by [int]
-      (data/run-query venues
-        (ql/aggregation (ql/count))
-        (ql/filter (ql/not (ql/between $id 30 40)))))))
+      (data/run-mbql-query venues
+        {:aggregation [[:count]]
+         :filter      [:not [:between $id 30 40]]}))))
 
 ;;; inside
 (expect-with-non-timeseries-dbs
   [39]
   (first-row
     (format-rows-by [int]
-      (data/run-query venues
-        (ql/aggregation (ql/count))
-        (ql/filter (ql/not (ql/inside $latitude $longitude 40 -120 30 -110)))))))
+      (data/run-mbql-query venues
+        {:aggregation [[:count]]
+         :filter      [:not [:inside $latitude $longitude 40 -120 30 -110]]}))))
 
 ;;; starts-with
 (expect-with-non-timeseries-dbs
   [80]
   (first-row
     (format-rows-by [int]
-      (data/run-query venues
-        (ql/aggregation (ql/count))
-        (ql/filter (ql/not (ql/starts-with $name "T")))))))
+      (data/run-mbql-query venues
+        {:aggregation [[:count]]
+         :filter      [:not [:starts-with $name "T"]]}))))
 
 ;;; contains
 (expect-with-non-timeseries-dbs
   [97]
   (first-row
     (format-rows-by [int]
-      (data/run-query venues
-        (ql/aggregation (ql/count))
-        (ql/filter (ql/not (ql/contains $name "BBQ")))))))
+      (data/run-mbql-query venues
+        {:aggregation [[:count]]
+         :filter      [:not [:contains $name "BBQ"]]}))))
 
 ;;; does-not-contain
 ;;
@@ -394,67 +391,72 @@
   [97]
   (first-row
     (format-rows-by [int]
-      (data/run-query venues
-        (ql/aggregation (ql/count))
-        (ql/filter (ql/does-not-contain $name "BBQ"))))))
+      (data/run-mbql-query venues
+        {:aggregation [[:count]]
+         :filter      [:does-not-contain $name "BBQ"]}))))
 
 ;;; ends-with
 (expect-with-non-timeseries-dbs
   [87]
   (first-row
     (format-rows-by [int]
-      (data/run-query venues
-        (ql/aggregation (ql/count))
-        (ql/filter (ql/not (ql/ends-with $name "a")))))))
+      (data/run-mbql-query venues
+        {:aggregation [[:count]]
+         :filter      [:not [:ends-with $name "a"]]}))))
 
 ;;; and
 (expect-with-non-timeseries-dbs
   [98]
   (first-row
     (format-rows-by [int]
-      (data/run-query venues
-        (ql/aggregation (ql/count))
-        (ql/filter (ql/not (ql/and (ql/> $id 32)
-                                   (ql/contains $name "BBQ"))))))))
+      (data/run-mbql-query venues
+        {:aggregation [[:count]]
+         :filter      [:not [:and
+                             [:> $id 32]
+                             [:contains $name "BBQ"]]]}))))
 ;;; or
 (expect-with-non-timeseries-dbs
   [31]
   (first-row
     (format-rows-by [int]
-      (data/run-query venues
-        (ql/aggregation (ql/count))
-        (ql/filter (ql/not (ql/or (ql/> $id 32)
-                                  (ql/contains $name "BBQ"))))))))
+      (data/run-mbql-query venues
+        {:aggregation [[:count]]
+         :filter      [:not [:or
+                             [:> $id 32]
+                             [:contains $name "BBQ"]]]}))))
 
 ;;; nested and/or
 (expect-with-non-timeseries-dbs
   [96]
   (first-row
     (format-rows-by [int]
-      (data/run-query venues
-        (ql/aggregation (ql/count))
-        (ql/filter (ql/not (ql/or (ql/and (ql/> $id 32)
-                                          (ql/< $id 35))
-                                  (ql/contains $name "BBQ"))))))))
+      (data/run-mbql-query venues
+        {:aggregation [[:count]]
+         :filter      [:not [:or
+                             [:and
+                              [:> $id 32]
+                              [:< $id 35]]
+                             [:contains $name "BBQ"]]]}))))
 
 ;;; nested not
 (expect-with-non-timeseries-dbs
   [3]
   (first-row
     (format-rows-by [int]
-      (data/run-query venues
-        (ql/aggregation (ql/count))
-        (ql/filter (ql/not (ql/not (ql/contains $name "BBQ"))))))))
+      (data/run-mbql-query venues
+        {:aggregation [[:count]]
+         :filter      [:not [:not [:contains $name "BBQ"]]]}))))
 
 ;;; not nested inside and/or
 (expect-with-non-timeseries-dbs
   [1]
   (first-row
     (format-rows-by [int]
-      (data/run-query venues
-        (ql/aggregation (ql/count))
-        (ql/filter (ql/and (ql/not (ql/> $id 32))
-                           (ql/contains $name "BBQ")))))))
+      (data/run-mbql-query venues
+        {:aggregation [[:count]]
+         :filter      [:and
+                       [:not [:> $id 32]]
+                       [:contains $name "BBQ"]]}))))
 
 
 ;; make sure that filtering with dates truncating to minutes works (#4632)
@@ -462,15 +464,15 @@
   [107]
   (first-row
     (format-rows-by [int]
-      (data/run-query checkins
-        (ql/aggregation (ql/count))
-        (ql/filter (ql/between (ql/datetime-field $date :minute) "2015-01-01T12:30:00" "2015-05-31"))))))
+      (data/run-mbql-query checkins
+        {:aggregation [[:count]]
+         :filter      [:between [:datetime-field $date :minute] "2015-01-01T12:30:00" "2015-05-31"]}))))
 
 ;; make sure that filtering with dates bucketing by weeks works (#4956)
 (expect-with-non-timeseries-dbs
   [7]
   (first-row
     (format-rows-by [int]
-      (data/run-query checkins
-        (ql/aggregation (ql/count))
-        (ql/filter (ql/= (ql/datetime-field $date :week) "2015-06-21T07:00:00.000000000-00:00"))))))
+      (data/run-mbql-query checkins
+        {:aggregation [[:count]]
+         :filter      [:= [:datetime-field $date :week] "2015-06-21T07:00:00.000000000-00:00"]}))))
diff --git a/test/metabase/query_processor_test/joins_test.clj b/test/metabase/query_processor_test/joins_test.clj
index 8bf39505309b540fb41ee9b1a0e40c2eb86a1fb3..5a621f19947b5d690413865af2359fc31c72a43c 100644
--- a/test/metabase/query_processor_test/joins_test.clj
+++ b/test/metabase/query_processor_test/joins_test.clj
@@ -1,7 +1,6 @@
 (ns metabase.query-processor-test.joins-test
   "Test for JOIN behavior."
   (:require [metabase.query-processor-test :refer :all]
-            [metabase.query-processor.middleware.expand :as ql]
             [metabase.test.data :as data]
             [metabase.test.data.datasets :as datasets]))
 
@@ -19,11 +18,11 @@
    ["Irvine"       11]
    ["Lakeland"     11]]
   (->> (data/dataset tupac-sightings
-         (data/run-query sightings
-           (ql/aggregation (ql/count))
-           (ql/breakout $city_id->cities.name)
-           (ql/order-by (ql/desc (ql/aggregate-field 0)))
-           (ql/limit 10)))
+         (data/run-mbql-query sightings
+           {:aggregation [[:count]]
+            :breakout    [$city_id->cities.name]
+            :order-by    [[:desc [:aggregation 0]]]
+            :limit       10}))
        rows (format-rows-by [str int])))
 
 
@@ -33,9 +32,9 @@
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :foreign-keys)
   [[60]]
   (->> (data/dataset tupac-sightings
-         (data/run-query sightings
-           (ql/aggregation (ql/count))
-           (ql/filter (ql/= $category_id->categories.id 8))))
+         (data/run-mbql-query sightings
+           {:aggregation [[:count]]
+            :filter      [:= $category_id->categories.id 8]}))
        rows (format-rows-by [int])))
 
 
@@ -54,10 +53,10 @@
    [897 "Wearing a Biggie Shirt"]
    [499 "In the Expa Office"]]
   (->> (data/dataset tupac-sightings
-         (data/run-query sightings
-           (ql/fields $id $category_id->categories.name)
-           (ql/order-by (ql/desc $timestamp))
-           (ql/limit 10)))
+         (data/run-mbql-query sightings
+           {:fields   [$id $category_id->categories.name]
+            :order-by [[:desc $timestamp]]
+            :limit    10}))
        rows (format-rows-by [int str])))
 
 
@@ -79,25 +78,25 @@
    [2 13  77]
    [2 13 202]]
   (->> (data/dataset tupac-sightings
-         (data/run-query sightings
-           (ql/order-by (ql/asc $city_id->cities.name)
-                        (ql/desc $category_id->categories.name)
-                        (ql/asc $id))
-           (ql/limit 10)))
-       ;; drop timestamps. reverse ordering to make the results columns order match order_by
+         (data/run-mbql-query sightings
+           {:order-by [[:asc $city_id->cities.name]
+                       [:desc $category_id->categories.name]
+                       [:asc $id]]
+            :limit    10}))
+       ;; drop timestamps. reverse ordering to make the results columns order match order-by
        rows (map butlast) (map reverse) (format-rows-by [int int int])))
 
 
 ;; Check that trying to use a Foreign Key fails for Mongo
 (datasets/expect-with-engines (non-timeseries-engines-without-feature :foreign-keys)
   {:status :failed
-   :error "foreign-keys is not supported by this driver."}
+   :error  "foreign-keys is not supported by this driver."}
   (select-keys (data/dataset tupac-sightings
-                 (data/run-query sightings
-                   (ql/order-by (ql/asc $city_id->cities.name)
-                                (ql/desc $category_id->categories.name)
-                                (ql/asc $id))
-                   (ql/limit 10)))
+                 (data/run-mbql-query sightings
+                   {:order-by [[:asc $city_id->cities.name]
+                               [:desc $category_id->categories.name]
+                               [:asc $id]]
+                    :limit    10}))
                [:status :error]))
 
 
@@ -124,7 +123,7 @@
    ["Ronald Raven"     1]]
   (data/dataset avian-singles
     (format-rows-by [str int]
-      (rows (data/run-query messages
-              (ql/aggregation (ql/count))
-              (ql/breakout $sender_id->users.name)
-              (ql/filter (ql/= $reciever_id->users.name "Rasta Toucan")))))))
+      (rows (data/run-mbql-query messages
+              {:aggregation [[:count]]
+               :breakout    [$sender_id->users.name]
+               :filter      [:= $reciever_id->users.name "Rasta Toucan"]})))))
diff --git a/test/metabase/query_processor_test/nested_field_test.clj b/test/metabase/query_processor_test/nested_field_test.clj
index 41945c8aabcccf273bee044eac12278d9565521e..03bfa20f583c740335a0c8cd2dadbacbbdbd1bda 100644
--- a/test/metabase/query_processor_test/nested_field_test.clj
+++ b/test/metabase/query_processor_test/nested_field_test.clj
@@ -1,7 +1,6 @@
 (ns metabase.query-processor-test.nested-field-test
   "Tests for nested field access."
   (:require [metabase.query-processor-test :refer :all]
-            [metabase.query-processor.middleware.expand :as ql]
             [metabase.test.data :as data]
             [metabase.test.data.datasets :as datasets]))
 
@@ -18,10 +17,10 @@
    [426 "Kyle's Low-Carb Grill"]
    [470 "Kyle's Low-Carb Grill"]]
   (->> (data/dataset geographical-tips
-         (data/run-query tips
-           (ql/filter (ql/= $tips.venue.name "Kyle's Low-Carb Grill"))
-           (ql/order-by (ql/asc $id))
-           (ql/limit 10)))
+         (data/run-mbql-query tips
+           {:filter   [:= $tips.venue.name "Kyle's Low-Carb Grill"]
+            :order-by [[:asc $id]]
+            :limit    10}))
        rows (mapv (fn [[id _ _ _ {venue-name :name}]] [id venue-name]))))
 
 ;;; Nested Field in ORDER
@@ -56,25 +55,26 @@
      :small  "http://cloudfront.net/cedd4221-dbdb-46c3-95a9-935cce6b3fe5/small.jpg"}
     {:phone "415-901-6541", :name "Pacific Heights Free-Range Eatery", :categories ["Free-Range" "Eatery"], :id "88b361c8-ce69-4b2e-b0f2-9deedd574af6"}]]
   (rows (data/dataset geographical-tips
-          (data/run-query tips
-            (ql/filter (ql/and (ql/= $tips.source.service "twitter")
-                               (ql/= $tips.source.username "kyle")))
-            (ql/order-by (ql/asc $tips.venue.name))))))
+          (data/run-mbql-query tips
+            {:filter   [:and
+                        [:= $tips.source.service "twitter"]
+                        [:= $tips.source.username "kyle"]]
+             :order-by [[:asc $tips.venue.name]]}))))
 
 ;; Nested Field in AGGREGATION
 ;; Let's see how many *distinct* venue names are mentioned
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :nested-fields)
   [99]
   (first-row (data/dataset geographical-tips
-               (data/run-query tips
-                 (ql/aggregation (ql/distinct $tips.venue.name))))))
+               (data/run-mbql-query tips
+                 {:aggregation [[:distinct $tips.venue.name]]}))))
 
 ;; Now let's just get the regular count
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :nested-fields)
   [500]
   (first-row (data/dataset geographical-tips
-               (data/run-query tips
-                 (ql/aggregation (ql/count $tips.venue.name))))))
+               (data/run-mbql-query tips
+                 {:aggregation [[:count $tips.venue.name]]}))))
 
 ;;; Nested Field in BREAKOUT
 ;; Let's see how many tips we have by source.service
@@ -87,9 +87,9 @@
    :columns     ["source.service" "count"]
    :native_form true}
   (->> (data/dataset geographical-tips
-         (data/run-query tips
-           (ql/aggregation (ql/count))
-           (ql/breakout $tips.source.service)))
+         (data/run-mbql-query tips
+           {:aggregation [[:count]]
+            :breakout    [$tips.source.service]}))
        booleanize-native-form
        :data (#(dissoc % :cols)) (format-rows-by [str int])))
 
@@ -108,10 +108,10 @@
              ["Mission Homestyle Churros"]
              ["Sameer's Pizza Liquor Store"]]}
   (select-keys (:data (data/dataset geographical-tips
-                        (data/run-query tips
-                          (ql/fields $tips.venue.name)
-                          (ql/order-by (ql/asc $id))
-                          (ql/limit 10))))
+                        (data/run-mbql-query tips
+                          {:fields   [$tips.venue.name]
+                           :order-by [[:asc $id]]
+                           :limit    10})))
                [:columns :rows]))
 
 
@@ -132,8 +132,8 @@
    ["rasta_toucan"  13]
    [nil            400]]
   (->> (data/dataset geographical-tips
-         (data/run-query tips
-           (ql/aggregation (ql/count))
-           (ql/breakout $tips.source.mayor)
-           (ql/order-by (ql/asc (ql/aggregate-field 0)))))
+         (data/run-mbql-query tips
+           {:aggregation [[:count]]
+            :breakout    [$tips.source.mayor]
+            :order-by    [[:asc [:aggregation 0]]]}))
        rows (format-rows-by [identity int])))
diff --git a/test/metabase/query_processor_test/nested_queries_test.clj b/test/metabase/query_processor_test/nested_queries_test.clj
index d1d7be8641e11855b9fc6139120956828bdc63e4..1374247075694aa06c6c93faa077b5526409cd8d 100644
--- a/test/metabase/query_processor_test/nested_queries_test.clj
+++ b/test/metabase/query_processor_test/nested_queries_test.clj
@@ -58,7 +58,7 @@
         {:database (data/id)
          :type     :query
          :query    {:source-query {:source-table (data/id :venues)
-                                   :order-by     [:asc (data/id :venues :id)]
+                                   :order-by     [[:asc (data/id :venues :id)]]
                                    :limit        10}
                     :limit        5}}))))
 
@@ -108,7 +108,7 @@
                                                    (quoted-identifier :venues :name)
                                                    (quoted-identifier :venues :latitude)
                                                    (quoted-identifier :venues))}
-                    :order-by     [:asc [:field-literal (keyword (data/format-name :id)) :type/Integer]]
+                    :order-by     [[:asc [:field-literal (data/format-name :id) :type/Integer]]]
                     :limit        5}}))))
 
 
@@ -132,6 +132,69 @@
                     :aggregation  [:count]
                     :breakout     [[:field-literal (keyword (data/format-name :price)) :type/Integer]]}}))))
 
+;; Test including a breakout of a nested query column that follows an FK
+(datasets/expect-with-engines (non-timeseries-engines-with-feature :nested-queries :foreign-keys)
+  {:rows [[1 174] [2 474] [3 78] [4 39]]
+   :cols [{:name "price", :base_type (data/expected-base-type->actual :type/Integer)}
+          {:name "count", :base_type :type/Integer}]}
+  (rows+cols
+    (format-rows-by [int int]
+      (qp/process-query
+        {:database (data/id)
+         :type     :query
+         :query    {:source-query {:source-table (data/id :checkins)
+                                   :filter [:> (data/id :checkins :date) "2014-01-01"]}
+                    :aggregation  [:count]
+                    :order-by     [[:asc [:fk-> (data/id :checkins :venue_id) (data/id :venues :price)]]]
+                    :breakout     [[:fk-> (data/id :checkins :venue_id) (data/id :venues :price)]]}}))))
+
+;; Test two breakout columns from the nested query, both following an FK
+(datasets/expect-with-engines (non-timeseries-engines-with-feature :nested-queries :foreign-keys)
+  {:rows [[2 33.7701 7]
+          [2 33.8894 8]
+          [2 33.9997 7]
+          [3 10.0646 2]
+          [4 33.983 2]],
+   :cols [{:name "price", :base_type (data/expected-base-type->actual :type/Integer)}
+          {:name "latitude", :base_type :type/Float}
+          {:name "count", :base_type :type/Integer}]}
+  (rows+cols
+    (format-rows-by [int (partial u/round-to-decimals 4) int]
+      (qp/process-query
+        {:database (data/id)
+         :type     :query
+         :query    {:source-query {:source-table (data/id :checkins)
+                                   :filter       [:> (data/id :checkins :date) "2014-01-01"]}
+                    :filter       [:< [:fk-> (data/id :checkins :venue_id) (data/id :venues :latitude)] 34]
+                    :aggregation  [:count]
+                    :order-by     [[:asc [:fk-> (data/id :checkins :venue_id) (data/id :venues :price)]]]
+                    :breakout     [[:fk-> (data/id :checkins :venue_id) (data/id :venues :price)]
+                                   [:fk-> (data/id :checkins :venue_id) (data/id :venues :latitude)]]}}))))
+
+;; Test two breakout columns from the nested query, one following an FK the other from the source table
+(datasets/expect-with-engines (non-timeseries-engines-with-feature :nested-queries :foreign-keys)
+  {:rows [[1 1 6]
+          [1 2 14]
+          [1 3 13]
+          [1 4 8]
+          [1 5 10]],
+   :cols [{:name "price", :base_type (data/expected-base-type->actual :type/Integer)}
+          {:name "user_id", :base_type :type/Integer}
+          {:name "count", :base_type :type/Integer}]}
+  (rows+cols
+    (format-rows-by [int int int]
+      (qp/process-query
+        {:database (data/id)
+         :type     :query
+         :query    {:source-query {:source-table (data/id :checkins)
+                                   :filter       [:> (data/id :checkins :date) "2014-01-01"]}
+                    :aggregation  [:count]
+                    :filter       [:= [:fk-> (data/id :checkins :venue_id) (data/id :venues :price)] 1]
+                    :order-by     [[:asc [:fk-> (data/id :checkins :venue_id) (data/id :venues :price)]]]
+                    :breakout     [[:fk-> (data/id :checkins :venue_id) (data/id :venues :price)]
+                                   [:field-literal (keyword (data/format-name :user_id)) :type/Integer]]
+                    :limit        5}}))))
+
 ;; make sure we can do a query with breakout and aggregation using a SQL source query
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :nested-queries)
   breakout-results
@@ -167,9 +230,9 @@
    :query    (merge {:source-table (str "card__" (u/get-id card))}
                     additional-clauses)})
 
-;; Make sure we can run queries using source table `card__id` format. This is the format that is actually used by the frontend;
-;; it gets translated to the normal `source-query` format by middleware. It's provided as a convenience so only minimal changes
-;; need to be made to the frontend.
+;; Make sure we can run queries using source table `card__id` format. This is the format that is actually used by the
+;; frontend; it gets translated to the normal `source-query` format by middleware. It's provided as a convenience so
+;; only minimal changes need to be made to the frontend.
 (expect
   breakout-results
   (tt/with-temp Card [card (venues-mbql-card-def)]
@@ -282,18 +345,24 @@
      :query    {:source-query {:source-table (data/id :venues)
                                :aggregation  [[:stddev [:field-id (data/id :venues :id)]]]
                                :breakout     [[:field-id (data/id :venues :price)]]
-                               :order-by     [[[:aggregate-field 0] :descending]]}
+                               :order-by     [[[:aggregation 0] :descending]]}
                 :aggregation  [[:avg [:field-literal "stddev" :type/Integer]]]}}))
 
+(def ^:private ^:const ^String venues-source-with-category-sql
+  (str "(SELECT \"PUBLIC\".\"VENUES\".\"ID\" AS \"ID\", \"PUBLIC\".\"VENUES\".\"NAME\" AS \"NAME\", "
+       "\"PUBLIC\".\"VENUES\".\"CATEGORY_ID\" AS \"CATEGORY_ID\", \"PUBLIC\".\"VENUES\".\"LATITUDE\" AS \"LATITUDE\", "
+       "\"PUBLIC\".\"VENUES\".\"LONGITUDE\" AS \"LONGITUDE\", \"PUBLIC\".\"VENUES\".\"PRICE\" AS \"PRICE\", \"category_id\" AS \"category_id\" "
+       "FROM \"PUBLIC\".\"VENUES\") \"source\""))
+
 ;; make sure that we handle [field-id [field-literal ...]] forms gracefully, despite that not making any sense
 (expect
-  {:query  (format "SELECT \"category_id\" AS \"category_id\" FROM %s GROUP BY \"category_id\" ORDER BY \"category_id\" ASC LIMIT 10" venues-source-sql)
+  {:query  (format "SELECT \"category_id\" AS \"category_id\" FROM %s GROUP BY \"category_id\" ORDER BY \"category_id\" ASC LIMIT 10" venues-source-with-category-sql)
    :params nil}
   (qp/query->native
     {:database (data/id)
      :type     :query
      :query    {:source-query {:source-table (data/id :venues)}
-                :breakout     [:field-id [:field-literal "category_id" :type/Integer]]
+                :breakout     [[:field-id [:field-literal "category_id" :type/Integer]]]
                 :limit        10}}))
 
 ;; Make sure we can filter by string fields
@@ -323,7 +392,7 @@
   (tt/with-temp Card [card {:dataset_query {:database (data/id)
                                             :type     :native
                                             :native   {:query         "SELECT * FROM PRODUCTS WHERE CATEGORY = {{category}} LIMIT 10"
-                                                       :template_tags {:category {:name         "category"
+                                                       :template-tags {:category {:name         "category"
                                                                                   :display_name "Category"
                                                                                   :type         "text"
                                                                                   :required     true
@@ -495,7 +564,7 @@
         rows)))
 
 
-;; Make suer you're allowed to save a query that uses a SQL-based source query even if you don't have SQL *write*
+;; Make suer you're allowed to save a query that uses a SQL-based source query even if you don't have SQL *write*1337
 ;; permissions (#6845)
 
 ;; Check that perms for a Card with a source query require that you have read permissions for its Collection!
@@ -525,6 +594,10 @@
     (tt/with-temp Database [db {:details (:details (data/db)), :engine "h2"}]
       (f db))))
 
+(defmacro ^:private with-temp-copy-of-test-db {:style/indent 1} [[db-binding] & body]
+  `(do-with-temp-copy-of-test-db (fn [~(or db-binding '_)]
+                                   ~@body)))
+
 (defn- save-card-via-API-with-native-source-query!
   "Attempt to save a Card that uses a native source query and belongs to a Collection with `collection-id` via the API
   using Rasta. Use this to test how the API endpoint behaves based on certain permissions grants for the `All Users`
@@ -546,14 +619,14 @@
 ;; Card is in, and write permissions for the Collection you're trying to save the new Card in
 (expect
   :ok
-  (do-with-temp-copy-of-test-db
-   (fn [db]
-     (tt/with-temp* [Collection [source-card-collection]
-                     Collection [dest-card-collection]]
-       (perms/grant-collection-read-permissions!      (group/all-users) source-card-collection)
-       (perms/grant-collection-readwrite-permissions! (group/all-users) dest-card-collection)
-       (save-card-via-API-with-native-source-query! 200 db source-card-collection dest-card-collection)
-       :ok))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (with-temp-copy-of-test-db [db]
+      (tt/with-temp* [Collection [source-card-collection]
+                      Collection [dest-card-collection]]
+        (perms/grant-collection-read-permissions!      (group/all-users) source-card-collection)
+        (perms/grant-collection-readwrite-permissions! (group/all-users) dest-card-collection)
+        (save-card-via-API-with-native-source-query! 200 db source-card-collection dest-card-collection)
+        :ok))))
 
 ;; however, if we do *not* have read permissions for the source Card's collection we shouldn't be allowed to save the
 ;; query. This API call should fail
@@ -561,39 +634,39 @@
 ;; Card in the Root Collection
 (expect
   "You don't have permissions to do that."
-  (do-with-temp-copy-of-test-db
-   (fn [db]
-     (tt/with-temp Collection [dest-card-collection]
-       (perms/grant-collection-readwrite-permissions! (group/all-users) dest-card-collection)
-       (save-card-via-API-with-native-source-query! 403 db nil dest-card-collection)))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (with-temp-copy-of-test-db [db]
+      (tt/with-temp Collection [dest-card-collection]
+        (perms/grant-collection-readwrite-permissions! (group/all-users) dest-card-collection)
+        (save-card-via-API-with-native-source-query! 403 db nil dest-card-collection)))))
 
 ;; Card in a different Collection for which we do not have perms
 (expect
   "You don't have permissions to do that."
-  (do-with-temp-copy-of-test-db
-   (fn [db]
-     (tt/with-temp* [Collection [source-card-collection]
-                     Collection [dest-card-collection]]
-       (perms/grant-collection-readwrite-permissions! (group/all-users) dest-card-collection)
-       (save-card-via-API-with-native-source-query! 403 db source-card-collection dest-card-collection)))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (with-temp-copy-of-test-db [db]
+      (tt/with-temp* [Collection [source-card-collection]
+                      Collection [dest-card-collection]]
+        (perms/grant-collection-readwrite-permissions! (group/all-users) dest-card-collection)
+        (save-card-via-API-with-native-source-query! 403 db source-card-collection dest-card-collection)))))
 
 ;; similarly, if we don't have *write* perms for the dest collection it should also fail
 
 ;; Try to save in the Root Collection
 (expect
   "You don't have permissions to do that."
-  (do-with-temp-copy-of-test-db
-   (fn [db]
-     (tt/with-temp Collection [source-card-collection]
-       (perms/grant-collection-read-permissions! (group/all-users) source-card-collection)
-       (save-card-via-API-with-native-source-query! 403 db source-card-collection nil)))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (with-temp-copy-of-test-db [db]
+      (tt/with-temp Collection [source-card-collection]
+        (perms/grant-collection-read-permissions! (group/all-users) source-card-collection)
+        (save-card-via-API-with-native-source-query! 403 db source-card-collection nil)))))
 
 ;; Try to save in a different Collection for which we do not have perms
 (expect
   "You don't have permissions to do that."
-  (do-with-temp-copy-of-test-db
-   (fn [db]
-     (tt/with-temp* [Collection [source-card-collection]
-                     Collection [dest-card-collection]]
-       (perms/grant-collection-read-permissions! (group/all-users) source-card-collection)
-       (save-card-via-API-with-native-source-query! 403 db source-card-collection dest-card-collection)))))
+  (tu/with-non-admin-groups-no-root-collection-perms
+    (with-temp-copy-of-test-db [db]
+      (tt/with-temp* [Collection [source-card-collection]
+                      Collection [dest-card-collection]]
+        (perms/grant-collection-read-permissions! (group/all-users) source-card-collection)
+        (save-card-via-API-with-native-source-query! 403 db source-card-collection dest-card-collection)))))
diff --git a/test/metabase/query_processor_test/order_by_test.clj b/test/metabase/query_processor_test/order_by_test.clj
index be0c7ff93f4b66823684dc9adf4359cb52c9ee7a..97babc3e6a0f80b99c74e6d4c0fb838419a50040 100644
--- a/test/metabase/query_processor_test/order_by_test.clj
+++ b/test/metabase/query_processor_test/order_by_test.clj
@@ -2,7 +2,6 @@
   "Tests for the `:order-by` clause."
   (:require [clojure.math.numeric-tower :as math]
             [metabase.query-processor-test :refer :all]
-            [metabase.query-processor.middleware.expand :as ql]
             [metabase.test.data :as data]
             [metabase.test.data.datasets :as datasets :refer [*engine*]]))
 
@@ -17,18 +16,18 @@
    [2  9 833]
    [2  8 380]
    [2  5 719]]
-  (->> (data/run-query checkins
-         (ql/fields $venue_id $user_id $id)
-         (ql/order-by (ql/asc $venue_id)
-                      (ql/desc $user_id)
-                      (ql/asc $id))
-         (ql/limit 10))
+  (->> (data/run-mbql-query checkins
+         {:fields   [$venue_id $user_id $id]
+          :order-by [[:asc $venue_id]
+                     [:desc $user_id]
+                     [:asc $id]]
+          :limit    10})
        rows (format-rows-by [int int int])))
 
 
-;;; ------------------------------------------------------------ order_by aggregate fields ------------------------------------------------------------
+;;; ------------------------------------------- order-by aggregate fields --------------------------------------------
 
-;;; order_by aggregate ["count"]
+;;; order-by aggregate ["count"]
 (qp-expect-with-all-engines
   {:columns     [(data/format-name "price")
                  "count"]
@@ -39,15 +38,15 @@
    :cols        [(breakout-col (venues-col :price))
                  (aggregate-col :count)]
    :native_form true}
-  (->> (data/run-query venues
-         (ql/aggregation (ql/count))
-         (ql/breakout $price)
-         (ql/order-by (ql/asc (ql/aggregate-field 0))))
+  (->> (data/run-mbql-query venues
+         {:aggregation [[:count]]
+          :breakout    [$price]
+          :order-by    [[:asc [:aggregation 0]]]})
        booleanize-native-form
        (format-rows-by [int int])))
 
 
-;;; order_by aggregate ["sum" field-id]
+;;; order-by aggregate ["sum" field-id]
 (qp-expect-with-all-engines
   {:columns     [(data/format-name "price")
                  "sum"]
@@ -58,15 +57,15 @@
    :cols        [(breakout-col (venues-col :price))
                  (aggregate-col :sum (venues-col :id))]
    :native_form true}
-  (->> (data/run-query venues
-         (ql/aggregation (ql/sum $id))
-         (ql/breakout $price)
-         (ql/order-by (ql/desc (ql/aggregate-field 0))))
+  (->> (data/run-mbql-query venues
+         {:aggregation [[:sum $id]]
+          :breakout    [$price]
+          :order-by    [[:desc [:aggregation 0]]]})
        booleanize-native-form
        (format-rows-by [int int])))
 
 
-;;; order_by aggregate ["distinct" field-id]
+;;; order-by aggregate ["distinct" field-id]
 (qp-expect-with-all-engines
   {:columns     [(data/format-name "price")
                  "count"]
@@ -77,15 +76,15 @@
    :cols        [(breakout-col (venues-col :price))
                  (aggregate-col :count)]
    :native_form true}
-  (->> (data/run-query venues
-         (ql/aggregation (ql/distinct $id))
-         (ql/breakout $price)
-         (ql/order-by (ql/asc (ql/aggregate-field 0))))
+  (->> (data/run-mbql-query venues
+         {:aggregation [[:distinct $id]]
+          :breakout    [$price]
+          :order-by    [[:asc [:aggregation 0]]]})
        booleanize-native-form
        (format-rows-by [int int])))
 
 
-;;; order_by aggregate ["avg" field-id]
+;;; order-by aggregate ["avg" field-id]
 (expect-with-non-timeseries-dbs
   {:columns     [(data/format-name "price")
                  "avg"]
@@ -96,14 +95,14 @@
    :cols        [(breakout-col (venues-col :price))
                  (aggregate-col :avg (venues-col :category_id))]
    :native_form true}
-  (->> (data/run-query venues
-         (ql/aggregation (ql/avg $category_id))
-         (ql/breakout $price)
-         (ql/order-by (ql/asc (ql/aggregate-field 0))))
+  (->> (data/run-mbql-query venues
+         {:aggregation [[:avg $category_id]]
+          :breakout    [$price]
+          :order-by    [[:asc [:aggregation 0]]]})
        booleanize-native-form
        :data (format-rows-by [int int])))
 
-;;; ### order_by aggregate ["stddev" field-id]
+;;; ### order-by aggregate ["stddev" field-id]
 ;; SQRT calculations are always NOT EXACT (normal behavior) so round everything to the nearest int.
 ;; Databases might use different versions of SQRT implementations
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :standard-deviation-aggregations)
@@ -116,9 +115,9 @@
    :cols        [(breakout-col (venues-col :price))
                  (aggregate-col :stddev (venues-col :category_id))]
    :native_form true}
-  (->> (data/run-query venues
-         (ql/aggregation (ql/stddev $category_id))
-         (ql/breakout $price)
-         (ql/order-by (ql/desc (ql/aggregate-field 0))))
+  (->> (data/run-mbql-query venues
+         {:aggregation [[:stddev $category_id]]
+          :breakout    [$price]
+          :order-by    [[:desc [:aggregation 0]]]})
        booleanize-native-form
        :data (format-rows-by [int (comp int math/round)])))
diff --git a/test/metabase/query_processor_test/page_test.clj b/test/metabase/query_processor_test/page_test.clj
index 014e755f939157bb752df45d1a818c80390a70f0..a7bd724862e77feb448d6130ffe8c7e61a11c6a0 100644
--- a/test/metabase/query_processor_test/page_test.clj
+++ b/test/metabase/query_processor_test/page_test.clj
@@ -1,7 +1,6 @@
 (ns metabase.query-processor-test.page-test
   "Tests for the `:page` clause."
   (:require [metabase.query-processor-test :refer :all]
-            [metabase.query-processor.middleware.expand :as ql]
             [metabase.test.data :as data]))
 
 ;; Test that we can get "pages" of results.
@@ -13,9 +12,9 @@
    [3 "Artisan"]
    [4 "Asian"]
    [5 "BBQ"]]
-  (->> (data/run-query categories
-         (ql/page {:page 1, :items 5})
-         (ql/order-by (ql/asc $id)))
+  (->> (data/run-mbql-query categories
+         {:page     {:page 1, :items 5}
+          :order-by [[:asc $id]]})
        rows (format-rows-by [int str])))
 
 ;; get the second page
@@ -25,7 +24,7 @@
    [ 8 "Beer Garden"]
    [ 9 "Breakfast / Brunch"]
    [10 "Brewery"]]
-  (->> (data/run-query categories
-         (ql/page {:page 2, :items 5})
-         (ql/order-by (ql/asc $id)))
+  (->> (data/run-mbql-query categories
+         {:page     {:page 2, :items 5}
+          :order-by [[:asc $id]]})
        rows (format-rows-by [int str])))
diff --git a/test/metabase/query_processor_test/remapping_test.clj b/test/metabase/query_processor_test/remapping_test.clj
index 36cf45f351a1bee8c049481920038dba3cc75bbe..bc8baf65860b64e442292a7a87925c62532d109b 100644
--- a/test/metabase/query_processor_test/remapping_test.clj
+++ b/test/metabase/query_processor_test/remapping_test.clj
@@ -1,41 +1,41 @@
 (ns metabase.query-processor-test.remapping-test
   "Tests for the remapping results"
-  (:require [metabase.query-processor-test :refer :all]
-            [metabase.query-processor.middleware
-             [add-dimension-projections :as add-dimension-projections]
-             [expand :as ql]]
+  (:require [metabase
+             [query-processor :as qp]
+             [query-processor-test :refer :all]]
+            [metabase.models.dimension :refer [Dimension]]
+            [metabase.query-processor.middleware.add-dimension-projections :as add-dimension-projections]
             [metabase.test
              [data :as data]
              [util :as tu]]
-            [metabase.test.data.datasets :as datasets]))
+            [metabase.test.data.datasets :as datasets]
+            [toucan.db :as db]))
 
 (qp-expect-with-all-engines
-  {:rows  [["20th Century Cafe" 12 "Café Sweets"]
-           ["25°" 11 "Café"]
-           ["33 Taps" 7 "Beer Garden"]
-           ["800 Degrees Neapolitan Pizzeria" 58 "Ramen"]]
-   :columns [(data/format-name "name")
-             (data/format-name "category_id")
-             "Foo"]
-   :cols    [(venues-col :name)
-             (assoc (venues-col :category_id) :remapped_to "Foo")
-             (#'add-dimension-projections/create-remapped-col "Foo" (data/format-name "category_id"))]
+  {:rows        [["20th Century Cafe" 12 "Café Sweets"]
+                 ["25°" 11 "Café"]
+                 ["33 Taps" 7 "Beer Garden"]
+                 ["800 Degrees Neapolitan Pizzeria" 58 "Ramen"]]
+   :columns     [(data/format-name "name")
+                 (data/format-name "category_id")
+                 "Foo"]
+   :cols        [(venues-col :name)
+                 (assoc (venues-col :category_id) :remapped_to "Foo")
+                 (#'add-dimension-projections/create-remapped-col "Foo" (data/format-name "category_id"))]
    :native_form true}
   (data/with-data
     (data/create-venue-category-remapping "Foo")
-    (->> (data/run-query venues
-           (ql/fields $name $category_id)
-           (ql/order-by (ql/asc $name))
-           (ql/limit 4))
+    (->> (data/run-mbql-query venues
+           {:fields   [$name $category_id]
+            :order-by [[:asc $name]]
+            :limit    4})
          booleanize-native-form
          (format-rows-by [str int str]))))
 
 (defn- select-columns
-  "Focuses the given resultset to columns that return true when passed
-  to `COLUMNS-PRED`. Typically this would be done as part of the
-  query, however there's a bug currently preventing that from working
-  when remapped. This allows the data compared to be smaller and avoid
-  that bug."
+  "Focuses the given resultset to columns that return true when passed to `columns-pred`. Typically this would be done
+  as part of the query, however there's a bug currently preventing that from working when remapped. This allows the
+  data compared to be smaller and avoid that bug."
   [columns-pred results]
   (let [col-indexes (remove nil? (map-indexed (fn [idx col]
                                                 (when (columns-pred col)
@@ -53,29 +53,74 @@
                      (map #(mapv % col-indexes) rows))))))
 
 (datasets/expect-with-engines (non-timeseries-engines-with-feature :foreign-keys)
-  {:rows   [["20th Century Cafe" 2 "Café"]
-            ["25°" 2 "Burger"]
-            ["33 Taps" 2 "Bar"]
-            ["800 Degrees Neapolitan Pizzeria" 2 "Pizza"]]
-   :columns [(:name (venues-col :name))
-             (:name (venues-col :price))
-             (data/format-name "name_2")]
-   :cols    [(venues-col :name)
-             (venues-col :price)
-             (assoc (categories-col :name)
-               :fk_field_id (data/id :venues :category_id)
-               :display_name "Foo"
-               :name (data/format-name "name_2")
-               :remapped_from (data/format-name "category_id")
-               :schema_name nil)]
+  {:rows        [["20th Century Cafe" 2 "Café"]
+                 ["25°" 2 "Burger"]
+                 ["33 Taps" 2 "Bar"]
+                 ["800 Degrees Neapolitan Pizzeria" 2 "Pizza"]]
+   :columns     [(:name (venues-col :name))
+                 (:name (venues-col :price))
+                 (data/format-name "name_2")]
+   :cols        [(venues-col :name)
+                 (venues-col :price)
+                 (assoc (categories-col :name)
+                   :fk_field_id (data/id :venues :category_id)
+                   :display_name "Foo"
+                   :name (data/format-name "name_2")
+                   :remapped_from (data/format-name "category_id")
+                   :schema_name nil)]
    :native_form true}
   (data/with-data
     (data/create-venue-category-fk-remapping "Foo")
-    (->> (data/run-query venues
-           (ql/order-by (ql/asc $name))
-           (ql/limit 4))
+    (->> (data/run-mbql-query venues
+           {:order-by [[:asc $name]]
+            :limit    4})
          booleanize-native-form
          (format-rows-by [int str int double double int str])
          (select-columns (set (map data/format-name ["name" "price" "name_2"])))
          tu/round-fingerprint-cols
          :data)))
+
+;; Check that we can have remappings when we include a `:fields` clause that restricts the query fields returned
+(datasets/expect-with-engines (non-timeseries-engines-with-feature :foreign-keys)
+  {:rows        [["20th Century Cafe" 2 "Café"]
+                 ["25°" 2 "Burger"]
+                 ["33 Taps" 2 "Bar"]
+                 ["800 Degrees Neapolitan Pizzeria" 2 "Pizza"]]
+   :columns     [(:name (venues-col :name))
+                 (:name (venues-col :price))
+                 (data/format-name "name_2")]
+   :cols        [(venues-col :name)
+                 (venues-col :price)
+                 (assoc (categories-col :name)
+                   :fk_field_id (data/id :venues :category_id)
+                   :display_name "Foo"
+                   :name (data/format-name "name_2")
+                   :remapped_from (data/format-name "category_id")
+                   :schema_name nil)]
+   :native_form true}
+  (data/with-data
+    (data/create-venue-category-fk-remapping "Foo")
+    (->> (data/run-mbql-query venues
+           {:fields   [$name $price $category_id]
+            :order-by [[:asc $name]]
+            :limit    4})
+         booleanize-native-form
+         (format-rows-by [str int str str])
+         (select-columns (set (map data/format-name ["name" "price" "name_2"])))
+         tu/round-fingerprint-cols
+         :data)))
+
+;; Test that we can remap inside an MBQL nested query
+(datasets/expect-with-engines (non-timeseries-engines-with-feature :foreign-keys :nested-queries)
+  ["Kinaree Thai Bistro" "Ruen Pair Thai Restaurant" "Yamashiro Hollywood" "Spitz Eagle Rock" "The Gumbo Pot"]
+  (data/with-data
+    (fn []
+      [(db/insert! Dimension {:field_id                (data/id :checkins :venue_id)
+                              :name                    "venue-remapping"
+                              :type                    :external
+                              :human_readable_field_id (data/id :venues :name)})])
+    (->> (data/run-mbql-query checkins
+           {:order-by [[:asc $date]]
+            :limit    5})
+         rows
+         (map last))))
diff --git a/test/metabase/query_processor_test/time_field_test.clj b/test/metabase/query_processor_test/time_field_test.clj
index bd91e02f5cac173bf08f398ab0a63c2aa1afdb47..ef2c40ac2d06c82d968a11c8cf0596ee00758246 100644
--- a/test/metabase/query_processor_test/time_field_test.clj
+++ b/test/metabase/query_processor_test/time_field_test.clj
@@ -1,6 +1,5 @@
 (ns metabase.query-processor-test.time-field-test
   (:require [metabase.query-processor-test :as qpt]
-            [metabase.query-processor.middleware.expand :as ql]
             [metabase.test
              [data :as data]
              [util :as tu]]
@@ -8,13 +7,14 @@
              [dataset-definitions :as defs]
              [datasets :refer [*engine*]]]))
 
-(defmacro ^:private time-query [& filter-clauses]
+(defmacro ^:private time-query [additional-clauses]
   `(qpt/rows
      (data/with-db (data/get-or-create-database! defs/test-data-with-time)
-       (data/run-query users
-         (ql/fields ~'$id ~'$name ~'$last_login_time)
-         (ql/order-by (ql/asc ~'$id))
-         ~@filter-clauses))))
+       (data/run-mbql-query users
+         ~(merge
+           {:fields   `[~'$id ~'$name ~'$last_login_time]
+            :order-by `[[:asc ~'$id]]}
+           additional-clauses)))))
 
 ;; Basic between query on a time field
 (qpt/expect-with-non-timeseries-dbs-except #{:oracle :mongo :redshift :sparksql}
@@ -24,9 +24,7 @@
 
     [[1 "Plato Yeshua" "08:30:00.000Z"]
      [4 "Simcha Yan"   "08:30:00.000Z"]])
-  (time-query (ql/filter (ql/between $last_login_time
-                                     "08:00:00"
-                                     "09:00:00"))))
+  (time-query {:filter [:between $last_login_time "08:00:00" "09:00:00"]}))
 
 ;; Basic between query on a time field with milliseconds
 (qpt/expect-with-non-timeseries-dbs-except #{:oracle :mongo :redshift :sparksql}
@@ -36,9 +34,7 @@
 
     [[1 "Plato Yeshua" "08:30:00.000Z"]
      [4 "Simcha Yan"   "08:30:00.000Z"]])
-  (time-query (ql/filter (ql/between $last_login_time
-                                     "08:00:00.000"
-                                     "09:00:00.000"))))
+  (time-query {:filter [:between $last_login_time "08:00:00.000" "09:00:00.000"]}))
 
 ;; Basic > query with a time field
 (qpt/expect-with-non-timeseries-dbs-except #{:oracle :mongo :redshift :sparksql}
@@ -50,7 +46,7 @@
     [[3 "Kaneonuskatew Eiran" "16:15:00.000Z"]
      [5 "Quentin Sören" "17:30:00.000Z"]
      [10 "Frans Hevel" "19:30:00.000Z"]])
-  (time-query (ql/filter (ql/> $last_login_time "16:00:00.000Z"))))
+  (time-query {:filter [:> $last_login_time "16:00:00.000Z"]}))
 
 ;; Basic query with an = filter on a time field
 (qpt/expect-with-non-timeseries-dbs-except #{:oracle :mongo :redshift :sparksql}
@@ -58,7 +54,7 @@
     [[3 "Kaneonuskatew Eiran" "16:15:00"]]
 
     [[3 "Kaneonuskatew Eiran" "16:15:00.000Z"]])
-  (time-query (ql/filter (ql/= $last_login_time "16:15:00.000Z"))))
+  (time-query {:filter [:= $last_login_time "16:15:00.000Z"]}))
 
 ;; Query with a time filter and a report timezone
 (qpt/expect-with-non-timeseries-dbs-except #{:oracle :mongo :redshift :sparksql}
@@ -95,8 +91,10 @@
     [[1 "Plato Yeshua" "08:30:00.000Z"]
      [4 "Simcha Yan" "08:30:00.000Z"]])
   (tu/with-temporary-setting-values [report-timezone "America/Los_Angeles"]
-    (time-query (ql/filter (apply ql/between
-                                  $last_login_time
-                                  (if (qpt/supports-report-timezone? *engine*)
-                                    ["08:00:00" "09:00:00"]
-                                    ["08:00:00-00:00" "09:00:00-00:00"]))))))
+    (time-query {:filter (vec (cons
+                               :between
+                               (cons
+                                $last_login_time
+                                (if (qpt/supports-report-timezone? *engine*)
+                                  ["08:00:00" "09:00:00"]
+                                  ["08:00:00-00:00" "09:00:00-00:00"]))))})))
diff --git a/test/metabase/query_processor_test/timezones_test.clj b/test/metabase/query_processor_test/timezones_test.clj
index 0dce8ceaad4d6e9c2d963154b47e9fa55850569e..9f13ddfd2d7bd371925443205ccc5c74a4572c46 100644
--- a/test/metabase/query_processor_test/timezones_test.clj
+++ b/test/metabase/query_processor_test/timezones_test.clj
@@ -1,50 +1,25 @@
 (ns metabase.query-processor-test.timezones-test
-  (:require [clojure.java.jdbc :as jdbc]
-            [expectations :refer :all]
-            [metabase
+  (:require [metabase
              [query-processor :as qp]
-             [query-processor-test :as qptest]]
-            [metabase.query-processor.middleware.expand :as ql]
-            [metabase.query-processor-test :as qpt]
+             [query-processor-test :as qpt]]
             [metabase.test
              [data :as data]
              [util :as tu]]
-            [metabase.test.data.interface :as i]
-            [metabase.test.data.mysql :as mysql-data]
-            [metabase.driver.generic-sql :as sql]
-            [metabase.test.data.generic-sql :as generic-sql]
-            [metabase.test.data.datasets :refer [expect-with-engine expect-with-engines *engine* *driver*]]
-            [metabase.test.data.dataset-definitions :as defs]
+            [metabase.test.data
+             [dataset-definitions :as defs]
+             [datasets :refer [*driver* *engine* expect-with-engine expect-with-engines]]
+             [generic-sql :as generic-sql]
+             [interface :as i]]
             [toucan.db :as db])
   (:import metabase.driver.mysql.MySQLDriver))
 
 (def ^:private mysql-driver (MySQLDriver.))
 
-(defn- fix-mysql-timestamps?
-  "Returns true if the database at `db-spec` needs to have it's timestamps fixed. See the `update-mysql-timestamps
-  comment for more information on why these are being fixed"
-  [db-spec]
-  (empty? (jdbc/query db-spec "select 1 from users where id=1 and last_login='2014-04-01 01:30:00'")))
-
-(defn- update-mysql-timestamps
-  "Unfortunately the timestamps we insert in this dataset are in UTC, but MySQL is inserting them as if they were in
-  pacific time. This means that they are rolling them forward 7 (or 8) hours. Instead of inserting 08:30 it's
-  inserting 15:30. This is wrong, rather than hack something together that weaves through all of the data loading
-  code, this function just fixes up the timestamps after the data is loaded using MySQL's `convert_tz` function"
-  []
-  (when (= :mysql *engine*)
-    (let [details (i/database->connection-details mysql-driver :db {:database-name "test-data-with-timezones"})
-          db-spec (sql/connection-details->spec mysql-driver details)]
-      (when (fix-mysql-timestamps? db-spec)
-        (jdbc/execute! db-spec "update users set last_login=convert_tz(last_login,'UTC','America/Los_Angeles')")))))
-
 (defn- call-with-timezones-db [f]
   ;; Does the database exist?
   (when-not (i/metabase-instance defs/test-data-with-timezones *engine*)
     ;; The database doesn't exist, so we need to create it
-    (data/get-or-create-database! defs/test-data-with-timezones)
-    ;; The db has been created but the timestamps are wrong on MySQL, fix them up
-    (update-mysql-timestamps))
+    (data/get-or-create-database! defs/test-data-with-timezones))
   ;; The database can now be used in tests
   (data/with-db (data/get-or-create-database! defs/test-data-with-timezones)
     (f)))
@@ -73,11 +48,9 @@
     [12 "Kfir Caj" "2014-07-03T01:30:00.000Z"]}
   (with-tz-db
     (tu/with-temporary-setting-values [report-timezone "Europe/Brussels"]
-      (-> (data/run-query users
-            (ql/filter (ql/between $last_login
-                                   "2014-07-02"
-                                   "2014-07-03")))
-          qptest/rows
+      (-> (data/run-mbql-query users
+            {:filter [:between $last_login "2014-07-02" "2014-07-03"]})
+          qpt/rows
           set))))
 
 ;; Query PG using a report-timezone set to pacific time. Should adjust the query parameter using that report timezone
@@ -86,11 +59,9 @@
   default-pacific-results
   (with-tz-db
     (tu/with-temporary-setting-values [report-timezone "America/Los_Angeles"]
-      (-> (data/run-query users
-            (ql/filter (ql/between $last_login
-                                   "2014-08-02T03:00:00.000000"
-                                   "2014-08-02T06:00:00.000000")))
-          qptest/rows
+      (-> (data/run-mbql-query users
+            {:filter [:between $last_login "2014-08-02T03:00:00.000000" "2014-08-02T06:00:00.000000"]})
+          qpt/rows
           set))))
 
 (defn- quote-name [identifier]
@@ -120,14 +91,14 @@
   (with-tz-db
     (tu/with-temporary-setting-values [report-timezone "America/Los_Angeles"]
       (process-query'
-       {:database (data/id)
-        :type :native
+       {:database   (data/id)
+        :type       :native
         :native     {:query         (format "select %s, %s, %s from %s where cast(last_login as date) between {{date1}} and {{date2}}"
                                             (field-identifier :users :id)
                                             (field-identifier :users :name)
                                             (field-identifier :users :last_login)
                                             (users-table-identifier))
-                     :template_tags {:date1 {:name "date1" :display_name "Date1" :type "date" }
+                     :template-tags {:date1 {:name "date1" :display_name "Date1" :type "date" }
                                      :date2 {:name "date2" :display_name "Date2" :type "date" }}}
         :parameters [{:type "date/single" :target ["variable" ["template-tag" "date1"]] :value "2014-08-02T02:00:00.000000"}
                      {:type "date/single" :target ["variable" ["template-tag" "date2"]] :value "2014-08-02T06:00:00.000000"}]}))))
@@ -138,14 +109,14 @@
   (with-tz-db
     (tu/with-temporary-setting-values [report-timezone "America/Los_Angeles"]
       (process-query'
-       {:database (data/id)
-        :type :native
+       {:database   (data/id)
+        :type       :native
         :native     {:query         (format "select %s, %s, %s from %s where {{ts_range}}"
                                             (field-identifier :users :id)
                                             (field-identifier :users :name)
                                             (field-identifier :users :last_login)
                                             (users-table-identifier))
-                     :template_tags {:ts_range {:name "ts_range", :display_name "Timestamp Range", :type "dimension",
+                     :template-tags {:ts_range {:name      "ts_range", :display_name "Timestamp Range", :type "dimension",
                                                 :dimension ["field-id" (data/id :users :last_login)]}}}
         :parameters [{:type "date/range", :target ["dimension" ["template-tag" "ts_range"]], :value "2014-08-02~2014-08-03"}]}))))
 
@@ -162,7 +133,7 @@
                                             (field-identifier :users :name)
                                             (field-identifier :users :last_login)
                                             (users-table-identifier))
-                     :template_tags {:just_a_date {:name "just_a_date", :display_name "Just A Date", :type "dimension",
+                     :template-tags {:just_a_date {:name "just_a_date", :display_name "Just A Date", :type "dimension",
                                                    :dimension ["field-id" (data/id :users :last_login)]}}}
         :parameters [{:type "date/single", :target ["dimension" ["template-tag" "just_a_date"]], :value "2014-08-02"}]}))))
 
@@ -172,11 +143,9 @@
   default-pacific-results
   (with-tz-db
     (tu/with-temporary-setting-values [report-timezone "America/Los_Angeles"]
-      (-> (data/run-query users
-            (ql/filter (ql/between $last_login
-                                   "2014-08-02T10:00:00.000000Z"
-                                   "2014-08-02T13:00:00.000000Z")))
-          qptest/rows
+      (-> (data/run-mbql-query users
+            {:filter [:between $last_login "2014-08-02T10:00:00.000000Z" "2014-08-02T13:00:00.000000Z"]})
+          qpt/rows
           set))))
 
 ;; Checking UTC report timezone filtering and responses
@@ -184,11 +153,9 @@
   default-utc-results
   (with-tz-db
     (tu/with-temporary-setting-values [report-timezone "UTC"]
-      (-> (data/run-query users
-            (ql/filter (ql/between $last_login
-                                   "2014-08-02T10:00:00.000000"
-                                   "2014-08-02T13:00:00.000000")))
-          qptest/rows
+      (-> (data/run-mbql-query users
+            {:filter [:between $last_login "2014-08-02T10:00:00.000000" "2014-08-02T13:00:00.000000"]})
+          qpt/rows
           set))))
 
 ;; With no report timezone, the JVM timezone is used. For our tests this is UTC so this should be the same as
@@ -196,9 +163,7 @@
 (expect-with-engines [:postgres :bigquery :mysql]
   default-utc-results
   (with-tz-db
-    (-> (data/run-query users
-          (ql/filter (ql/between $last_login
-                                 "2014-08-02T10:00:00.000000"
-                                 "2014-08-02T13:00:00.000000")))
-        qptest/rows
+    (-> (data/run-mbql-query users
+          {:filter [:between $last_login "2014-08-02T10:00:00.000000" "2014-08-02T13:00:00.000000"]})
+        qpt/rows
         set)))
diff --git a/test/metabase/query_processor_test/unix_timestamp_test.clj b/test/metabase/query_processor_test/unix_timestamp_test.clj
index ee8f9737d2b595fc90583d40182e586a128afe84..d9e6e69e897b78cbe6f5550029ba6234ad530ede 100644
--- a/test/metabase/query_processor_test/unix_timestamp_test.clj
+++ b/test/metabase/query_processor_test/unix_timestamp_test.clj
@@ -1,13 +1,10 @@
 (ns metabase.query-processor-test.unix-timestamp-test
   "Tests for UNIX timestamp support."
   (:require [metabase.query-processor-test :refer :all]
-            [metabase.query-processor.middleware.expand :as ql]
             [metabase.test
              [data :as data]
              [util :as tu]]
-            [metabase.test.data
-             [datasets :as datasets :refer [*driver* *engine*]]
-             [interface :as i]]))
+            [metabase.test.data.datasets :as datasets :refer [*engine*]]))
 
 ;; There were 10 "sad toucan incidents" on 2015-06-02 in UTC
 (expect-with-non-timeseries-dbs
@@ -20,10 +17,11 @@
   ;; the issue go away
   (tu/with-temporary-setting-values [report-timezone "UTC"]
     (count (rows (data/dataset sad-toucan-incidents
-                   (data/run-query incidents
-                     (ql/filter (ql/and (ql/> $timestamp "2015-06-01")
-                                        (ql/< $timestamp "2015-06-03")))
-                     (ql/order-by (ql/asc $timestamp))))))))
+                   (data/run-mbql-query incidents
+                     {:filter   [:and
+                                 [:> $timestamp "2015-06-01"]
+                                 [:< $timestamp "2015-06-03"]]
+                      :order-by [[:asc $timestamp]]}))))))
 
 (expect-with-non-timeseries-dbs
   (cond
@@ -77,8 +75,8 @@
 
   (tu/with-temporary-setting-values [report-timezone "America/Los_Angeles"]
     (->> (data/dataset sad-toucan-incidents
-           (data/run-query incidents
-             (ql/aggregation (ql/count))
-             (ql/breakout $timestamp)
-             (ql/limit 10)))
+           (data/run-mbql-query incidents
+             {:aggregation [[:count]]
+              :breakout    [$timestamp]
+              :limit       10}))
          rows (format-rows-by [identity int]))))
diff --git a/test/metabase/related_test.clj b/test/metabase/related_test.clj
index d3e68ff5114e72eb19a503e34a9acde25b1faade..d2f8ecdda20c4920ceed6bbfc3794ed701e53796 100644
--- a/test/metabase/related_test.clj
+++ b/test/metabase/related_test.clj
@@ -1,13 +1,17 @@
 (ns metabase.related-test
-  (:require [expectations :refer :all]
+  (:require [clojure.java.jdbc :as jdbc]
+            [expectations :refer :all]
+            [metabase
+             [related :as r :refer :all]
+             [sync :as sync]]
             [metabase.models
              [card :refer [Card]]
              [collection :refer [Collection]]
              [metric :refer [Metric]]
              [segment :refer [Segment]]]
-            [metabase.related :as r :refer :all]
             [metabase.test.data :as data]
-            [metabase.test.data.users :as users]
+            [metabase.test.data.one-off-dbs :as one-off-dbs]
+            [toucan.util.test :as tt][metabase.test.data.users :as users]
             [toucan.util.test :as tt]))
 
 (expect
@@ -23,19 +27,19 @@
    0.0
    1.0]
   (tt/with-temp* [Card [{card-id-1 :id}
-                        {:dataset_query {:query {:source_table (data/id :venues)
+                        {:dataset_query {:query {:source-table (data/id :venues)
                                                  :aggregation [:sum [:field-id (data/id :venues :price)]]
                                                  :breakout [[:field-id (data/id :venues :category_id)]]}
                                          :type  :query
                                          :database (data/id)}}]
                   Card [{card-id-2 :id}
-                        {:dataset_query {:query {:source_table (data/id :venues)
+                        {:dataset_query {:query {:source-table (data/id :venues)
                                                  :aggregation [:sum [:field-id (data/id :venues :longitude)]]
                                                  :breakout [[:field-id (data/id :venues :category_id)]]}
                                          :type  :query
                                          :database (data/id)}}]
                   Card [{card-id-3 :id}
-                        {:dataset_query {:query {:source_table (data/id :venues)
+                        {:dataset_query {:query {:source-table (data/id :venues)
                                                  :aggregation [:sum [:field-id (data/id :venues :longitude)]]
                                                  :breakout [[:field-id (data/id :venues :latitude)]]}
                                          :type  :query
@@ -49,22 +53,22 @@
   [& body]
   `(tt/expect-with-temp [Collection [{~'collection-id :id}]
                          Metric     [{~'metric-id-a :id} {:table_id (data/id :venues)
-                                                          :definition {:source_table (data/id :venues)
+                                                          :definition {:source-table (data/id :venues)
                                                                        :aggregation [:sum [:field-id (data/id :venues :price)]]}}]
                          Metric     [{~'metric-id-b :id} {:table_id (data/id :venues)
-                                                          :definition {:source_table (data/id :venues)
+                                                          :definition {:source-table (data/id :venues)
                                                                        :aggregation [:count]}}]
                          Segment    [{~'segment-id-a :id} {:table_id (data/id :venues)
-                                                           :definition {:source_table (data/id :venues)
-                                                                        :filter [:not= [:field-id (data/id :venues :category_id)] nil]}}]
+                                                           :definition {:source-table (data/id :venues)
+                                                                        :filter [:!= [:field-id (data/id :venues :category_id)] nil]}}]
                          Segment    [{~'segment-id-b :id} {:table_id (data/id :venues)
-                                                           :definition {:source_table (data/id :venues)
-                                                                        :filter [:not= [:field-id (data/id :venues :name)] nil]}}]
+                                                           :definition {:source-table (data/id :venues)
+                                                                        :filter [:!= [:field-id (data/id :venues :name)] nil]}}]
                          Card       [{~'card-id-a :id :as ~'card-a}
                                      {:table_id (data/id :venues)
                                       :dataset_query {:type :query
                                                       :database (data/id)
-                                                      :query {:source_table (data/id :venues)
+                                                      :query {:source-table (data/id :venues)
                                                               :aggregation [:sum [:field-id (data/id :venues :price)]]
                                                               :breakout [[:field-id (data/id :venues :category_id)]]}}}]
                          Card       [{~'card-id-b :id :as ~'card-b}
@@ -72,14 +76,14 @@
                                       :collection_id ~'collection-id
                                       :dataset_query {:type :query
                                                       :database (data/id)
-                                                      :query {:source_table (data/id :venues)
+                                                      :query {:source-table (data/id :venues)
                                                               :aggregation [:sum [:field-id (data/id :venues :longitude)]]
                                                               :breakout [[:field-id (data/id :venues :category_id)]]}}}]
                          Card       [{~'card-id-c :id :as ~'card-c}
                                      {:table_id (data/id :venues)
                                       :dataset_query {:type :query
                                                       :database (data/id)
-                                                      :query {:source_table (data/id :venues)
+                                                      :query {:source-table (data/id :venues)
                                                               :aggregation [:sum [:field-id (data/id :venues :longitude)]]
                                                               :breakout [[:field-id (data/id :venues :name)]
                                                                          [:field-id (data/id :venues :latitude)]]}}}]]
@@ -130,6 +134,28 @@
        result-mask))
 
 
+;; We should ignore non-active entities
+
+(defn- exec! [& statements]
+  (doseq [statement statements]
+    (jdbc/execute! one-off-dbs/*conn* [statement])))
+
+(expect
+  [1 0]
+  (one-off-dbs/with-blank-db
+    (exec! "CREATE TABLE blueberries_consumed (num SMALLINT NOT NULL, weight FLOAT)")
+    (one-off-dbs/insert-rows-and-sync! (range 50))
+    (let [count-related-fields (fn []
+                                 (->> ((users/user->client :crowberto) :get 200
+                                       (format "field/%s/related" (data/id :blueberries_consumed :num)))
+                                      :fields
+                                      count))
+          before               (count-related-fields)]
+      (exec! "ALTER TABLE blueberries_consumed DROP COLUMN weight")
+      (sync/sync-database! (data/db))
+      [before (count-related-fields)])))
+
+
 ;; Test transitive similarity:
 ;; (A is similar to B and B is similar to C, but A is not similar to C). Test if
 ;; this property holds and `:similar-questions` for A returns B, for B A and C,
diff --git a/test/metabase/sync/analyze/classifiers/name_test.clj b/test/metabase/sync/analyze/classifiers/name_test.clj
index 6b244c08753d9502cafc681324449bfdf51801ce..8741638cda4d1386c388c91b29dd422bec582952 100644
--- a/test/metabase/sync/analyze/classifiers/name_test.clj
+++ b/test/metabase/sync/analyze/classifiers/name_test.clj
@@ -37,7 +37,7 @@
                                          :special_type :type/FK
                                          :name         "City"
                                          :base_type    :type/Text}]]
-    (-> field-id Field (infer-special-type nil) :special_type)))
+    (-> field-id Field (infer-and-assoc-special-type nil) :special_type)))
 
 ;; ... but overwrite other types to alow evolution of our type system
 (expect
@@ -47,4 +47,4 @@
                                          :special_type :type/Category
                                          :name         "City"
                                          :base_type    :type/Text}]]
-    (-> field-id Field (infer-special-type nil) :special_type)))
+    (-> field-id Field (infer-and-assoc-special-type nil) :special_type)))
diff --git a/test/metabase/sync/analyze/fingerprint/datetime_test.clj b/test/metabase/sync/analyze/fingerprint/datetime_test.clj
deleted file mode 100644
index 770cd8d96b0143bb8c845532ea584db7712a202e..0000000000000000000000000000000000000000
--- a/test/metabase/sync/analyze/fingerprint/datetime_test.clj
+++ /dev/null
@@ -1,9 +0,0 @@
-(ns metabase.sync.analyze.fingerprint.datetime-test
-  (:require [clj-time.core :as t]
-            [expectations :refer :all]
-            [metabase.sync.analyze.fingerprint.datetime :refer :all]))
-
-(expect
-  {:earliest "2013-01-01T00:00:00.000Z"
-   :latest   "2018-01-01T00:00:00.000Z"}
-  (datetime-fingerprint ["2013" "2018" "2015"]))
diff --git a/test/metabase/sync/analyze/fingerprint/fingerprinters_test.clj b/test/metabase/sync/analyze/fingerprint/fingerprinters_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..9214e52df4ad16ed9d1de4615c2275c6ea8e96ee
--- /dev/null
+++ b/test/metabase/sync/analyze/fingerprint/fingerprinters_test.clj
@@ -0,0 +1,40 @@
+(ns metabase.sync.analyze.fingerprint.fingerprinters-test
+  (:require [expectations :refer :all]
+            [metabase.models.field :as field]
+            [metabase.sync.analyze.fingerprint.fingerprinters :refer :all]
+            [metabase.util.date :as du]))
+
+(expect
+  {:global {:distinct-count 3}
+   :type {:type/DateTime {:earliest (du/date->iso-8601 #inst "2013")
+                          :latest   (du/date->iso-8601 #inst "2018")}}}
+  (transduce identity
+             (fingerprinter (field/map->FieldInstance {:base_type :type/DateTime}))
+             [#inst "2013" #inst "2018" #inst "2015"]))
+
+(expect
+  {:global {:distinct-count 1}
+   :type {:type/DateTime {:earliest nil
+                          :latest   nil}}}
+  (transduce identity
+             (fingerprinter (field/map->FieldInstance {:base_type :type/DateTime}))
+             (repeat 10 nil)))
+
+(expect
+  {:global {:distinct-count 3}
+   :type {:type/Number {:avg 2.0
+                        :min 1.0
+                        :max 3.0}}}
+  (transduce identity
+             (fingerprinter (field/map->FieldInstance {:base_type :type/Number}))
+             [1.0 2.0 3.0]))
+
+(expect
+  {:global {:distinct-count 5}
+   :type   {:type/Text {:percent-json 0.2,
+                        :percent-url 0.0,
+                        :percent-email 0.0,
+                        :average-length 6.4}}}
+  (transduce identity
+             (fingerprinter (field/map->FieldInstance {:base_type :type/Text}))
+             ["metabase" "more" "like" "metabae" "[1, 2, 3]"]))
diff --git a/test/metabase/sync/analyze/fingerprint/number_test.clj b/test/metabase/sync/analyze/fingerprint/number_test.clj
deleted file mode 100644
index 06d7da0df1e00c2e40b36ed8551e4533038cfa63..0000000000000000000000000000000000000000
--- a/test/metabase/sync/analyze/fingerprint/number_test.clj
+++ /dev/null
@@ -1,7 +0,0 @@
-(ns metabase.sync.analyze.fingerprint.number-test
-  (:require [expectations :refer :all]
-            [metabase.sync.analyze.fingerprint.number :as number]))
-
-(expect
-  (approximately (double Long/MAX_VALUE))
-  (:avg (number/number-fingerprint [Long/MAX_VALUE Long/MAX_VALUE])))
diff --git a/test/metabase/sync/analyze/fingerprint/sample_test.clj b/test/metabase/sync/analyze/fingerprint/sample_test.clj
deleted file mode 100644
index df27eb39d32d7b52302e390799d9c306440f0fbb..0000000000000000000000000000000000000000
--- a/test/metabase/sync/analyze/fingerprint/sample_test.clj
+++ /dev/null
@@ -1,63 +0,0 @@
-(ns metabase.sync.analyze.fingerprint.sample-test
-  (:require [expectations :refer :all]
-            [metabase.models
-             [field :refer [Field]]
-             [table :refer [Table]]]
-            [metabase.sync.analyze.fingerprint.sample :as sample]
-            [metabase.test.data :as data]))
-
-;;; +----------------------------------------------------------------------------------------------------------------+
-;;; |                                             TESTS FOR BASIC-SAMPLE                                             |
-;;; +----------------------------------------------------------------------------------------------------------------+
-
-;; Actually the order the rows come back in isn't really guaranteed so this test is sort of testing a circumstantial
-;; side-effect of the way H2 returns rows when order isn't specified
-(expect
-  [[1 "Red Medicine"]
-   [2 "Stout Burgers & Beers"]
-   [3 "The Apple Pan"]
-   [4 "Wurstküche"]
-   [5 "Brite Spot Family Restaurant"]]
-  (take 5 (#'sample/basic-sample
-           (Table (data/id :venues))
-           [(Field (data/id :venues :id))
-            (Field (data/id :venues :name))])))
-
-;;; +----------------------------------------------------------------------------------------------------------------+
-;;; |                                      TESTS FOR TABLE-SAMPLE->FIELD-SAMPLE                                      |
-;;; +----------------------------------------------------------------------------------------------------------------+
-
-(def ^:private table-sample
-  [[100 "ABC" nil]
-   [200 "DEF" nil]
-   [300 nil   nil]
-   [400 "GHI" nil]
-   [500 "JKL" nil]])
-
-(expect
-  [100 200 300 400 500]
-  (#'sample/table-sample->field-sample table-sample 0))
-
-;; should skip any `nil` values
-(expect
-  ["ABC" "DEF" "GHI" "JKL"]
-  (#'sample/table-sample->field-sample table-sample 1))
-
-;; should return `nil` if all values are `nil` (instead of empty sequence)
-(expect
-  nil
-  (#'sample/table-sample->field-sample table-sample 2))
-
-
-;;; +----------------------------------------------------------------------------------------------------------------+
-;;; |                                            TESTS FOR SAMPLE-FIELDS                                             |
-;;; +----------------------------------------------------------------------------------------------------------------+
-
-(expect
-  [["ID"   [1 2 3 4 5]]
-   ["NAME" ["Red Medicine" "Stout Burgers & Beers" "The Apple Pan" "Wurstküche" "Brite Spot Family Restaurant"]]]
-  (for [[field sample] (sample/sample-fields
-                        (Table (data/id :venues))
-                        [(Field (data/id :venues :id))
-                         (Field (data/id :venues :name))])]
-    [(:name field) (take 5 sample)]))
diff --git a/test/metabase/sync/analyze/fingerprint_test.clj b/test/metabase/sync/analyze/fingerprint_test.clj
index c5e8bedf8800538ce7e0888ea9c4f50eb995f536..76ffe0497513b9e46a72da2c46221ae01658d58f 100644
--- a/test/metabase/sync/analyze/fingerprint_test.clj
+++ b/test/metabase/sync/analyze/fingerprint_test.clj
@@ -1,11 +1,13 @@
 (ns metabase.sync.analyze.fingerprint-test
   "Basic tests to make sure the fingerprint generatation code is doing something that makes sense."
   (:require [expectations :refer :all]
+            [metabase.driver :as driver]
+            [metabase.db :as mdb]
             [metabase.models
              [field :as field :refer [Field]]
              [table :refer [Table]]]
             [metabase.sync.analyze.fingerprint :as fingerprint]
-            [metabase.sync.analyze.fingerprint.sample :as sample]
+            [metabase.sync.analyze.fingerprint.fingerprinters :as fingerprinters]
             [metabase.sync.interface :as i]
             [metabase.test.data :as data]
             [metabase.test.util]
@@ -14,35 +16,6 @@
             [toucan.db :as db]
             [toucan.util.test :as tt]))
 
-(defn- fingerprint [field]
-  (let [[[_ sample]] (sample/sample-fields (field/table field) [field])]
-    (#'fingerprint/fingerprint field sample)))
-
-;; basic test for a numeric Field
-(expect
-  {:global {:distinct-count 4}
-   :type   {:type/Number {:min 1, :max 4, :avg 2.03}}}
-  (fingerprint (Field (data/id :venues :price))))
-
-;; basic test for a Text Field
-(expect
-  {:global {:distinct-count 100}
-   :type   {:type/Text {:percent-json 0.0, :percent-url 0.0, :percent-email 0.0, :average-length 15.63}}}
-  (fingerprint (Field (data/id :venues :name))))
-
-;; a non-integer numeric Field
-(expect
-  {:global {:distinct-count 94}
-   :type   {:type/Number {:min 10.0646, :max 40.7794, :avg 35.50589199999998}}}
-  (fingerprint (Field (data/id :venues :latitude))))
-
-;; a datetime field
-(expect
-  {:global {:distinct-count 618}
-   :type   {:type/DateTime {:earliest "2013-01-03T00:00:00.000Z"
-                            :latest   "2015-12-29T00:00:00.000Z"}}}
-  (fingerprint (Field (data/id :checkins :date))))
-
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                   TESTS FOR WHICH FIELDS NEED FINGERPRINTING                                   |
@@ -64,6 +37,9 @@
   {:where
    [:and
     [:= :active true]
+    [:or
+     [:not (mdb/isa :special_type :type/PK)]
+     [:= :special_type nil]]
     [:not= :visibility_type "retired"]
     [:or
      [:and
@@ -76,6 +52,9 @@
   {:where
    [:and
     [:= :active true]
+    [:or
+     [:not (mdb/isa :special_type :type/PK)]
+     [:= :special_type nil]]
     [:not= :visibility_type "retired"]
     [:or
      [:and
@@ -94,6 +73,9 @@
   {:where
    [:and
     [:= :active true]
+    [:or
+     [:not (mdb/isa :special_type :type/PK)]
+     [:= :special_type nil]]
     [:not= :visibility_type "retired"]
     [:or
      [:and
@@ -112,6 +94,9 @@
   {:where
    [:and
     [:= :active true]
+    [:or
+     [:not (mdb/isa :special_type :type/PK)]
+     [:= :special_type nil]]
     [:not= :visibility_type "retired"]
     [:or
      [:and
@@ -134,10 +119,9 @@
 
 ;; Make sure that the above functions are used correctly to determine which Fields get (re-)fingerprinted
 (defn- field-was-fingerprinted? {:style/indent 0} [fingerprint-versions field-properties]
-  (let [fingerprinted? (atom false)
-        fake-field     (field/map->FieldInstance {:name "Fake Field"})]
+  (let [fingerprinted? (atom false)]
     (with-redefs [i/fingerprint-version->types-that-should-be-re-fingerprinted fingerprint-versions
-                  sample/sample-fields                                         (constantly [[fake-field [1 2 3 4 5]]])
+                  driver/table-rows-sample                                     (constantly [[1] [2] [3] [4] [5]])
                   fingerprint/save-fingerprint!                                (fn [& _] (reset! fingerprinted? true))]
       (tt/with-temp* [Table [table]
                       Field [_ (assoc field-properties :table_id (u/get-id table))]]
@@ -228,7 +212,7 @@
                               :fingerprint_version 1
                               :last_analyzed       (du/->Timestamp #inst "2017-08-09")}]
     (with-redefs [i/latest-fingerprint-version 3
-                  sample/sample-fields         (constantly [[field [1 2 3 4 5]]])
-                  fingerprint/fingerprint      (constantly {:experimental {:fake-fingerprint? true}})]
+                  driver/table-rows-sample         (constantly [[1] [2] [3] [4] [5]])
+                  fingerprinters/fingerprinter (constantly (fingerprinters/constant-fingerprinter {:experimental {:fake-fingerprint? true}}))]
       [(#'fingerprint/fingerprint-table! (Table (data/id :venues)) [field])
        (into {} (db/select-one [Field :fingerprint :fingerprint_version :last_analyzed] :id (u/get-id field)))])))
diff --git a/test/metabase/sync/analyze/query_results_test.clj b/test/metabase/sync/analyze/query_results_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..bd723646b4d75df3bbb6ea54103747ba6389c019
--- /dev/null
+++ b/test/metabase/sync/analyze/query_results_test.clj
@@ -0,0 +1,109 @@
+(ns metabase.sync.analyze.query-results-test
+  (:require [clojure.string :as str]
+            [expectations :refer :all]
+            [metabase
+             [query-processor :as qp]
+             [util :as u]]
+            [metabase.models
+             [card :refer [Card]]
+             [database :as database]]
+            [metabase.sync.analyze.fingerprint.fingerprinters :as fprint]
+            [metabase.sync.analyze.query-results :as qr :refer :all]
+            [metabase.test
+             [data :as data]
+             [util :as tu]]
+            [metabase.test.mock.util :as mutil]
+            [toucan.util.test :as tt]))
+
+(defn- column->name-keyword [field-or-column-metadata]
+  (-> field-or-column-metadata
+      :name
+      str/lower-case
+      keyword))
+
+(defn- name->fingerprints [field-or-metadata]
+  (zipmap (map column->name-keyword field-or-metadata)
+          (map :fingerprint field-or-metadata)))
+
+(defn- name->special-type [field-or-metadata]
+  (zipmap (map column->name-keyword field-or-metadata)
+          (map :special_type field-or-metadata)))
+
+(defn- query->result-metadata
+  [query-map]
+  (->> query-map
+       qp/process-query
+       :data
+       results->column-metadata
+       (tu/round-all-decimals 2)))
+
+(defn- query-for-card [card]
+  {:database database/virtual-id
+   :type     :query
+   :query    {:source-table (str "card__" (u/get-id card))}})
+
+(def ^:private venue-name->special-types
+  {:id          :type/PK,
+   :name        :type/Name,
+   :price       :type/Category,
+   :category_id :type/FK,
+   :latitude    :type/Latitude,
+   :longitude   :type/Longitude})
+
+;; Getting the result metadata for a card backed by an MBQL query should use the fingerprints from the related fields
+(expect
+  mutil/venue-fingerprints
+  (tt/with-temp Card [card {:dataset_query {:database (data/id)
+                                            :type     :query
+                                            :query    {:source-table (data/id :venues)}}}]
+    (tu/throw-if-called fprint/with-global-fingerprinter ; check for a "proper" fingerprinter, fallthrough for PKs is fune.
+      (name->fingerprints
+       (query->result-metadata (query-for-card card))))))
+
+;; Getting the result metadata for a card backed by an MBQL query should just infer the types of all the fields
+(expect
+  venue-name->special-types
+  (tt/with-temp Card [card {:dataset_query {:database (data/id)
+                                            :type     :query
+                                            :query    {:source-table (data/id :venues)}}}]
+    (name->special-type (query->result-metadata (query-for-card card)))))
+
+;; Native queries don't know what the associated Fields are for the results, we need to compute the fingerprints, but
+;; they should sill be the same except for some of the optimizations we do when we have all the information.
+(expect
+  (update mutil/venue-fingerprints :category_id assoc :type {:type/Number {:min 2.0, :max 74.0, :avg 29.98}})
+  (tt/with-temp Card [card {:dataset_query {:database (data/id)
+                                            :type     :native
+                                            :native   {:query "select * from venues"}}}]
+    (name->fingerprints
+     (query->result-metadata (query-for-card card)))))
+
+;; Similarly, check that we computed the correct special types. Note that we don't know that the category_id is an FK
+;; as it's just an integer flowing through, similarly Price isn't found to be a category as we're inferring by name
+;; only
+(expect
+  (assoc venue-name->special-types :category_id nil, :price nil)
+  (tt/with-temp Card [card {:dataset_query {:database (data/id)
+                                            :type     :native
+                                            :native   {:query "select * from venues"}}}]
+    (name->special-type
+     (query->result-metadata (query-for-card card)))))
+
+;; Limiting to just 1 column on an MBQL query should still get the result metadata from the Field
+(expect
+  (select-keys mutil/venue-fingerprints [:longitude])
+  (tt/with-temp Card [card {:dataset_query {:database (data/id)
+                                            :type     :query
+                                            :query    {:source-table (data/id :venues)}}}]
+    (tu/throw-if-called fprint/fingerprinter
+      (name->fingerprints
+       (query->result-metadata (assoc-in (query-for-card card) [:query :fields] [[:field-id (data/id :venues :longitude)]]))))))
+
+;; Similar query as above, just native so that we need to calculate the fingerprint
+(expect
+  (select-keys mutil/venue-fingerprints [:longitude])
+  (tt/with-temp Card [card {:dataset_query {:database (data/id)
+                                            :type     :native
+                                            :native   {:query "select longitude from venues"}}}]
+    (name->fingerprints
+     (query->result-metadata (query-for-card card)))))
diff --git a/test/metabase/sync/analyze/special_types/values_test.clj b/test/metabase/sync/analyze/special_types/values_test.clj
deleted file mode 100644
index 223a2a4da3e74635d22190cb05e8935568f07852..0000000000000000000000000000000000000000
--- a/test/metabase/sync/analyze/special_types/values_test.clj
+++ /dev/null
@@ -1,18 +0,0 @@
-(ns metabase.sync.analyze.special-types.values-test
-  (:require [metabase.models
-             [field :refer [Field]]
-             [table :refer [Table]]]
-            [metabase.query-processor-test :as qp-test]
-            [metabase.sync.analyze.fingerprint :as fingerprint]
-            [metabase.sync.analyze.fingerprint.sample :as sample]
-            [metabase.test.data :as data]
-            [metabase.test.data.datasets :as datasets]))
-
-;; field-avg-length
-;; This test won't work for Druid because it doesn't have a 'venues' Table. TODO - Add a test for Druid as well
-(datasets/expect-with-engines qp-test/non-timeseries-engines
-  16
-  (let [field        (Field (data/id :venues :name))
-        [[_ sample]] (sample/sample-fields (Table (data/id :venues)) [field])]
-    (Math/round (get-in (#'fingerprint/fingerprint field sample)
-                        [:type :type/Text :average-length]))))
diff --git a/test/metabase/sync/analyze_test.clj b/test/metabase/sync/analyze_test.clj
index 8564e98d6703d81d6e2d8a442b01b6754d8b71a5..47e9db899dc1c4a446fa8d6d2ddb7919c60a2f7e 100644
--- a/test/metabase/sync/analyze_test.clj
+++ b/test/metabase/sync/analyze_test.clj
@@ -40,10 +40,10 @@
     (set (for [field (db/select [Field :name :special_type :last_analyzed] :table_id (u/get-id table))]
            (into {} field)))))
 
-;; ...but they *SHOULD* get analyzed if they ARE newly created
+;; ...but they *SHOULD* get analyzed if they ARE newly created (expcept for PK which we skip)
 (expect
   #{{:name "LATITUDE",    :special_type :type/Latitude,  :last_analyzed true}
-    {:name "ID",          :special_type :type/PK,        :last_analyzed true}
+    {:name "ID",          :special_type :type/PK,        :last_analyzed false}
     {:name "PRICE",       :special_type :type/Category,  :last_analyzed true}
     {:name "LONGITUDE",   :special_type :type/Longitude, :last_analyzed true}
     {:name "CATEGORY_ID", :special_type :type/Category,  :last_analyzed true}
diff --git a/test/metabase/sync/field_values_test.clj b/test/metabase/sync/field_values_test.clj
index 6ae88c8b336ddf4d1577fe546af12d25782600a6..c683853d61d66cc39d2c5c5400b09bde3612390e 100644
--- a/test/metabase/sync/field_values_test.clj
+++ b/test/metabase/sync/field_values_test.clj
@@ -18,40 +18,43 @@
 (defn- venues-price-field-values []
   (db/select-one-field :values FieldValues, :field_id (data/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)]))
+
 (expect
   {1 [1 2 3 4]
    2 nil
-   3 {:errors 0, :created 1, :updated 5, :deleted 0}
+   3 (repeat 2 {:errors 0, :created 1, :updated 5, :deleted 0})
    4 [1 2 3 4]}
-  (array-map
-   ;; 1. Check that we have expected field values to start with
+  {;; 1. Check that we have expected field values to start with
    1 (venues-price-field-values)
    ;; 2. Delete the Field values, make sure they're gone
    2 (do (db/delete! FieldValues :field_id (data/id :venues :price))
          (venues-price-field-values))
    ;; 3. After the delete, a field values should be created, the rest updated
-   3 (sut/only-step-keys (sut/sync-database! "update-field-values" (Database (data/id))))
+   3 (sync-database!' "update-field-values" (Database (data/id)))
    ;; 4. Now re-sync the table and make sure they're back
    4 (do (sync/sync-table! (Table (data/id :venues)))
-         (venues-price-field-values))))
+         (venues-price-field-values))})
 
 ;; Test that syncing will cause FieldValues to be updated
 (expect
   {1 [1 2 3 4]
    2 [1 2 3]
-   3 {:errors 0, :created 0, :updated 6, :deleted 0}
+   3 (repeat 2 {:errors 0, :created 0, :updated 6, :deleted 0})
    4 [1 2 3 4]}
-  (array-map
-   ;; 1. Check that we have expected field values to start with
+  { ;; 1. Check that we have expected field values to start with
    1 (venues-price-field-values)
    ;; 2. Update the FieldValues, remove one of the values that should be there
    2 (do (db/update! FieldValues (db/select-one-id FieldValues :field_id (data/id :venues :price))
            :values [1 2 3])
          (venues-price-field-values))
    ;; 3. Now re-sync the table and validate the field values updated
-   3 (sut/only-step-keys (sut/sync-database! "update-field-values" (Database (data/id))))
+   3 (sync-database!' "update-field-values" (Database (data/id)))
    ;; 4. Make sure the value is back
-   4 (venues-price-field-values)))
+   4 (venues-price-field-values)})
 
 
 ;; A Field with 50 values should get marked as `auto-list` on initial sync, because it should be 'list', but was
diff --git a/test/metabase/sync/sync_metadata/fields_test.clj b/test/metabase/sync/sync_metadata/fields_test.clj
index b029c4c51cb6eea6957576f1a26cfd3177c686e0..32da736ddb0f8e647ba0334503f7ac092d540cf4 100644
--- a/test/metabase/sync/sync_metadata/fields_test.clj
+++ b/test/metabase/sync/sync_metadata/fields_test.clj
@@ -12,7 +12,9 @@
              [field :refer [Field]]
              [table :refer [Table]]]
             [metabase.sync.util-test :as sut]
-            [metabase.test.data :as data]
+            [metabase.test
+             [data :as data]
+             [util :as tu]]
             [metabase.test.data.one-off-dbs :as one-off-dbs]
             [toucan
              [db :as db]
@@ -151,26 +153,34 @@
   (db/select-one-field :fk_target_field_id Field, :id (data/id :venues :category_id)))
 
 ;; Check that sync-table! causes FKs to be set like we'd expect
-(expect [{:total-fks 3, :updated-fks 0, :total-failed 0}
-         {:special_type :type/FK, :fk_target_field_id true}
-         {:special_type nil,      :fk_target_field_id false}
-         {:total-fks 3, :updated-fks 1, :total-failed 0}
-         {:special_type :type/FK, :fk_target_field_id true}]
+(expect (concat
+         (repeat 2 {:total-fks 3, :updated-fks 0, :total-failed 0})
+         [{:special_type :type/FK, :fk_target_field_id true}
+          {:special_type nil,      :fk_target_field_id false}]
+         (repeat 2 {:total-fks 3, :updated-fks 1, :total-failed 0})
+         [{:special_type :type/FK, :fk_target_field_id true}])
   (let [field-id (data/id :checkins :user_id)
         get-special-type-and-fk-exists? (fn []
                                           (into {} (-> (db/select-one [Field :special_type :fk_target_field_id],
                                                          :id field-id)
-                                                       (update :fk_target_field_id #(db/exists? Field :id %)))))]
+                                                       (update :fk_target_field_id #(db/exists? Field :id %)))))
+        {before-step-info :step-info,
+         before-task-history :task-history} (sut/sync-database! "sync-fks" (Database (data/id)))
+        before-special-type-exists? (get-special-type-and-fk-exists?)
+        _ (db/update! Field field-id, :special_type nil, :fk_target_field_id nil)
+        after-special-type-exists? (get-special-type-and-fk-exists?)
+        {after-step-info :step-info,
+         after-task-history :task-history} (sut/sync-database! "sync-fks" (Database (data/id)))]
     [
-     (sut/only-step-keys (sut/sync-database! "sync-fks" (Database (data/id))))
+     (sut/only-step-keys before-step-info)
+     (:task_details before-task-history)
      ;; FK should exist to start with
-     (get-special-type-and-fk-exists?)
+     before-special-type-exists?
      ;; Clear out FK / special_type
-     (do (db/update! Field field-id, :special_type nil, :fk_target_field_id nil)
-         (get-special-type-and-fk-exists?))
-
+     after-special-type-exists?
      ;; Run sync-table and they should be set again
-     (sut/only-step-keys (sut/sync-database! "sync-fks" (Database (data/id))))
+     (sut/only-step-keys after-step-info)
+     (:task_details after-task-history)
      (get-special-type-and-fk-exists?)]))
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
@@ -181,11 +191,6 @@
   (doseq [statement statements]
     (jdbc/execute! one-off-dbs/*conn* [statement])))
 
-(defmacro ^:private throw-if-called {:style/indent 1} [fn-var & body]
-  `(with-redefs [~fn-var (fn [& args#]
-                           (throw (RuntimeException. "Should not be called!")))]
-     ~@body))
-
 ;; Validate the changing of a column's type triggers a hash miss and sync
 (expect
   [ ;; Original column type
@@ -212,7 +217,7 @@
             {new-db-type :database_type} (get-field)]
 
         ;; Syncing again with no change should not call sync-field-instances! or update the hash
-        (throw-if-called metabase.sync.sync-metadata.fields/sync-field-instances!
+        (tu/throw-if-called metabase.sync.sync-metadata.fields/sync-field-instances!
           (sync/sync-database! (data/db))
           [old-db-type
            new-db-type
diff --git a/test/metabase/sync/sync_metadata/sync_database_type_test.clj b/test/metabase/sync/sync_metadata/sync_database_type_test.clj
index 9000f386cb2e42add729885066f47c55a7ee8cff..0d8fa149f193ebf9d14eabb7cbb01e88f445f187 100644
--- a/test/metabase/sync/sync_metadata/sync_database_type_test.clj
+++ b/test/metabase/sync/sync_metadata/sync_database_type_test.clj
@@ -16,13 +16,14 @@
 
 ;; make sure that if a driver reports back a different database-type the Field gets updated accordingly
 (expect
-  [{:total-fields 16 :updated-fields 6}
-   #{{:name "NAME",        :database_type "VARCHAR"}
-     {:name "LATITUDE",    :database_type "DOUBLE"}
-     {:name "LONGITUDE",   :database_type "DOUBLE"}
-     {:name "ID",          :database_type "BIGINT"}
-     {:name "PRICE",       :database_type "INTEGER"}
-     {:name "CATEGORY_ID", :database_type "INTEGER"}}]
+  (concat
+   (repeat 2 {:total-fields 16 :updated-fields 6})
+   [#{{:name "NAME",        :database_type "VARCHAR"}
+      {:name "LATITUDE",    :database_type "DOUBLE"}
+      {:name "LONGITUDE",   :database_type "DOUBLE"}
+      {:name "ID",          :database_type "BIGINT"}
+      {:name "PRICE",       :database_type "INTEGER"}
+      {:name "CATEGORY_ID", :database_type "INTEGER"}}])
   ;; create a copy of the sample dataset :D
   (tt/with-temp Database [db (select-keys (data/db) [:details :engine])]
     (sync/sync-database! db)
@@ -32,33 +33,37 @@
       (db/update-where! Field {:table_id (u/get-id venues-table)}, :database_type "?")
       (db/update! Table (u/get-id venues-table) :fields_hash "something new")
       ;; now sync the DB again
-      (let [after-update-step-info (sut/sync-database! "sync-fields" db)]
-        [(sut/only-step-keys after-update-step-info)
+      (let [{:keys [step-info task-history]} (sut/sync-database! "sync-fields" db)]
+        [(sut/only-step-keys step-info)
+         (:task_details task-history)
          ;; The database_type of these Fields should get set to the correct types. Let's see...
          (set (map (partial into {})
                    (db/select [Field :name :database_type] :table_id (u/get-id venues-table))))]))))
 
 ;; make sure that if a driver reports back a different base-type the Field gets updated accordingly
 (expect
-  [{:updated-fields 16, :total-fields 16}
-   {:updated-fields 6, :total-fields 16}
-   #{{:name "NAME",        :base_type :type/Text}
-     {:name "LATITUDE",    :base_type :type/Float}
-     {:name "PRICE",       :base_type :type/Integer}
-     {:name "ID",          :base_type :type/BigInteger}
-     {:name "LONGITUDE",   :base_type :type/Float}
-     {:name "CATEGORY_ID", :base_type :type/Integer}}]
+  (concat
+   (repeat 2 {:updated-fields 16, :total-fields 16})
+   (repeat 2 {:updated-fields 6, :total-fields 16})
+   [#{{:name "NAME",        :base_type :type/Text}
+      {:name "LATITUDE",    :base_type :type/Float}
+      {:name "PRICE",       :base_type :type/Integer}
+      {:name "ID",          :base_type :type/BigInteger}
+      {:name "LONGITUDE",   :base_type :type/Float}
+      {:name "CATEGORY_ID", :base_type :type/Integer}}])
   ;; create a copy of the sample dataset :D
   (tt/with-temp Database [db (select-keys (data/db) [:details :engine])]
-    (let [new-db-step-info (sut/sync-database! "sync-fields" db)
+    (let [{new-step-info :step-info, new-task-history :task-history} (sut/sync-database! "sync-fields" db)
           venues-table     (Table :db_id (u/get-id db), :display_name "Venues")]
       (db/update! Table (u/get-id venues-table) :fields_hash "something new")
       ;; ok, now give all the Fields `:type/*` as their `base_type`
       (db/update-where! Field {:table_id (u/get-id venues-table)}, :base_type "type/*")
       ;; now sync the DB again
-      (let [after-update-step-info (sut/sync-database! "sync-fields" db)]
-        [(sut/only-step-keys new-db-step-info)
-         (sut/only-step-keys after-update-step-info)
+      (let [{after-step-info :step-info, after-task-history :task-history} (sut/sync-database! "sync-fields" db)]
+        [(sut/only-step-keys new-step-info)
+         (:task_details new-task-history)
+         (sut/only-step-keys after-step-info)
+         (:task_details after-task-history)
          ;; The database_type of these Fields should get set to the correct types. Let's see...
          (set (map (partial into {})
                    (db/select [Field :name :base_type] :table_id (u/get-id venues-table))))]))))
diff --git a/test/metabase/sync/sync_metadata/sync_timezone_test.clj b/test/metabase/sync/sync_metadata/sync_timezone_test.clj
index 8387d0af62d06c65c5466507bf8effb0eb1119bc..1e1eeef9b3de276743554e45f313c2cbcc9dda3d 100644
--- a/test/metabase/sync/sync_metadata/sync_timezone_test.clj
+++ b/test/metabase/sync/sync_metadata/sync_timezone_test.clj
@@ -17,18 +17,21 @@
 ;; sync happens automatically, so this test removes it first to ensure
 ;; that it gets set when missing
 (datasets/expect-with-engines #{:h2 :postgres}
-  [{:timezone-id "UTC"} true true true]
+  (concat
+   (repeat 2 {:timezone-id "UTC"})
+   [true true true])
   (data/dataset test-data
-    (let [db              (data/db)
-          tz-on-load      (db-timezone db)
-          _               (db/update! Database (:id db) :timezone nil)
-          tz-after-update (db-timezone db)]
+    (let [db                               (data/db)
+          tz-on-load                       (db-timezone db)
+          _                                (db/update! Database (:id db) :timezone nil)
+          tz-after-update                  (db-timezone db)
+          ;; It looks like we can get some stale timezone information depending on which thread is used for querying the
+          ;; database in sync. Clearing the connection pool to ensure we get the most updated TZ data
+          _                                (tu/clear-connection-pool db)
+          {:keys [step-info task-history]} (sut/sync-database! "sync-timezone" db)]
 
-      ;; It looks like we can get some stale timezone information depending on which thread is used for querying the
-      ;; database in sync. Clearing the connection pool to ensure we get the most updated TZ data
-      (tu/clear-connection-pool db)
-
-      [(sut/only-step-keys (sut/sync-database! "sync-timezone" db))
+      [(sut/only-step-keys step-info)
+       (:task_details task-history)
        ;; On startup is the timezone specified?
        (boolean (time/time-zone-for-id tz-on-load))
        ;; Check to make sure the test removed the timezone
diff --git a/test/metabase/sync/util_test.clj b/test/metabase/sync/util_test.clj
index 8f5f1c76f91eabb92a10f99ff455dc42f94f36bd..0f1f2f73090d1ece671b996c82f744fa243b54b4 100644
--- a/test/metabase/sync/util_test.clj
+++ b/test/metabase/sync/util_test.clj
@@ -1,14 +1,16 @@
 (ns metabase.sync.util-test
   "Tests for the utility functions shared by all parts of sync, such as the duplicate ops guard."
-  (:require [clj-time.coerce :as tcoerce]
-            [expectations :refer :all]
+  (:require [expectations :refer :all]
             [metabase
              [driver :as driver]
              [sync :as sync]]
-            [metabase.sync
-             [interface :as i]
-             [util :refer :all]]
-            [metabase.models.database :refer [Database] :as mdb]
+            [metabase.models
+             [database :as mdb :refer [Database]]
+             [task-history :refer [TaskHistory]]]
+            [metabase.sync.util :refer :all]
+            [metabase.test.util :as tu]
+            [metabase.util.date :as du]
+            [toucan.db :as db]
             [toucan.util.test :as tt]))
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
@@ -58,56 +60,80 @@
      ;; Check the number of syncs that took place. Should be 2 (just the first)
      @calls-to-describe-database)))
 
-(defn call-with-operation-info
-  "Call `f` with `log-sync-summary` redef'd to intercept the step metadata before the information is logged. This is
-  useful to validate that the metadata is correct as the message might not be logged at all (depending on the logging
-  level)."
+(defn- call-with-operation-info
+  "Call `f` with `log-sync-summary` and `store-sync-summary!` redef'd. For `log-sync-summary`, it intercepts the step
+  metadata before the information is logged. For `store-sync-summary!` it will return the IDs for the newly created
+  TaskHistory rows. This is useful to validate that the metadata and history is correct as the message might not be
+  logged at all (depending on the logging level) or not stored."
   [f]
   (let [step-info-atom (atom [])
-        orig-fn (var-get #'metabase.sync.util/log-sync-summary)]
+        created-task-history-ids (atom [])
+        orig-log-fn (var-get #'metabase.sync.util/log-sync-summary)
+        orig-store-fn (var-get #'metabase.sync.util/store-sync-summary!)]
     (with-redefs [metabase.sync.util/log-sync-summary (fn [operation database {:keys [steps] :as operation-metadata}]
                                                         (swap! step-info-atom conj operation-metadata)
-                                                        (orig-fn operation database operation-metadata))]
+                                                        (orig-log-fn operation database operation-metadata))
+                  metabase.sync.util/store-sync-summary! (fn [operation database operation-metadata]
+                                                           (let [result (orig-store-fn operation database operation-metadata)]
+                                                             (swap! created-task-history-ids concat result)
+                                                             result))]
       (f))
-    @step-info-atom))
+    {:operation-results @step-info-atom
+     :task-history-ids @created-task-history-ids}))
 
 (defn sync-database!
-  "Calls `sync-database!` and returns the the metadata for `step` as the result. This function is useful for
-  validating that each steps metadata correctly reflects the changes that were made via a test scenario."
+  "Calls `sync-database!` and returns the the metadata for `step` as the result along with the `TaskHistory` for that
+  `step`. This function is useful for validating that each step's metadata correctly reflects the changes that were
+  made via a test scenario."
   [step db]
-  (let [operation-results (call-with-operation-info #(sync/sync-database! db))]
-    (-> (into {} (mapcat :steps operation-results))
-        (get step))))
+  (let [{:keys [operation-results task-history-ids]} (call-with-operation-info #(sync/sync-database! db))]
+    {:step-info    (-> (into {} (mapcat :steps operation-results))
+                       (get step))
+     :task-history (when (seq task-history-ids)
+                     (db/select-one TaskHistory :id [:in task-history-ids]
+                                    :task [:= step]))}))
 
 (defn only-step-keys
   "This function removes the generic keys for the step metadata, returning only the step specific keypairs to make
   validating the results for the given step easier."
   [step-info]
-  (dissoc step-info :start-time :end-time :duration :log-summary-fn))
-
-(defn- date-string? [s]
-  (-> s tcoerce/from-string boolean))
+  (dissoc step-info :start-time :end-time :log-summary-fn))
 
 (defn- validate-times [m]
-  (and (-> m :start-time date-string?)
-       (-> m :end-time date-string?)
-       (-> m :duration string?)))
+  (and (-> m :start-time du/is-temporal?)
+       (-> m :end-time du/is-temporal?)))
 
-(expect
-  [
-   ;; There should only be 1 operation info returned
-   true
-   ;; Validate that start/end/duration of the entire sync operation is included
-   true
-   ;; Each step should have a valid start/end/duration value
-   [true true]
-   ;; Each step name is included with the results, the order is preseverd
-   ["step1" "step2"]]
-  (let [sync-steps [(create-sync-step "step1" (fn [_] (Thread/sleep 10)))
-                    (create-sync-step "step2" (fn [_] (Thread/sleep 10)))]
-        mock-db    (mdb/map->DatabaseInstance {:name "test", :id  1, :engine :h2})
-        [results & none]    (call-with-operation-info #(run-sync-operation "sync" mock-db sync-steps))]
-    [(empty? none)
-     (validate-times results)
-     (map (comp validate-times second) (:steps results))
-     (map first (:steps results))]))
+(def ^:private default-task-history
+  {:id true, :db_id true, :started_at true, :ended_at true})
+
+(defn- fetch-task-history-row [task-name]
+  (let [task-history (db/select-one TaskHistory :task task-name)]
+    (assert (integer? (:duration task-history)))
+    (tu/boolean-ids-and-timestamps (dissoc task-history :duration))))
+
+(let [process-name (tu/random-name)
+      step-1-name  (tu/random-name)
+      step-2-name  (tu/random-name)]
+  (expect
+    {:valid-operation-metadata? true
+     :valid-step-metadata?      [true true]
+     :step-names                [step-1-name step-2-name]
+     :operation-history         (merge default-task-history
+                                       {:task process-name, :task_details nil})
+     :step-1-history            (merge default-task-history
+                                       {:task step-1-name, :task_details {:foo "bar"}})
+     :step-2-history            (merge default-task-history
+                                       {:task step-2-name, :task_details nil})}
+    (let [sync-steps [(create-sync-step step-1-name (fn [_] (Thread/sleep 10) {:foo "bar"}))
+                      (create-sync-step step-2-name (fn [_] (Thread/sleep 10)))]
+          mock-db    (mdb/map->DatabaseInstance {:name "test", :id 1, :engine :h2})
+          [results]  (:operation-results
+                      (call-with-operation-info #(run-sync-operation process-name mock-db sync-steps)))]
+
+      {:valid-operation-metadata? (validate-times results)
+       :valid-step-metadata?      (map (comp validate-times second) (:steps results))
+       :step-names                (map first (:steps results))
+       ;; Check that the TaskHistory was stored for the operation and each of the steps
+       :operation-history         (fetch-task-history-row process-name)
+       :step-1-history            (fetch-task-history-row step-1-name)
+       :step-2-history            (fetch-task-history-row step-2-name)})))
diff --git a/test/metabase/sync_database/analyze_test.clj b/test/metabase/sync_database/analyze_test.clj
index 8d803dbc470ace336342b907650cbb35f5209b14..0a374bdafd4579b48dec44873cd1e11c86a5fb10 100644
--- a/test/metabase/sync_database/analyze_test.clj
+++ b/test/metabase/sync_database/analyze_test.clj
@@ -13,7 +13,7 @@
              [field-values :as field-values]
              [table :as table :refer [Table]]]
             [metabase.sync.analyze :as analyze]
-            [metabase.sync.analyze.fingerprint :as fingerprint]
+            [metabase.sync.analyze.fingerprint.fingerprinters :as fingerprinters]
             [metabase.sync.analyze.classifiers.text-fingerprint :as classify-text-fingerprint]
             [metabase.test
              [data :as data]
@@ -40,7 +40,7 @@
 
 (defn- values-are-valid-json? [values]
   (let [field (field/map->FieldInstance {:base_type :type/Text})]
-    (= (:special_type (classify-text-fingerprint/infer-special-type field (#'fingerprint/fingerprint field values)))
+    (= (:special_type (classify-text-fingerprint/infer-special-type field (transduce identity (fingerprinters/fingerprinter field) values)))
        :type/SerializedJSON)))
 
 ;; When all the values are valid JSON dicts they're valid JSON
@@ -71,7 +71,7 @@
 
 (defn- values-are-valid-emails? [values]
   (let [field (field/map->FieldInstance {:base_type :type/Text})]
-    (= (:special_type (classify-text-fingerprint/infer-special-type field (#'fingerprint/fingerprint field values)))
+    (= (:special_type (classify-text-fingerprint/infer-special-type field (transduce identity (fingerprinters/fingerprinter field) values)))
        :type/Email)))
 
 (expect true (values-are-valid-emails? ["helper@metabase.com"]))
@@ -79,7 +79,6 @@
 
 (expect false (values-are-valid-emails? ["helper@metabase.com", "1111IsNot!An....email", "help@nope.com"]))
 (expect false (values-are-valid-emails? ["\"A string should not cause a Field to be marked as email\""]))
-(expect false (values-are-valid-emails? [100]))
 (expect false (values-are-valid-emails? ["true"]))
 (expect false (values-are-valid-emails? ["false"]))
 
diff --git a/test/metabase/sync_database_test.clj b/test/metabase/sync_database_test.clj
index eed35aa8fa78c76283083a95d55b4b58d6939628..6f483b806883d59c3021e2de943d65f15367f9d8 100644
--- a/test/metabase/sync_database_test.clj
+++ b/test/metabase/sync_database_test.clj
@@ -30,7 +30,8 @@
              :schema "default"
              :fields #{{:name          "id"
                         :database-type "SERIAL"
-                        :base-type     :type/Integer}
+                        :base-type     :type/Integer
+                        :special-type  :type/PK}
                        {:name          "title"
                         :database-type "VARCHAR"
                         :base-type     :type/Text
@@ -104,7 +105,6 @@
    :entity_type             :entity/GenericTable
    :id                      true
    :points_of_interest      nil
-   :raw_table_id            false
    :rows                    nil
    :schema                  nil
    :show_in_getting_started false
@@ -127,7 +127,6 @@
    :points_of_interest  nil
    :position            0
    :preview_display     true
-   :raw_column_id       false
    :special_type        nil
    :table_id            true
    :updated_at          true
@@ -143,7 +142,8 @@
                                  {:name          "id"
                                   :display_name  "ID"
                                   :database_type "SERIAL"
-                                  :base_type     :type/Integer})
+                                  :base_type     :type/Integer
+                                  :special_type  :type/PK})
                           (merge field-defaults
                                  {:name               "studio"
                                   :display_name       "Studio"
@@ -190,7 +190,8 @@
                                 {:name          "id"
                                  :display_name  "ID"
                                  :database_type "SERIAL"
-                                 :base_type     :type/Integer})
+                                 :base_type     :type/Integer
+                                 :special_type  :type/PK})
                          (merge field-defaults
                                 {:name          "studio"
                                  :display_name  "Studio"
diff --git a/test/metabase/task/DynamicClassLoadHelper_test.clj b/test/metabase/task/DynamicClassLoadHelper_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..0c58aa0fef68421dbbeb1ceca805c87c21a96e59
--- /dev/null
+++ b/test/metabase/task/DynamicClassLoadHelper_test.clj
@@ -0,0 +1,7 @@
+(ns metabase.task.DynamicClassLoadHelper-test
+  (:require [expectations :refer :all]
+            [metabase.task.DynamicClassLoadHelper :as DynamicClassLoadHelper]))
+
+(expect
+  "metabase.task.upgrade-checks"
+  (#'DynamicClassLoadHelper/task-class-name->namespace-str "metabase.task.upgrade_checks.CheckForNewVersions"))
diff --git a/test/metabase/task/sync_databases_test.clj b/test/metabase/task/sync_databases_test.clj
index 0f032d795b7af3f62b5bcaecfc4009f2e6e999d9..4be392e908260537049dd1e442a744ac8dce8a65 100644
--- a/test/metabase/task/sync_databases_test.clj
+++ b/test/metabase/task/sync_databases_test.clj
@@ -136,9 +136,9 @@
       :cache_field_values_schedule "2 CANS PER DAY")))
 
 
-;;; +------------------------------------------------------------------------------------------------------------------------+
-;;; |                                        CHECKING THAT SYNC TASKS RUN CORRECT FNS                                        |
-;;; +------------------------------------------------------------------------------------------------------------------------+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                    CHECKING THAT SYNC TASKS RUN CORRECT FNS                                    |
+;;; +----------------------------------------------------------------------------------------------------------------+
 
 (defn- check-if-sync-processes-ran-for-db {:style/indent 0} [db-info]
   (let [sync-db-metadata-counter    (atom 0)
diff --git a/test/metabase/task/task_history_cleanup_test.clj b/test/metabase/task/task_history_cleanup_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..963fcc0612b0d96aaad3cbc05a60546f16e849f4
--- /dev/null
+++ b/test/metabase/task/task_history_cleanup_test.clj
@@ -0,0 +1,51 @@
+(ns metabase.task.task-history-cleanup-test
+  (:require [clj-time.core :as time]
+            [expectations :refer :all]
+            [metabase.models
+             [task-history :refer [TaskHistory]]
+             [task-history-test :as tht]]
+            [metabase.task.task-history-cleanup :as cleanup-task]
+            [metabase.test.util :as tu]
+            [metabase.util :as u]
+            [toucan.db :as db]
+            [toucan.util.test :as tt]))
+
+;; Basic run of the cleanup task when it needs to remove rows. Should also add a TaskHistory row once complete
+(let [task-2 (tu/random-name)
+      task-3 (tu/random-name)]
+  (expect
+    #{task-2 task-3 (var-get #'cleanup-task/job-name)}
+    (let [t1-start (time/now)
+          t2-start (tht/add-second t1-start)
+          t3-start (tht/add-second t2-start)]
+      (tt/with-temp* [TaskHistory [t1 (tht/make-10-millis-task t1-start)]
+                      TaskHistory [t2 (assoc (tht/make-10-millis-task t2-start)
+                                        :task task-2)]
+                      TaskHistory [t3 (assoc (tht/make-10-millis-task t3-start)
+                                        :task task-3)]]
+        (with-redefs [cleanup-task/history-rows-to-keep 2]
+          (db/delete! TaskHistory :id [:not-in (map u/get-id [t1 t2 t3])])
+          ;; Delete all but 2 task history rows
+          (#'cleanup-task/task-history-cleanup!)
+          (set (map :task (TaskHistory))))))))
+
+;; When the task runs and nothing is removed, it should still insert a new TaskHistory row
+(let [task-1 (tu/random-name)
+      task-2 (tu/random-name)
+      task-3 (tu/random-name)]
+  (expect
+    #{task-1 task-2 task-3 (var-get #'cleanup-task/job-name)}
+    (let [t1-start (time/now)
+          t2-start (tht/add-second t1-start)
+          t3-start (tht/add-second t2-start)]
+      (tt/with-temp* [TaskHistory [t1 (assoc (tht/make-10-millis-task t1-start)
+                                        :task task-1)]
+                      TaskHistory [t2 (assoc (tht/make-10-millis-task t2-start)
+                                        :task task-2)]
+                      TaskHistory [t3 (assoc (tht/make-10-millis-task t3-start)
+                                        :task task-3)]]
+        (with-redefs [cleanup-task/history-rows-to-keep 10]
+          (db/delete! TaskHistory :id [:not-in (map u/get-id [t1 t2 t3])])
+          ;; Delete all but 2 task history rows
+          (#'cleanup-task/task-history-cleanup!)
+          (set (map :task (TaskHistory))))))))
diff --git a/test/metabase/test/automagic_dashboards.clj b/test/metabase/test/automagic_dashboards.clj
new file mode 100644
index 0000000000000000000000000000000000000000..73a2f9ce3a33e79416409f1f17004629d64ed847
--- /dev/null
+++ b/test/metabase/test/automagic_dashboards.clj
@@ -0,0 +1,53 @@
+(ns metabase.test.automagic-dashboards
+  "Helper functions and macros for writing tests for automagic dashboards."
+  (:require [metabase.api.common :as api]
+            [metabase.models.user :as user]
+            [metabase.query-processor :as qp]
+            [metabase.test.data.users :as test-users]
+            [metabase.test.util :as tu]))
+
+(defmacro with-rasta
+  "Execute body with rasta as the current user."
+  [& body]
+  `(binding [api/*current-user-id*              (test-users/user->id :rasta)
+             api/*current-user-permissions-set* (-> :rasta
+                                                    test-users/user->id
+                                                    user/permissions-set
+                                                    atom)]
+     ~@body))
+
+(defmacro with-dashboard-cleanup
+  "Execute body and cleanup all dashboard elements created."
+  [& body]
+  `(tu/with-model-cleanup ['~'Card '~'Dashboard '~'Collection '~'DashboardCard]
+     ~@body))
+
+(defn- collect-urls
+  [dashboard]
+  (->> dashboard
+       (tree-seq (some-fn sequential? map?) identity)
+       (keep (fn [form]
+               (when (map? form)
+                 (:url form))))))
+
+(defn- valid-urls?
+  [dashboard]
+  (->> dashboard
+       collect-urls
+       (every? (fn [url]
+                 ((test-users/user->client :rasta) :get 200 (format "automagic-dashboards/%s"
+                                                                    (subs url 16)))))))
+
+(def ^:private valid-card?
+  (comp qp/expand :dataset_query))
+
+(defn valid-dashboard?
+  "Is generated dashboard valid?
+   Tests that the dashboard has cards, the queries for those cards are valid, all related URLs are
+   valid, and that it has correct metadata."
+  [dashboard]
+  (assert (:name dashboard))
+  (assert (-> dashboard :ordered_cards count pos?))
+  (assert (valid-urls? dashboard))
+  (assert (every? valid-card? (keep :card (:ordered_cards dashboard))))
+  true)
diff --git a/test/metabase/test/data.clj b/test/metabase/test/data.clj
index 274150ea3fae11a6779ff18c495a01910178e543..1067cf7b191a11e2db1eef95e80c8b8fa38440e1 100644
--- a/test/metabase/test/data.clj
+++ b/test/metabase/test/data.clj
@@ -10,21 +10,16 @@
              [query-processor :as qp]
              [sync :as sync]
              [util :as u]]
-            metabase.driver.h2
             [metabase.models
              [database :refer [Database]]
              [dimension :refer [Dimension]]
              [field :as field :refer [Field]]
              [field-values :refer [FieldValues]]
              [table :refer [Table]]]
-            [metabase.query-processor.interface :as qi]
-            [metabase.query-processor.middleware.expand :as ql]
             [metabase.test.data
              [dataset-definitions :as defs]
              [datasets :refer [*driver*]]
-             h2
              [interface :as i]]
-            [schema.core :as s]
             [toucan.db :as db])
   (:import [metabase.test.data.interface DatabaseDefinition TableDefinition]))
 
@@ -62,74 +57,104 @@
   [db & body]
   `(do-with-db ~db (fn [] ~@body)))
 
+;; $ids:
+;;
+;; The following macros make writing test queries a little easier. Wrap a body in `$ids` and you can avoid repeated
+;; calls to `data/id`:
+;;
+;;  ($ids venue [:= $id 200]) ; -> [:= (data/id :venue :id) 200]
+;;
+;; Tokens can be in the following formats:
+;;
+;; *  `$field`              -- assumes Field belongs to table specified in first arg
+;; *  `$table.field`        -- specify a different Table
+;; *  `$field->table.field` -- for FKs. Either Field can be qualified with `table`, or not
+
+(defn- ->id
+  "Internal impl of `$ids` and `mbql-query` macros. Low-level function to replace a token string like `field` or
+  `table.field` with a call to `id`."
+  [table-name token-str]
+  (let [parts (str/split token-str #"\.")]
+    (if (= (count parts) 1)
+      `(id ~(keyword table-name) ~(keyword (first parts)))
+      `(id ~@(map keyword parts)))))
+
+(defn- token->id-call
+  "Internal impl of `$ids` and `mbql-query` macros. Low-level function to replace a token string with calls to `id`,
+  handling `field->field` tokens as well, and wrapping in `:field-id` or `:fk->` clauses if appropriate."
+  [wrap-field-ids? table-name token-str]
+  (if-let [[_ token-1 token-2] (re-matches #"(^.*)->(.*$)" token-str)]
+    (if wrap-field-ids?
+      `[:fk-> [:field-id ~(->id table-name token-1)] [:field-id ~(->id table-name token-2)]]
+      (->id table-name token-2))
+    (if wrap-field-ids?
+      `[:field-id ~(->id table-name token-str)]
+      (->id table-name token-str))))
+
 (defn- $->id
+  "Internal impl fn of `$ids` and `mbql-query` macros. Walk `body` and replace `$field` (and related) tokens with calls
+  to `id`, optionally wrapping them in `:field-id` or `:fk->` clauses."
+  [table-name body & {:keys [wrap-field-ids?], :or {wrap-field-ids? true}}]
+  (walk/postwalk
+   (fn [form]
+     (or (when (symbol? form)
+           (let [[first-char & rest-chars] (name form)]
+             (when (= first-char \$)
+               (let [token (apply str rest-chars)]
+                 (token->id-call wrap-field-ids? table-name token)))))
+         form))
+   body))
+
+(defmacro $ids
   "Convert symbols like `$field` to `id` fn calls. Input is split into separate args by splitting the token on `.`.
-  With no `.` delimiters, it is assumed we're referring to a Field belonging to TABLE-NAME, which is passed
-  implicitly as the first arg. With one or more `.` delimiters, no implicit TABLE-NAME arg is passed to `id`:
-
-    $venue_id  -> (id :sightings :venue_id) ; TABLE-NAME is implicit first arg
-    $cities.id -> (id :cities :id)          ; specify non-default Table"
-  [table-name body]
-  (let [->id (fn [s]
-               (let [parts (str/split s #"\.")]
-                 (if (= (count parts) 1)
-                   `(id ~table-name ~(keyword (first parts)))
-                   `(id ~@(map keyword parts)))))]
-    (walk/postwalk (fn [form]
-                     (or (when (symbol? form)
-                           (let [[first-char & rest-chars] (name form)]
-                             (when (= first-char \$)
-                               (let [token (apply str rest-chars)]
-                                 (if-let [[_ token-1 token-2] (re-matches #"(^.*)->(.*$)" token)]
-                                   `(ql/fk-> ~(->id token-1) ~(->id token-2))
-                                   `(ql/field-id ~(->id token)))))))
-                         form))
-                   body)))
-
-(defmacro query
-  "Build a query, expands symbols like `$field` into calls to `id`.
-   Internally, this wraps `metabase.driver.query-processor.expand/query` and includes a call to `source-table`.
-   See the dox for `$->id` for more information on how `$`-prefixed expansion behaves.
-
-     (query venues
-       (ql/filter (ql/= $id 1)))
-
-      -> (ql/query
-           (ql/source-table (id :venues))
-           (ql/filter (ql/= (id :venues :id) 1)))"
+  With no `.` delimiters, it is assumed we're referring to a Field belonging to `table-name`, which is passed implicitly
+  as the first arg. With one or more `.` delimiters, no implicit `table-name` arg is passed to `id`:
+
+    $venue_id      -> (id :sightings :venue_id) ; TABLE-NAME is implicit first arg
+    $cities.id     -> (id :cities :id)          ; specify non-default Table"
   {:style/indent 1}
-  [table & forms]
-  `(ql/query (ql/source-table (id ~(keyword table)))
-             ~@(map (partial $->id (keyword table)) forms)))
+  [table-name body & {:keys [wrap-field-ids?], :or {wrap-field-ids? false}}]
+  ($->id (keyword table-name) body, :wrap-field-ids? wrap-field-ids?))
+
 
-(s/defn wrap-inner-query
+(defn wrap-inner-mbql-query
   "Wrap inner QUERY with `:database` ID and other 'outer query' kvs. DB ID is fetched by looking up the Database for
   the query's `:source-table`."
   {:style/indent 0}
-  [query :- qi/Query]
+  [query]
   {:database (db/select-one-field :db_id Table, :id (:source-table query))
    :type     :query
    :query    query})
 
-(s/defn run-query*
-  "Call `driver/process-query` on expanded inner QUERY, looking up the `Database` ID for the `source-table.`
+(defmacro mbql-query
+  "Build a query, expands symbols like `$field` into calls to `id`. See the dox for `$->id` for more information on how
+  `$`-prefixed expansion behaves.
 
-     (run-query* (query (source-table 5) ...))"
-  {:style/indent 0}
-  [query :- qi/Query]
-  (qp/process-query (wrap-inner-query query)))
+    (mbql-query venues
+      {:filter [:= $id 1]})
+
+    ;; -> {:database <database>
+           :type     :query
+           :query    {:source-table (data/id :venues)
+                      :filter       [:= [:field-id (data/id :venues :id)] 1]}} "
+  {:style/indent 1}
+  [table & [query]]
+  `(wrap-inner-mbql-query
+     ~(merge `{:source-table (id ~(keyword table))}
+             ($->id (keyword table) query))))
 
-(defmacro run-query
-  "Like `query`, but runs the query as well."
+(defmacro run-mbql-query
+  "Like `mbql-query`, but runs the query as well."
   {:style/indent 1}
-  [table & forms]
-  `(run-query* (query ~table ~@forms)))
+  [table & [query]]
+  `(qp/process-query
+     (mbql-query ~table ~query)))
 
 
 (defn format-name
   "Format a SQL schema, table, or field identifier in the correct way for the current database by calling the driver's
-   implementation of `format-name`. (Most databases use the default implementation of `identity`; H2 uses
-   `clojure.string/upper-case`.) This function DOES NOT quote the identifier."
+  implementation of `format-name`. (Most databases use the default implementation of `identity`; H2 uses
+  `clojure.string/upper-case`.) This function DOES NOT quote the identifier."
   [nm]
   (i/format-name *driver* (name nm)))
 
@@ -280,7 +305,7 @@
      (with-temp-db [db tupac-sightings]
        (driver/process-quiery {:database (:id db)
                                :type     :query
-                               :query    {:source_table (:id &events)
+                               :query    {:source-table (:id &events)
                                           :aggregation  [\"count\"]
                                           :filter       [\"<\" (:id &events.timestamp) \"1765-01-01\"]}}))
 
diff --git a/test/metabase/test/mock/util.clj b/test/metabase/test/mock/util.clj
index a3a5b77455e4e2403b2b9684cedfbad71cbb6b19..fe592beaf3496d8edc4b7f4fbb3192f172114c6a 100644
--- a/test/metabase/test/mock/util.clj
+++ b/test/metabase/test/mock/util.clj
@@ -8,7 +8,6 @@
    :points_of_interest      nil
    :show_in_getting_started false
    :schema                  nil
-   :raw_table_id            false
    :fields                  []
    :rows                    nil
    :updated_at              true
@@ -30,7 +29,6 @@
    :parent_id          false
    :special_type       nil
    :id                 true
-   :raw_column_id      false
    :last_analyzed      true
    :position           0
    :visibility_type    :normal
@@ -43,6 +41,20 @@
    :schedule_day   nil
    :enabled        true})
 
+(def venue-fingerprints
+  "Fingerprints for the full venues table"
+  {:name        {:global {:distinct-count 100},
+                 :type   {:type/Text {:percent-json  0.0, :percent-url    0.0,
+                                      :percent-email 0.0, :average-length 15.63}}}
+   :id          nil
+   :price       {:global {:distinct-count 4},
+                 :type   {:type/Number {:min 1.0, :max 4.0, :avg 2.03}}}
+   :latitude    {:global {:distinct-count 94},
+                 :type   {:type/Number {:min 10.06, :max 40.78, :avg 35.51}}}
+   :category_id {:global {:distinct-count 28}}
+   :longitude   {:global {:distinct-count 84},
+                 :type   {:type/Number {:min -165.37, :max -73.95, :avg -116.0}}}})
+
 ;; This is just a fake implementation that just swoops in and returns somewhat-correct looking results for different
 ;; queries we know will get ran as part of sync
 (defn- is-table-row-count-query? [expanded-query]
diff --git a/test/metabase/test/util.clj b/test/metabase/test/util.clj
index a45f36610b491c8aa85a6f33379ace43b85582c0..864f40c08e99bd17a134d5671d4626b7aff86a63 100644
--- a/test/metabase/test/util.clj
+++ b/test/metabase/test/util.clj
@@ -1,6 +1,10 @@
 (ns metabase.test.util
   "Helper functions and macros for writing unit tests."
   (:require [cheshire.core :as json]
+            [clj-time
+             [coerce :as tcoerce]
+             [core :as time]]
+            [clojure.string :as s]
             [clj-time.core :as time]
             [clojure.tools.logging :as log]
             [clojure.walk :as walk]
@@ -13,14 +17,15 @@
             [metabase.driver.generic-sql :as sql]
             [metabase.models
              [card :refer [Card]]
-             [collection :refer [Collection]]
+             [collection :as collection :refer [Collection]]
              [dashboard :refer [Dashboard]]
              [dashboard-card-series :refer [DashboardCardSeries]]
              [database :refer [Database]]
              [dimension :refer [Dimension]]
              [field :refer [Field]]
              [metric :refer [Metric]]
-             [permissions-group :refer [PermissionsGroup]]
+             [permissions :as perms :refer [Permissions]]
+             [permissions-group :as group :refer [PermissionsGroup]]
              [pulse :refer [Pulse]]
              [pulse-card :refer [PulseCard]]
              [pulse-channel :refer [PulseChannel]]
@@ -28,18 +33,23 @@
              [segment :refer [Segment]]
              [setting :as setting]
              [table :refer [Table]]
+             [task-history :refer [TaskHistory]]
              [user :refer [User]]]
-            [metabase.query-processor.middleware.expand :as ql]
+            [metabase.query-processor.util :as qputil]
             [metabase.test.data :as data]
             [metabase.test.data
-             [datasets :refer [*driver*]]
-             [dataset-definitions :as defs]]
+             [dataset-definitions :as defs]
+             [datasets :refer [*driver*]]]
+            [metabase.util.date :as du]
             [toucan.db :as db]
             [toucan.util.test :as test])
-  (:import java.util.TimeZone
+  (:import com.mchange.v2.c3p0.PooledDataSource
+           java.util.TimeZone
+           org.apache.log4j.Logger
            org.joda.time.DateTimeZone
            [org.quartz CronTrigger JobDetail JobKey Scheduler Trigger]))
 
+
 ;;; ---------------------------------------------------- match-$ -----------------------------------------------------
 
 (defn- $->prop
@@ -107,8 +117,9 @@
    (boolean-ids-and-timestamps
     (every-pred (some-fn keyword? string?)
                 (some-fn #{:id :created_at :updated_at :last_analyzed :created-at :updated-at :field-value-id :field-id
-                           :fields_hash :date_joined :date-joined :last_login}
-                         #(.endsWith (name %) "_id")))
+                           :fields_hash :date_joined :date-joined :last_login :dimension-id :human-readable-field-id}
+                         #(s/ends-with? % "_id")
+                         #(s/ends-with? % "_at")))
     data))
   ([pred data]
    (walk/prewalk (fn [maybe-map]
@@ -222,6 +233,17 @@
                                 :active true
                                 :name   (random-name)})})
 
+(u/strict-extend (class TaskHistory)
+  test/WithTempDefaults
+  {:with-temp-defaults (fn [_]
+                         (let [started (time/now)
+                               ended   (time/plus started (time/millis 10))]
+                           {:db_id      (data/id)
+                            :task       (random-name)
+                            :started_at (du/->Timestamp started)
+                            :ended_at   (du/->Timestamp ended)
+                            :duration   (du/calculate-duration started ended)}))})
+
 (u/strict-extend (class User)
   test/WithTempDefaults
   {:with-temp-defaults (fn [_] {:first_name (random-name)
@@ -337,6 +359,36 @@
   [& body]
   `(do-with-log-messages (fn [] ~@body)))
 
+(def level-kwd->level
+  "Conversion from a keyword log level to the Log4J constance mapped to that log level.
+   Not intended for use outside of the `with-mb-log-messages-at-level` macro."
+  {:error org.apache.log4j.Level/ERROR
+   :warn  org.apache.log4j.Level/WARN
+   :info  org.apache.log4j.Level/INFO
+   :debug org.apache.log4j.Level/DEBUG
+   :trace org.apache.log4j.Level/TRACE})
+
+(defn ^Logger metabase-logger
+  "Gets the root logger for all metabase namespaces. Not intended for use outside of the
+  `with-mb-log-messages-at-level` macro."
+  []
+  (Logger/getLogger "metabase"))
+
+(defmacro with-mb-log-messages-at-level
+  "Executes `body` with the metabase logging level set to `level-kwd`. This is needed when the logging level is set at
+  a higher threshold than the log messages you're wanting to example. As an example if the metabase logging level is
+  set to `ERROR` in the log4j.properties file and you are looking for a `WARN` message, it won't show up in the
+  `with-log-messages` call as there's a guard around the log invocation, if it's not enabled (it is set to `ERROR`)
+  the log function will never be invoked. This macro will temporarily set the logging level to `level-kwd`, then
+  invoke `with-log-messages`, then set the level back to what it was before the invocation. This allows testing log
+  messages even if the threshold is higher than the message you are looking for."
+  [level-kwd & body]
+  `(let  [orig-log-level# (.getLevel (metabase-logger))]
+     (try
+       (.setLevel (metabase-logger) (get level-kwd->level ~level-kwd))
+       (with-log-messages ~@body)
+       (finally
+         (.setLevel (metabase-logger) orig-log-level#)))))
 
 (defn vectorize-byte-arrays
   "Walk form X and convert any byte arrays in the results to standard Clojure vectors. This is useful when writing
@@ -379,6 +431,13 @@
                           [:cols])]
     (update-in query-results maybe-data-cols #(map round-fingerprint %))))
 
+(defn round-all-decimals
+  "Uses `walk/postwalk` to crawl `data`, looking for any double values, will round any it finds"
+  [decimal-place data]
+  (qputil/postwalk-pred double?
+                        #(u/round-to-decimals decimal-place %)
+                        data))
+
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                                   SCHEDULER                                                    |
@@ -443,7 +502,7 @@
   timezone. That causes problems for tests that we can determine the database's timezone. This function will reset the
   connections in the connection pool for `db` to ensure that we get fresh session with no timezone specified"
   [db]
-  (when-let [conn-pool (:datasource (sql/db->pooled-connection-spec db))]
+  (when-let [^PooledDataSource conn-pool (:datasource (sql/db->pooled-connection-spec db))]
     (.softResetAllUsers conn-pool)))
 
 (defn db-timezone-id
@@ -477,7 +536,8 @@
       (DateTimeZone/setDefault dtz)
       ;; We read the system property directly when formatting results, so this needs to be changed
       (System/setProperty "user.timezone" (.getID dtz))
-      (f)
+      (with-redefs [du/jvm-timezone (delay (.toTimeZone dtz))]
+        (f))
       (finally
         ;; We need to ensure we always put the timezones back the way
         ;; we found them as it will cause test failures
@@ -535,8 +595,8 @@
           pause-query                (promise)
           before-query-called-cancel (realized? called-cancel?)
           before-query-called-query  (realized? called-query?)
-          query-thunk                (fn [] (data/run-query checkins
-                                              (ql/aggregation (ql/count))))
+          query-thunk                (fn [] (data/run-mbql-query checkins
+                                              {:aggregation [[:count]]}))
           ;; When the query is ran via the datasets endpoint, it will run in a future. That future can be cancelled,
           ;; which should cause an interrupt
           query-future               (f query-thunk called-query? called-cancel? pause-query)]
@@ -558,3 +618,31 @@
          ;; This releases the fake query function so it finishes
          (deliver pause-query true)
          true)])))
+
+(defmacro throw-if-called
+  "Redefines `fn-var` with a function that throws an exception if it's called"
+  {:style/indent 1}
+  [fn-var & body]
+  `(with-redefs [~fn-var (fn [& args#]
+                           (throw (RuntimeException. "Should not be called!")))]
+     ~@body))
+
+
+(defn do-with-non-admin-groups-no-root-collection-perms [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)
+    (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))))))
+
+(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."
+  [& body]
+  `(do-with-non-admin-groups-no-root-collection-perms (fn [] ~@body)))
diff --git a/test/metabase/test_setup.clj b/test/metabase/test_setup.clj
index 67243da80c383c8e379a0c4bdadcf7ca757a88d5..5e38f6c524640a95c4a3dee133508904849913ef 100644
--- a/test/metabase/test_setup.clj
+++ b/test/metabase/test_setup.clj
@@ -10,6 +10,7 @@
              [db :as mdb]
              [driver :as driver]
              [plugins :as plugins]
+             [task :as task]
              [util :as u]]
             [metabase.core.initialization-status :as init-status]
             [metabase.models.setting :as setting]))
@@ -75,6 +76,10 @@
       (plugins/setup-plugins!)
       (log/info (format "Setting up %s test DB and running migrations..." (name (mdb/db-type))))
       (mdb/setup-db! :auto-migrate true)
+      ;; we don't want to actually start the task scheduler (we don't want sync or other stuff happening in the BG
+      ;; while running tests), but we still need to make sure it sets itself up properly so tasks can get scheduled
+      ;; without throwing Exceptions
+      (#'task/set-jdbc-backend-properties!)
       (setting/set! :site-name "Metabase Test")
       (init-status/set-complete!)
 
diff --git a/test/metabase/timeseries_query_processor_test.clj b/test/metabase/timeseries_query_processor_test.clj
index d6169e1d486c150f737f79442864cee571875971..f285220be8ff2e36bc7e74ec5299577666225f0d 100644
--- a/test/metabase/timeseries_query_processor_test.clj
+++ b/test/metabase/timeseries_query_processor_test.clj
@@ -4,11 +4,10 @@
   (:require [metabase
              [query-processor-test :refer [first-row format-rows-by rows]]
              [util :as u]]
-            [metabase.query-processor.middleware.expand :as ql]
             [metabase.test.data :as data]
             [metabase.timeseries-query-processor-test.util :refer :all]))
 
-(defn- data [results]
+(defn data [results]
   (when-let [data (or (:data results)
                       (println (u/pprint-to-str results)))] ; DEBUG
     (-> data
@@ -31,8 +30,8 @@
              "venue_price"]
    :rows [["931", "2013-01-03T08:00:00.000Z", 1, "2014-01-01T08:30:00.000Z", "Simcha Yan", "Thai", "34.094",  "-118.344", "Kinaree Thai Bistro",       "1"]
           ["285", "2013-01-10T08:00:00.000Z", 1, "2014-07-03T01:30:00.000Z", "Kfir Caj",   "Thai", "34.1021", "-118.306", "Ruen Pair Thai Restaurant", "2"]]}
-  (data (data/run-query checkins
-          (ql/limit 2))))
+  (data (data/run-mbql-query checkins
+          {:limit 2})))
 
 ;;; "bare rows" query, limit, order-by timestamp desc
 (expect-with-timeseries-dbs
@@ -46,11 +45,11 @@
              "venue_longitude"
              "venue_name"
              "venue_price"]
-   :rows [["693", "2015-12-29T08:00:00.000Z", 1, "2014-07-03T19:30:00.000Z", "Frans Hevel", "Mexican", "34.0489", "-118.238", "Señor Fish",       "2"]
-          ["570", "2015-12-26T08:00:00.000Z", 1, "2014-07-03T01:30:00.000Z", "Kfir Caj",    "Chinese", "37.7949", "-122.406", "Empress of China", "3"]]}
-  (data (data/run-query checkins
-          (ql/order-by (ql/desc $timestamp))
-          (ql/limit 2))))
+   :rows    [["693", "2015-12-29T08:00:00.000Z", 1, "2014-07-03T19:30:00.000Z", "Frans Hevel", "Mexican", "34.0489", "-118.238", "Señor Fish",       "2"]
+             ["570", "2015-12-26T08:00:00.000Z", 1, "2014-07-03T01:30:00.000Z", "Kfir Caj",    "Chinese", "37.7949", "-122.406", "Empress of China", "3"]]}
+  (data (data/run-mbql-query checkins
+          {:order-by [[:desc $timestamp]]
+           :limit    2})))
 
 ;;; "bare rows" query, limit, order-by timestamp asc
 (expect-with-timeseries-dbs
@@ -64,11 +63,11 @@
              "venue_longitude"
              "venue_name"
              "venue_price"]
-   :rows [["931", "2013-01-03T08:00:00.000Z", 1, "2014-01-01T08:30:00.000Z", "Simcha Yan", "Thai", "34.094",  "-118.344", "Kinaree Thai Bistro",       "1"]
-          ["285", "2013-01-10T08:00:00.000Z", 1, "2014-07-03T01:30:00.000Z", "Kfir Caj",   "Thai", "34.1021", "-118.306", "Ruen Pair Thai Restaurant", "2"]]}
-  (data (data/run-query checkins
-          (ql/order-by (ql/asc $timestamp))
-          (ql/limit 2))))
+   :rows    [["931", "2013-01-03T08:00:00.000Z", 1, "2014-01-01T08:30:00.000Z", "Simcha Yan", "Thai", "34.094",  "-118.344", "Kinaree Thai Bistro",       "1"]
+             ["285", "2013-01-10T08:00:00.000Z", 1, "2014-07-03T01:30:00.000Z", "Kfir Caj",   "Thai", "34.1021", "-118.306", "Ruen Pair Thai Restaurant", "2"]]}
+  (data (data/run-mbql-query checkins
+          {:order-by [[:asc $timestamp]]
+           :limit    2})))
 
 
 
@@ -77,72 +76,74 @@
   {:columns ["venue_name" "venue_category_name" "timestamp"],
    :rows    [["Kinaree Thai Bistro"       "Thai" "2013-01-03T08:00:00.000Z"]
              ["Ruen Pair Thai Restaurant" "Thai" "2013-01-10T08:00:00.000Z"]]}
-  (data (data/run-query checkins
-          (ql/fields $venue_name $venue_category_name)
-          (ql/limit 2))))
+  (data (data/run-mbql-query checkins
+          {:fields [$venue_name $venue_category_name]
+           :limit  2})))
 
 ;;; fields clause, order by timestamp asc
 (expect-with-timeseries-dbs
   {:columns ["venue_name" "venue_category_name" "timestamp"],
    :rows    [["Kinaree Thai Bistro"       "Thai" "2013-01-03T08:00:00.000Z"]
              ["Ruen Pair Thai Restaurant" "Thai" "2013-01-10T08:00:00.000Z"]]}
-  (data (data/run-query checkins
-          (ql/fields $venue_name $venue_category_name)
-          (ql/order-by (ql/asc $timestamp))
-          (ql/limit 2))))
+  (data (data/run-mbql-query checkins
+          {:fields   [$venue_name $venue_category_name]
+           :order-by [[:asc $timestamp]]
+           :limit    2})))
 
 ;;; fields clause, order by timestamp desc
 (expect-with-timeseries-dbs
   {:columns ["venue_name" "venue_category_name" "timestamp"],
    :rows    [["Señor Fish"       "Mexican" "2015-12-29T08:00:00.000Z"]
              ["Empress of China" "Chinese" "2015-12-26T08:00:00.000Z"]]}
-  (data (data/run-query checkins
-          (ql/fields $venue_name $venue_category_name)
-          (ql/order-by (ql/desc $timestamp))
-          (ql/limit 2))))
+  (data (data/run-mbql-query checkins
+          {:fields   [$venue_name $venue_category_name]
+           :order-by [[:desc $timestamp]]
+           :limit    2})))
 
 
 
 ;;; count
 (expect-with-timeseries-dbs
   [1000]
-  (first-row (data/run-query checkins
-               (ql/aggregation (ql/count)))))
+  (first-row
+    (data/run-mbql-query checkins
+      {:aggregation [[:count]]})))
 
 ;;; count(field)
 (expect-with-timeseries-dbs
   [1000]
-  (first-row (data/run-query checkins
-               (ql/aggregation (ql/count $user_name)))))
+  (first-row
+    (data/run-mbql-query checkins
+      {:aggregation [[:count $user_name]]})))
 
 ;;; avg
 (expect-with-timeseries-dbs
   {:columns ["avg"]
    :rows    [[1.992]]}
-  (data (data/run-query checkins
-          (ql/aggregation (ql/avg $venue_price)))))
+  (data (data/run-mbql-query checkins
+          {:aggregation [[:avg $venue_price]]})))
 
 ;;; sum
 (expect-with-timeseries-dbs
   {:columns ["sum"]
    :rows    [[1992.0]]}
-  (data (data/run-query checkins
-          (ql/aggregation (ql/sum $venue_price)))))
+  (data (data/run-mbql-query checkins
+          {:aggregation [[:sum $venue_price]]})))
 
 ;;; avg
 (expect-with-timeseries-dbs
   {:columns ["avg"]
    :rows    [[1.992]]}
-  (->> (data/run-query checkins
-         (ql/aggregation (ql/avg $venue_price)))
+  (->> (data/run-mbql-query checkins
+         {:aggregation [[:avg $venue_price]]})
        (format-rows-by [(partial u/round-to-decimals 3)])
        data))
 
 ;;; distinct count
 (expect-with-timeseries-dbs
   [[4]]
-  (->> (data/run-query checkins
-         (ql/aggregation (ql/distinct $venue_price)))
+  (->> (data/run-mbql-query checkins
+         {:aggregation [[:distinct $venue_price]]})
        rows (format-rows-by [int])))
 
 ;;; 1 breakout (distinct values)
@@ -163,8 +164,8 @@
              ["Simcha Yan"]
              ["Spiros Teofil"]
              ["Szymon Theutrich"]]}
-  (data (data/run-query checkins
-          (ql/breakout $user_name))))
+  (data (data/run-mbql-query checkins
+          {:breakout [$user_name]})))
 
 ;;; 2 breakouts
 (expect-with-timeseries-dbs
@@ -179,9 +180,9 @@
              ["Broen Olujimi" "Caribbean"]
              ["Broen Olujimi" "Deli"]
              ["Broen Olujimi" "Dim Sum"]]}
-  (data (data/run-query checkins
-          (ql/breakout $user_name $venue_category_name)
-          (ql/limit 10))))
+  (data (data/run-mbql-query checkins
+          {:breakout [$user_name $venue_category_name]
+           :limit    10})))
 
 ;;; 1 breakout w/ explicit order by
 (expect-with-timeseries-dbs
@@ -196,10 +197,10 @@
              ["Nils Gotam"]
              ["Kfir Caj"]
              ["Kaneonuskatew Eiran"]]}
-  (data (data/run-query checkins
-          (ql/breakout $user_name)
-          (ql/order-by (ql/desc $user_name))
-          (ql/limit 10))))
+  (data (data/run-mbql-query checkins
+          {:breakout [$user_name]
+           :order-by [[:desc $user_name]]
+           :limit    10})))
 
 ;;; 2 breakouts w/ explicit order by
 (expect-with-timeseries-dbs
@@ -214,10 +215,10 @@
              ["Nils Gotam"          "American"]
              ["Plato Yeshua"        "American"]
              ["Quentin Sören"       "American"]]}
-  (data (data/run-query checkins
-          (ql/breakout $user_name $venue_category_name)
-          (ql/order-by (ql/asc $venue_category_name))
-          (ql/limit 10))))
+  (data (data/run-mbql-query checkins
+          {:breakout [$user_name $venue_category_name]
+           :order-by [[:asc $venue_category_name]]
+           :limit    10})))
 
 ;;; count w/ 1 breakout
 (expect-with-timeseries-dbs
@@ -237,9 +238,9 @@
              ["Simcha Yan"          77]
              ["Spiros Teofil"       74]
              ["Szymon Theutrich"    81]]}
-  (data (data/run-query checkins
-          (ql/aggregation (ql/count))
-          (ql/breakout $user_name))))
+  (data (data/run-mbql-query checkins
+          {:aggregation [[:count]]
+           :breakout    [$user_name]})))
 
 ;;; count w/ 2 breakouts
 (expect-with-timeseries-dbs
@@ -254,38 +255,42 @@
              ["Broen Olujimi" "Caribbean" 1]
              ["Broen Olujimi" "Deli"      2]
              ["Broen Olujimi" "Dim Sum"   2]]}
-  (data (data/run-query checkins
-          (ql/aggregation (ql/count))
-          (ql/breakout $user_name $venue_category_name)
-          (ql/limit 10))))
+  (data (data/run-mbql-query checkins
+          {:aggregation [[:count]]
+           :breakout    [$user_name $venue_category_name]
+           :limit       10})))
 
 ;;; filter >
 (expect-with-timeseries-dbs
   [49]
-  (first-row (data/run-query checkins
-               (ql/aggregation (ql/count))
-               (ql/filter (ql/> $venue_price 3)))))
+  (first-row
+    (data/run-mbql-query checkins
+      {:aggregation [[:count]]
+       :filter      [:> $venue_price 3]})))
 
 ;;; filter <
 (expect-with-timeseries-dbs
   [836]
-  (first-row (data/run-query checkins
-               (ql/aggregation (ql/count))
-               (ql/filter (ql/< $venue_price 3)))))
+  (first-row
+    (data/run-mbql-query checkins
+      {:aggregation [[:count]]
+       :filter      [:< $venue_price 3]})))
 
 ;;; filter >=
 (expect-with-timeseries-dbs
   [164]
-  (first-row (data/run-query checkins
-               (ql/aggregation (ql/count))
-               (ql/filter (ql/>= $venue_price 3)))))
+  (first-row
+    (data/run-mbql-query checkins
+      {:aggregation [[:count]]
+       :filter      [:>= $venue_price 3]})))
 
 ;;; filter <=
 (expect-with-timeseries-dbs
   [951]
-  (first-row (data/run-query checkins
-               (ql/aggregation (ql/count))
-               (ql/filter (ql/<= $venue_price 3)))))
+  (first-row
+    (data/run-mbql-query checkins
+      {:aggregation [[:count]]
+       :filter      [:<= $venue_price 3]})))
 
 ;;; filter =
 (expect-with-timeseries-dbs
@@ -295,85 +300,92 @@
              ["Plato Yeshua" "Baby Blues BBQ" "BBQ"      "2013-06-03T07:00:00.000Z"]
              ["Plato Yeshua" "The Daily Pint" "Bar"      "2013-07-25T07:00:00.000Z"]
              ["Plato Yeshua" "Marlowe"        "American" "2013-09-10T07:00:00.000Z"]]}
-  (data (data/run-query checkins
-          (ql/fields $user_name $venue_name $venue_category_name)
-          (ql/filter (ql/= $user_name "Plato Yeshua"))
-          (ql/limit 5))))
+  (data (data/run-mbql-query checkins
+          {:fields [$user_name $venue_name $venue_category_name]
+           :filter [:= $user_name "Plato Yeshua"]
+           :limit  5})))
 
 ;;; filter !=
 (expect-with-timeseries-dbs
   [969]
-  (first-row (data/run-query checkins
-               (ql/aggregation (ql/count))
-               (ql/filter (ql/!= $user_name "Plato Yeshua")))))
+  (first-row
+    (data/run-mbql-query checkins
+      {:aggregation [[:count]]
+       :filter      [:!= $user_name "Plato Yeshua"]})))
 
 ;;; filter AND
 (expect-with-timeseries-dbs
   {:columns ["user_name" "venue_name" "timestamp"]
    :rows    [["Plato Yeshua" "The Daily Pint" "2013-07-25T07:00:00.000Z"]]}
-  (data (data/run-query checkins
-          (ql/fields $user_name $venue_name)
-          (ql/filter (ql/and (ql/= $venue_category_name "Bar")
-                             (ql/= $user_name "Plato Yeshua"))))))
+  (data (data/run-mbql-query checkins
+          {:fields [$user_name $venue_name]
+           :filter [:and
+                    [:= $venue_category_name "Bar"]
+                    [:= $user_name "Plato Yeshua"]]})))
 
 ;;; filter OR
 (expect-with-timeseries-dbs
   [199]
-  (first-row (data/run-query checkins
-               (ql/aggregation (ql/count))
-               (ql/filter (ql/or (ql/= $venue_category_name "Bar")
-                                 (ql/= $venue_category_name "American"))))))
+  (first-row
+    (data/run-mbql-query checkins
+      {:aggregation [[:count]]
+       :filter      [:or
+                     [:= $venue_category_name "Bar"]
+                     [:= $venue_category_name "American"]]})))
 
 ;;; filter BETWEEN (inclusive)
 (expect-with-timeseries-dbs
   [951]
-  (first-row (data/run-query checkins
-               (ql/aggregation (ql/count))
-               (ql/filter (ql/between $venue_price 1 3)))))
+  (first-row
+    (data/run-mbql-query checkins
+      {:aggregation [[:count]]
+       :filter      [:between $venue_price 1 3]})))
 
 ;;; filter INSIDE
 (expect-with-timeseries-dbs
   {:columns ["venue_name"]
    :rows    [["Red Medicine"]]}
-  (data (data/run-query checkins
-          (ql/breakout $venue_name)
-          (ql/filter (ql/inside $venue_latitude $venue_longitude 10.0649 -165.379 10.0641 -165.371)))))
+  (data (data/run-mbql-query checkins
+          {:breakout [$venue_name]
+           :filter   [:inside $venue_latitude $venue_longitude 10.0649 -165.379 10.0641 -165.371]})))
 
 ;;; filter IS_NULL
 (expect-with-timeseries-dbs
   [0]
-  (first-row (data/run-query checkins
-               (ql/aggregation (ql/count))
-               (ql/filter (ql/is-null $venue_category_name)))))
+  (first-row
+    (data/run-mbql-query checkins
+      {:aggregation [[:count]]
+       :filter      [:is-null $venue_category_name]})))
 
 ;;; filter NOT_NULL
 (expect-with-timeseries-dbs
   [1000]
-  (first-row (data/run-query checkins
-               (ql/aggregation (ql/count))
-               (ql/filter (ql/not-null $venue_category_name)))))
+  (first-row
+    (data/run-mbql-query checkins
+      {:aggregation [[:count]]
+       :filter      [:not-null $venue_category_name]})))
 
 ;;; filter STARTS_WITH
 (expect-with-timeseries-dbs
   {:columns ["venue_category_name"]
    :rows    [["Mediterannian"] ["Mexican"]]}
-  (data (data/run-query checkins
-          (ql/breakout $venue_category_name)
-          (ql/filter (ql/starts-with $venue_category_name "Me")))))
+  (data (data/run-mbql-query checkins
+          {:breakout [$venue_category_name]
+           :filter   [:starts-with $venue_category_name "Me"]})))
 
 (expect-with-timeseries-dbs
   {:columns ["venue_category_name"]
    :rows    []}
-  (data (data/run-query checkins
-          (ql/breakout $venue_category_name)
-          (ql/filter (ql/starts-with $venue_category_name "ME")))))
+  (data (data/run-mbql-query checkins
+          {:breakout [$venue_category_name]
+           :filter   [:starts-with $venue_category_name "ME"]})))
 
 (expect-with-timeseries-dbs
   {:columns ["venue_category_name"]
    :rows    [["Mediterannian"] ["Mexican"]]}
-  (data (data/run-query checkins
-          (ql/breakout $venue_category_name)
-          (ql/filter (ql/starts-with $venue_category_name "ME" {:case-sensitive false})))))
+  (data (data/run-mbql-query checkins
+          {:breakout [$venue_category_name]
+           :filter   [:starts-with $venue_category_name "ME" {:case-sensitive false}]})))
 
 ;;; filter ENDS_WITH
 (expect-with-timeseries-dbs
@@ -386,16 +398,16 @@
              ["Korean"]
              ["Mediterannian"]
              ["Mexican"]]}
-  (data (data/run-query checkins
-          (ql/breakout $venue_category_name)
-          (ql/filter (ql/ends-with $venue_category_name "an")))))
+  (data (data/run-mbql-query checkins
+          {:breakout [$venue_category_name]
+           :filter   [:ends-with $venue_category_name "an"]})))
 
 (expect-with-timeseries-dbs
   {:columns ["venue_category_name"]
    :rows    []}
-  (data (data/run-query checkins
-          (ql/breakout $venue_category_name)
-          (ql/filter (ql/ends-with $venue_category_name "AN")))))
+  (data (data/run-mbql-query checkins
+          {:breakout [$venue_category_name]
+           :filter   [:ends-with $venue_category_name "AN"]})))
 
 (expect-with-timeseries-dbs
   {:columns ["venue_category_name"]
@@ -407,9 +419,9 @@
              ["Korean"]
              ["Mediterannian"]
              ["Mexican"]]}
-  (data (data/run-query checkins
-          (ql/breakout $venue_category_name)
-          (ql/filter (ql/ends-with $venue_category_name "AN" {:case-sensitive false})))))
+  (data (data/run-mbql-query checkins
+          {:breakout [$venue_category_name]
+           :filter   [:ends-with $venue_category_name "AN" {:case-sensitive false}]})))
 
 ;;; filter CONTAINS
 (expect-with-timeseries-dbs
@@ -422,16 +434,16 @@
              ["German"]
              ["Mediterannian"]
              ["Southern"]]}
-  (data (data/run-query checkins
-          (ql/breakout $venue_category_name)
-          (ql/filter (ql/contains $venue_category_name "er")))))
+  (data (data/run-mbql-query checkins
+          {:breakout [$venue_category_name]
+           :filter   [:contains $venue_category_name "er"]})))
 
 (expect-with-timeseries-dbs
   {:columns ["venue_category_name"]
    :rows    []}
-  (data (data/run-query checkins
-          (ql/breakout $venue_category_name)
-          (ql/filter (ql/contains $venue_category_name "eR")))))
+  (data (data/run-mbql-query checkins
+          {:breakout [$venue_category_name]
+           :filter   [:contains $venue_category_name "eR"]})))
 
 (expect-with-timeseries-dbs
   {:columns ["venue_category_name"]
@@ -443,9 +455,9 @@
              ["German"]
              ["Mediterannian"]
              ["Southern"]]}
-  (data (data/run-query checkins
-          (ql/breakout $venue_category_name)
-          (ql/filter (ql/contains $venue_category_name "eR" {:case-sensitive false})))))
+  (data (data/run-mbql-query checkins
+          {:breakout [$venue_category_name]
+           :filter   [:contains $venue_category_name "eR" {:case-sensitive false}]})))
 
 ;;; order by aggregate field (?)
 (expect-with-timeseries-dbs
@@ -460,11 +472,11 @@
              ["Spiros Teofil"       "Bar"      10]
              ["Dwight Gresham"      "Bar"       9]
              ["Frans Hevel"         "Japanese"  9]]}
-  (data (data/run-query checkins
-          (ql/aggregation (ql/count))
-          (ql/breakout $user_name $venue_category_name)
-          (ql/order-by (ql/desc (ql/aggregate-field 0)))
-          (ql/limit 10))))
+  (data (data/run-mbql-query checkins
+          {:aggregation [[:count]]
+           :breakout    [$user_name $venue_category_name]
+           :order-by    [[:desc [:aggregation 0]]]
+           :limit       10})))
 
 ;;; date bucketing - default (day)
 (expect-with-timeseries-dbs
@@ -474,10 +486,10 @@
              ["2013-01-19+00:00" 1]
              ["2013-01-22+00:00" 1]
              ["2013-01-23+00:00" 1]]}
-  (data (data/run-query checkins
-          (ql/aggregation (ql/count))
-          (ql/breakout $timestamp)
-          (ql/limit 5))))
+  (data (data/run-mbql-query checkins
+          {:aggregation [[:count]]
+           :breakout    [$timestamp]
+           :limit       5})))
 
 ;;; date bucketing - minute
 (expect-with-timeseries-dbs
@@ -487,19 +499,19 @@
              ["2013-01-19T08:00:00+00:00" 1]
              ["2013-01-22T08:00:00+00:00" 1]
              ["2013-01-23T08:00:00+00:00" 1]]}
-  (data (data/run-query checkins
-          (ql/aggregation (ql/count))
-          (ql/breakout (ql/datetime-field $timestamp :minute))
-          (ql/limit 5))))
+  (data (data/run-mbql-query checkins
+          {:aggregation [[:count]]
+           :breakout    [[:datetime-field $timestamp :minute]]
+           :limit       5})))
 
 ;;; date bucketing - minute-of-hour
 (expect-with-timeseries-dbs
   {:columns ["timestamp" "count"]
    :rows    [[0 1000]]}
-  (data (data/run-query checkins
-          (ql/aggregation (ql/count))
-          (ql/breakout (ql/datetime-field $timestamp :minute-of-hour))
-          (ql/limit 5))))
+  (data (data/run-mbql-query checkins
+          {:aggregation [[:count]]
+           :breakout    [[:datetime-field $timestamp :minute-of-hour]]
+           :limit       5})))
 
 ;;; date bucketing - hour
 (expect-with-timeseries-dbs
@@ -509,20 +521,20 @@
              ["2013-01-19T08:00:00+00:00" 1]
              ["2013-01-22T08:00:00+00:00" 1]
              ["2013-01-23T08:00:00+00:00" 1]]}
-  (data (data/run-query checkins
-          (ql/aggregation (ql/count))
-          (ql/breakout (ql/datetime-field $timestamp :hour))
-          (ql/limit 5))))
+  (data (data/run-mbql-query checkins
+          {:aggregation [[:count]]
+           :breakout    [[:datetime-field $timestamp :hour]]
+           :limit       5})))
 
 ;;; date bucketing - hour-of-day
 (expect-with-timeseries-dbs
   {:columns ["timestamp" "count"]
    :rows    [[7 719]
              [8 281]]}
-  (data (data/run-query checkins
-          (ql/aggregation (ql/count))
-          (ql/breakout (ql/datetime-field $timestamp :hour-of-day))
-          (ql/limit 5))))
+  (data (data/run-mbql-query checkins
+          {:aggregation [[:count]]
+           :breakout    [[:datetime-field $timestamp :hour-of-day]]
+           :limit       5})))
 
 ;;; date bucketing - week
 (expect-with-timeseries-dbs
@@ -532,10 +544,10 @@
              ["2013-01-13" 1]
              ["2013-01-20" 4]
              ["2013-01-27" 1]]}
-  (data (data/run-query checkins
-          (ql/aggregation (ql/count))
-          (ql/breakout (ql/datetime-field $timestamp :week))
-          (ql/limit 5))))
+  (data (data/run-mbql-query checkins
+          {:aggregation [[:count]]
+           :breakout    [[:datetime-field $timestamp :week]]
+           :limit       5})))
 
 ;;; date bucketing - day
 (expect-with-timeseries-dbs
@@ -545,10 +557,10 @@
              ["2013-01-19+00:00" 1]
              ["2013-01-22+00:00" 1]
              ["2013-01-23+00:00" 1]]}
-  (data (data/run-query checkins
-          (ql/aggregation (ql/count))
-          (ql/breakout (ql/datetime-field $timestamp :day))
-          (ql/limit 5))))
+  (data (data/run-mbql-query checkins
+          {:aggregation [[:count]]
+           :breakout    [[:datetime-field $timestamp :day]]
+           :limit       5})))
 
 ;;; date bucketing - day-of-week
 (expect-with-timeseries-dbs
@@ -558,10 +570,10 @@
              [3 153]
              [4 136]
              [5 139]]}
-  (data (data/run-query checkins
-          (ql/aggregation (ql/count))
-          (ql/breakout (ql/datetime-field $timestamp :day-of-week))
-          (ql/limit 5))))
+  (data (data/run-mbql-query checkins
+          {:aggregation [[:count]]
+           :breakout    [[:datetime-field $timestamp :day-of-week]]
+           :limit       5})))
 
 ;;; date bucketing - day-of-month
 (expect-with-timeseries-dbs
@@ -571,10 +583,10 @@
              [3 42]
              [4 35]
              [5 43]]}
-  (data (data/run-query checkins
-          (ql/aggregation (ql/count))
-          (ql/breakout (ql/datetime-field $timestamp :day-of-month))
-          (ql/limit 5))))
+  (data (data/run-mbql-query checkins
+          {:aggregation [[:count]]
+           :breakout    [[:datetime-field $timestamp :day-of-month]]
+           :limit       5})))
 
 ;;; date bucketing - day-of-year
 (expect-with-timeseries-dbs
@@ -584,10 +596,10 @@
              [5 1]
              [6 1]
              [7 2]]}
-  (data (data/run-query checkins
-          (ql/aggregation (ql/count))
-          (ql/breakout (ql/datetime-field $timestamp :day-of-year))
-          (ql/limit 5))))
+  (data (data/run-mbql-query checkins
+          {:aggregation [[:count]]
+           :breakout    [[:datetime-field $timestamp :day-of-year]]
+           :limit       5})))
 
 ;;; date bucketing - week-of-year
 (expect-with-timeseries-dbs
@@ -597,10 +609,10 @@
              [3  8]
              [4 10]
              [5  4]]}
-  (data (data/run-query checkins
-          (ql/aggregation (ql/count))
-          (ql/breakout (ql/datetime-field $timestamp :week-of-year))
-          (ql/limit 5))))
+  (data (data/run-mbql-query checkins
+          {:aggregation [[:count]]
+           :breakout    [[:datetime-field $timestamp :week-of-year]]
+           :limit       5})))
 
 ;;; date bucketing - month
 (expect-with-timeseries-dbs
@@ -610,25 +622,24 @@
              ["2013-03-01" 21]
              ["2013-04-01" 26]
              ["2013-05-01" 23]]}
-  (data (data/run-query checkins
-          (ql/aggregation (ql/count))
-          (ql/breakout (ql/datetime-field $timestamp :month))
-          (ql/limit 5))))
+  (data (data/run-mbql-query checkins
+          {:aggregation [[:count]]
+           :breakout    [[:datetime-field $timestamp :month]]
+           :limit       5})))
 
-;; This test is similar to the above query but doesn't use a limit
-;; clause which causes the query to be a grouped timeseries query
-;; rather than a topN query. The dates below are formatted incorrectly
-;; due to https://github.com/metabase/metabase/issues/5969.
+;; This test is similar to the above query but doesn't use a limit clause which causes the query to be a grouped
+;; timeseries query rather than a topN query. The dates below are formatted incorrectly due to
+;; https://github.com/metabase/metabase/issues/5969.
 (expect-with-timeseries-dbs
   {:columns ["timestamp" "count"]
-   :rows [["2013-01-01T00:00:00.000Z" 8]
-          ["2013-02-01T00:00:00.000Z" 11]
-          ["2013-03-01T00:00:00.000Z" 21]
-          ["2013-04-01T00:00:00.000Z" 26]
-          ["2013-05-01T00:00:00.000Z" 23]]}
-  (-> (data/run-query checkins
-        (ql/aggregation (ql/count))
-        (ql/breakout (ql/datetime-field $timestamp :month)))
+   :rows    [["2013-01-01T00:00:00.000Z" 8]
+             ["2013-02-01T00:00:00.000Z" 11]
+             ["2013-03-01T00:00:00.000Z" 21]
+             ["2013-04-01T00:00:00.000Z" 26]
+             ["2013-05-01T00:00:00.000Z" 23]]}
+  (-> (data/run-mbql-query checkins
+        {:aggregation [[:count]]
+         :breakout    [[:datetime-field $timestamp :month]]})
       data
       (update :rows #(take 5 %))))
 
@@ -640,10 +651,10 @@
              [3  92]
              [4  89]
              [5 111]]}
-  (data (data/run-query checkins
-          (ql/aggregation (ql/count))
-          (ql/breakout (ql/datetime-field $timestamp :month-of-year))
-          (ql/limit 5))))
+  (data (data/run-mbql-query checkins
+          {:aggregation [[:count]]
+           :breakout    [[:datetime-field $timestamp :month-of-year]]
+           :limit       5})))
 
 ;;; date bucketing - quarter
 (expect-with-timeseries-dbs
@@ -653,10 +664,10 @@
              ["2013-07-01" 55]
              ["2013-10-01" 65]
              ["2014-01-01" 107]]}
-  (data (data/run-query checkins
-          (ql/aggregation (ql/count))
-          (ql/breakout (ql/datetime-field $timestamp :quarter))
-          (ql/limit 5))))
+  (data (data/run-mbql-query checkins
+          {:aggregation [[:count]]
+           :breakout    [[:datetime-field $timestamp :quarter]]
+           :limit       5})))
 
 ;;; date bucketing - quarter-of-year
 (expect-with-timeseries-dbs
@@ -665,10 +676,10 @@
              [2 284]
              [3 278]
              [4 238]]}
-  (data (data/run-query checkins
-          (ql/aggregation (ql/count))
-          (ql/breakout (ql/datetime-field $timestamp :quarter-of-year))
-          (ql/limit 5))))
+  (data (data/run-mbql-query checkins
+          {:aggregation [[:count]]
+           :breakout    [[:datetime-field $timestamp :quarter-of-year]]
+           :limit       5})))
 
 ;;; date bucketing - year
 (expect-with-timeseries-dbs
@@ -676,139 +687,205 @@
    :rows    [[2013 235]
              [2014 498]
              [2015 267]]}
-  (data (data/run-query checkins
-          (ql/aggregation (ql/count))
-          (ql/breakout (ql/datetime-field $timestamp :year))
-          (ql/limit 5))))
+  (data (data/run-mbql-query checkins
+          {:aggregation [[:count]]
+           :breakout    [[:datetime-field $timestamp :year]]
+           :limit       5})))
 
 
 
 ;;; `not` filter -- Test that we can negate the various other filter clauses
 
 ;;; =
-(expect-with-timeseries-dbs [999] (first-row (data/run-query checkins
-                                              (ql/aggregation (ql/count))
-                                              (ql/filter (ql/not (ql/= $id 1))))))
+(expect-with-timeseries-dbs
+  [999]
+  (first-row
+    (data/run-mbql-query checkins
+      {:aggregation [[:count]]
+       :filter      [:not [:= $id 1]]})))
 
 ;;; !=
-(expect-with-timeseries-dbs [1] (first-row (data/run-query checkins
-                                             (ql/aggregation (ql/count))
-                                             (ql/filter (ql/not (ql/!= $id 1))))))
+(expect-with-timeseries-dbs
+  [1]
+  (first-row
+    (data/run-mbql-query checkins
+      {:aggregation [[:count]]
+       :filter      [:not [:!= $id 1]]})))
 ;;; <
-(expect-with-timeseries-dbs [961] (first-row (data/run-query checkins
-                                               (ql/aggregation (ql/count))
-                                               (ql/filter (ql/not (ql/< $id 40))))))
+(expect-with-timeseries-dbs
+  [961]
+  (first-row
+    (data/run-mbql-query checkins
+      {:aggregation [[:count]]
+       :filter      [:not [:< $id 40]]})))
 
 ;;; >
-(expect-with-timeseries-dbs [40] (first-row (data/run-query checkins
-                                              (ql/aggregation (ql/count))
-                                              (ql/filter (ql/not (ql/> $id 40))))))
+(expect-with-timeseries-dbs
+  [40]
+  (first-row
+    (data/run-mbql-query checkins
+      {:aggregation [[:count]]
+       :filter      [:not [:> $id 40]]})))
 
 ;;; <=
-(expect-with-timeseries-dbs [960] (first-row (data/run-query checkins
-                                               (ql/aggregation (ql/count))
-                                               (ql/filter (ql/not (ql/<= $id 40))))))
+(expect-with-timeseries-dbs
+  [960]
+  (first-row
+    (data/run-mbql-query checkins
+      {:aggregation [[:count]]
+       :filter      [:not [:<= $id 40]]})))
 
 ;;; >=
-(expect-with-timeseries-dbs [39] (first-row (data/run-query checkins
-                                              (ql/aggregation (ql/count))
-                                              (ql/filter (ql/not (ql/>= $id 40))))))
+(expect-with-timeseries-dbs
+  [39]
+  (first-row
+    (data/run-mbql-query checkins
+      {:aggregation [[:count]]
+       :filter      [:not [:>= $id 40]]})))
 
 ;;; is-null
-(expect-with-timeseries-dbs [1000] (first-row (data/run-query checkins
-                                                (ql/aggregation (ql/count))
-                                                (ql/filter (ql/not (ql/is-null $id))))))
+(expect-with-timeseries-dbs
+  [1000]
+  (first-row
+    (data/run-mbql-query checkins
+      {:aggregation [[:count]]
+       :filter      [:not [:is-null $id]]})))
 
 ;;; between
-(expect-with-timeseries-dbs [989] (first-row (data/run-query checkins
-                                               (ql/aggregation (ql/count))
-                                               (ql/filter (ql/not (ql/between $id 30 40))))))
+(expect-with-timeseries-dbs
+  [989]
+  (first-row
+    (data/run-mbql-query checkins
+      {:aggregation [[:count]]
+       :filter      [:not [:between $id 30 40]]})))
 
 ;;; inside
-(expect-with-timeseries-dbs [377] (first-row (data/run-query checkins
-                                               (ql/aggregation (ql/count))
-                                               (ql/filter (ql/not (ql/inside $venue_latitude $venue_longitude 40 -120 30 -110))))))
+(expect-with-timeseries-dbs
+  [377]
+  (first-row
+    (data/run-mbql-query checkins
+      {:aggregation [[:count]]
+       :filter      [:not [:inside $venue_latitude $venue_longitude 40 -120 30 -110]]})))
 
 ;;; starts-with
-(expect-with-timeseries-dbs [795] (first-row (data/run-query checkins
-                                               (ql/aggregation (ql/count))
-                                               (ql/filter (ql/not (ql/starts-with $venue_name "T"))))))
+(expect-with-timeseries-dbs
+  [795]
+  (first-row
+    (data/run-mbql-query checkins
+      {:aggregation [[:count]]
+       :filter      [:not [:starts-with $venue_name "T"]]})))
 
 ;;; contains
-(expect-with-timeseries-dbs [971] (first-row (data/run-query checkins
-                                               (ql/aggregation (ql/count))
-                                               (ql/filter (ql/not (ql/contains $venue_name "BBQ"))))))
+(expect-with-timeseries-dbs
+  [971]
+  (first-row
+    (data/run-mbql-query checkins
+      {:aggregation [[:count]]
+       :filter      [:not [:contains $venue_name "BBQ"]]})))
 
 ;;; ends-with
-(expect-with-timeseries-dbs [884] (first-row (data/run-query checkins
-                                               (ql/aggregation (ql/count))
-                                               (ql/filter (ql/not (ql/ends-with $venue_name "a"))))))
+(expect-with-timeseries-dbs
+  [884]
+  (first-row
+    (data/run-mbql-query checkins
+      {:aggregation [[:count]]
+       :filter      [:not [:ends-with $venue_name "a"]]})))
 
 ;;; and
-(expect-with-timeseries-dbs [975] (first-row (data/run-query checkins
-                                              (ql/aggregation (ql/count))
-                                              (ql/filter (ql/not (ql/and (ql/> $id 32)
-                                                                         (ql/contains $venue_name "BBQ")))))))
+(expect-with-timeseries-dbs
+  [975]
+  (first-row
+    (data/run-mbql-query checkins
+      {:aggregation [[:count]]
+       :filter      [:not [:and
+                           [:> $id 32]
+                           [:contains $venue_name "BBQ"]]]})))
 ;;; or
-(expect-with-timeseries-dbs [28] (first-row (data/run-query checkins
-                                              (ql/aggregation (ql/count))
-                                              (ql/filter (ql/not (ql/or (ql/> $id 32)
-                                                                        (ql/contains $venue_name "BBQ")))))))
+(expect-with-timeseries-dbs
+  [28]
+  (first-row
+    (data/run-mbql-query checkins
+      {:aggregation [[:count]]
+       :filter      [:not [:or [:> $id 32]
+                           [:contains $venue_name "BBQ"]]]})))
 
 ;;; nested and/or
-(expect-with-timeseries-dbs [969] (first-row (data/run-query checkins
-                                               (ql/aggregation (ql/count))
-                                               (ql/filter (ql/not (ql/or (ql/and (ql/> $id 32)
-                                                                                 (ql/< $id 35))
-                                                                         (ql/contains $venue_name "BBQ")))))))
+(expect-with-timeseries-dbs
+  [969]
+  (first-row
+    (data/run-mbql-query checkins
+      {:aggregation [[:count]]
+       :filter      [:not [:or [:and
+                                [:> $id 32]
+                                [:< $id 35]]
+                           [:contains $venue_name "BBQ"]]]})))
 
 ;;; nested not
-(expect-with-timeseries-dbs [29] (first-row (data/run-query checkins
-                                             (ql/aggregation (ql/count))
-                                             (ql/filter (ql/not (ql/not (ql/contains $venue_name "BBQ")))))))
+(expect-with-timeseries-dbs
+  [29]
+  (first-row
+    (data/run-mbql-query checkins
+      {:aggregation [[:count]]
+       :filter      [:not [:not [:contains $venue_name "BBQ"]]]})))
 
 ;;; not nested inside and/or
-(expect-with-timeseries-dbs [4] (first-row (data/run-query checkins
-                                             (ql/aggregation (ql/count))
-                                             (ql/filter (ql/and (ql/not (ql/> $id 32))
-                                                                (ql/contains $venue_name "BBQ"))))))
+(expect-with-timeseries-dbs
+  [4]
+  (first-row
+    (data/run-mbql-query checkins
+      {:aggregation [[:count]]
+       :filter      [:and
+                     [:not [:> $id 32]]
+                     [:contains $venue_name "BBQ"]]})))
 
 ;;; time-interval
 (expect-with-timeseries-dbs
   [1000]
   (first-row
-    (data/run-query checkins
-      (ql/aggregation (ql/count))
-      ;; test data is all in the past so nothing happened today <3
-      (ql/filter (ql/not (ql/time-interval $timestamp :current :day))))))
+    (data/run-mbql-query checkins
+      {:aggregation [[:count]]
+       ;; test data is all in the past so nothing happened today <3
+       :filter      [:not [:time-interval $timestamp :current :day]]})))
 
 
 
 ;;; MIN & MAX
 
 ;; tests for dimension columns
-(expect-with-timeseries-dbs [4.0] (first-row (data/run-query checkins
-                                               (ql/aggregation (ql/max $venue_price)))))
+(expect-with-timeseries-dbs
+  [4.0]
+  (first-row
+    (data/run-mbql-query checkins
+      {:aggregation [[:max $venue_price]]})))
 
-(expect-with-timeseries-dbs [1.0] (first-row (data/run-query checkins
-                                               (ql/aggregation (ql/min $venue_price)))))
+(expect-with-timeseries-dbs
+  [1.0]
+  (first-row
+    (data/run-mbql-query checkins
+      {:aggregation [[:min $venue_price]]})))
 
 ;; tests for metric columns
-(expect-with-timeseries-dbs [1.0] (first-row (data/run-query checkins
-                                               (ql/aggregation (ql/max $count)))))
+(expect-with-timeseries-dbs
+  [1.0]
+  (first-row
+    (data/run-mbql-query checkins
+      {:aggregation [[:max $count]]})))
 
-(expect-with-timeseries-dbs [1.0] (first-row (data/run-query checkins
-                                               (ql/aggregation (ql/min $count)))))
+(expect-with-timeseries-dbs
+  [1.0]
+  (first-row
+    (data/run-mbql-query checkins
+      {:aggregation [[:min $count]]})))
 
 (expect-with-timeseries-dbs
- ;; some sort of weird quirk w/ druid where all columns in breakout get converted to strings
+  ;; some sort of weird quirk w/ druid where all columns in breakout get converted to strings
   [["1" 34.0071] ["2" 33.7701] ["3" 10.0646] ["4" 33.983]]
-  (rows (data/run-query checkins
-          (ql/aggregation (ql/min $venue_latitude))
-          (ql/breakout $venue_price))))
+  (rows (data/run-mbql-query checkins
+          {:aggregation [[:min $venue_latitude]]
+           :breakout    [$venue_price]})))
 
 (expect-with-timeseries-dbs
   [["1" 37.8078] ["2" 40.7794] ["3" 40.7262] ["4" 40.7677]]
-  (rows (data/run-query checkins
-          (ql/aggregation (ql/max $venue_latitude))
-          (ql/breakout $venue_price))))
+  (rows (data/run-mbql-query checkins
+          {:aggregation [[:max $venue_latitude]]
+           :breakout    [$venue_price]})))
diff --git a/test/metabase/util/encryption_test.clj b/test/metabase/util/encryption_test.clj
index a858a6b1243f5060b8291ecc9ac9000e685d2155..4783db6a989a5258c436eb9702f6523394f2ac0f 100644
--- a/test/metabase/util/encryption_test.clj
+++ b/test/metabase/util/encryption_test.clj
@@ -5,6 +5,18 @@
             [metabase.test.util :as tu]
             [metabase.util.encryption :as encryption]))
 
+(defn do-with-secret-key [^String secret-key, f]
+  (with-redefs [encryption/default-secret-key (when (seq secret-key)
+                                                (encryption/secret-key->hash secret-key))]
+    (f)))
+
+(defmacro with-secret-key
+  "Run `body` with the encryption secret key temporarily bound to `secret-key`. Useful for testing how functions behave
+  with and without encryption disabled."
+  {:style/indent 1}
+  [^String secret-key, & body]
+  `(do-with-secret-key ~secret-key (fn [] ~@body)))
+
 (def ^:private secret   (encryption/secret-key->hash "Orw0AAyzkO/kPTLJRxiyKoBHXa/d6ZcO+p+gpZO/wSQ="))
 (def ^:private secret-2 (encryption/secret-key->hash "0B9cD6++AME+A7/oR7Y2xvPRHX3cHA2z7w+LbObd/9Y="))
 
@@ -42,14 +54,49 @@
   "{\"a\":100}"
   (encryption/maybe-decrypt secret "{\"a\":100}"))
 
-;; trying to decrypt something that is encrypted with the wrong key with `maybe-decrypt` should return `nil`...
+;; trying to decrypt something that is encrypted with the wrong key with `maybe-decrypt` should return the ciphertext...
+(let [original-ciphertext (encryption/encrypt secret "WOW")]
+  (expect
+    original-ciphertext
+    (encryption/maybe-decrypt secret-2 original-ciphertext)))
+
+(defn- includes-encryption-warning? [log-messages]
+  (some (fn [[level _ message]]
+          (and (= level :warn)
+               (str/includes? message (str "Cannot decrypt encrypted string. Have you changed or forgot to set "
+                                           "MB_ENCRYPTION_SECRET_KEY? Message seems corrupt or manipulated."))))
+        log-messages))
+
+(expect
+  (includes-encryption-warning?
+   (tu/with-mb-log-messages-at-level :warn
+     (encryption/maybe-decrypt secret-2 (encryption/encrypt secret "WOW")))))
+
+;; Something obviously not encrypted should avoiding trying to decrypt it (and thus not log an error)
+(expect
+  []
+  (tu/with-mb-log-messages-at-level :warn
+    (encryption/maybe-decrypt secret "abc")))
+
+;; Something obviously not encrypted should return the original string
+(expect
+  "abc"
+  (encryption/maybe-decrypt secret "abc"))
+
+(def ^:private fake-ciphertext
+  "AES+CBC's block size is 16 bytes and the tag length is 32 bytes. This is a string of characters that is the same
+  length as would be expected for something that has been encrypted, but it is not encrypted, just unlucky enough to
+  have the same size"
+  (apply str (repeat 64 "a")))
+
+;; Something that is not encrypted, but might be (is the correct shape etc) should attempt to be decrypted. If unable
+;; to decrypt it, log a warning.
 (expect
-  nil
-  (encryption/maybe-decrypt secret-2 (encryption/encrypt secret "WOW")))
+  (includes-encryption-warning?
+   (tu/with-mb-log-messages-at-level :warn
+     (encryption/maybe-decrypt secret fake-ciphertext))))
 
+;; Something that is not encrypted, but might be should return the original text
 (expect
-  (some (fn [[_ _ message]]
-          (str/includes? message (str "Cannot decrypt encrypted details. Have you changed or forgot to set "
-                                      "MB_ENCRYPTION_SECRET_KEY? Message seems corrupt or manipulated.")))
-        (tu/with-log-messages
-          (encryption/maybe-decrypt secret-2 (encryption/encrypt secret "WOW")))))
+  fake-ciphertext
+  (encryption/maybe-decrypt secret fake-ciphertext))
diff --git a/test/metabase/util/schema_test.clj b/test/metabase/util/schema_test.clj
index ec063f7b779b74c68cfaca4bcf0aa609c0cc3e4b..37cad63c68b97b15bc5fd67c1d006db57a888663 100644
--- a/test/metabase/util/schema_test.clj
+++ b/test/metabase/util/schema_test.clj
@@ -4,6 +4,7 @@
             [expectations :refer :all]
             [metabase.api.common :as api]
             [metabase.util.schema :as su]
+            [puppetlabs.i18n.core :as i18n]
             [schema.core :as s]))
 
 ;; check that the API error message generation is working as intended
@@ -11,7 +12,7 @@
   (str "value may be nil, or if non-nil, value must satisfy one of the following requirements: "
        "1) value must be a boolean. "
        "2) value must be a valid boolean string ('true' or 'false').")
-  (su/api-error-message (s/maybe (s/cond-pre s/Bool su/BooleanString))))
+  (str (su/api-error-message (s/maybe (s/cond-pre s/Bool su/BooleanString)))))
 
 ;; check that API error message respects `api-param` when specified
 (api/defendpoint POST "/:id/dimension"
@@ -34,3 +35,15 @@
        "\n"
        "*  **`dimension-name`** value must be a non-blank string.")
   (:doc (meta #'POST_:id_dimension)))
+
+(defn- ex-info-msg [f]
+  (try
+    (f)
+    (catch clojure.lang.ExceptionInfo e
+      (.getMessage e))))
+
+(expect
+  #"INTEGER GREATER THAN ZERO"
+  (let [zz (i18n/string-as-locale "zz")]
+    (i18n/with-user-locale zz
+      (ex-info-msg #(s/validate su/IntGreaterThanZero -1)))))
diff --git a/test/metabase/util_test.clj b/test/metabase/util_test.clj
index a618a245c9188e4e588690ecfff4bf41c191c00c..b0d527863f26b94e41250b3785ac80f5d4c21599 100644
--- a/test/metabase/util_test.clj
+++ b/test/metabase/util_test.clj
@@ -185,3 +185,19 @@
 (expect nil (index-of pos? [-1 0 -2 -3]))
 (expect nil (index-of pos? nil))
 (expect nil (index-of pos? []))
+
+
+;; is-java-9-or-higher?
+(expect
+  false
+  (is-java-9-or-higher? "1.8.0_141"))
+
+(expect
+  (is-java-9-or-higher? "1.9.0_141"))
+
+(expect
+ (is-java-9-or-higher? "10.0.1"))
+
+;; make sure we can parse wacky version strings like `9-internal`: See #8282
+(expect
+  (is-java-9-or-higher? "9-internal"))
diff --git a/test_resources/locales.clj b/test_resources/locales.clj
new file mode 100644
index 0000000000000000000000000000000000000000..7b4ba58c4e21af427388e9f0bba6ea4668d5aa92
--- /dev/null
+++ b/test_resources/locales.clj
@@ -0,0 +1,5 @@
+{
+  :locales  #{"en" "zz"}
+  :packages ["metabase"]
+  :bundle   "metabase.Messages"
+}
diff --git a/test_resources/metabase/Messages_zz.properties b/test_resources/metabase/Messages_zz.properties
new file mode 100644
index 0000000000000000000000000000000000000000..7f910db401b5fd17927fc530179bceec5e622103
--- /dev/null
+++ b/test_resources/metabase/Messages_zz.properties
@@ -0,0 +1,4 @@
+Host=HOST
+Integer\ greater\ than\ zero=INTEGER\ GREATER\ THAN ZERO
+value\ must\ be\ an\ integer\ greater\ than\ zero.=VALUE\ MUST\ BE\ AN\ INTEGER\ GREATER\ THAN\ ZERO.
+Test\ setting\ -\ with\ i18n=TEST\ SETTING\ -\ WITH\ I18N
\ No newline at end of file
diff --git a/webpack.config.js b/webpack.config.js
index f4e9350db7fc3aa79e1aed3c0b83339b043a509f..61ef97e7b2c934fe82b3a6911faa2c8d0922c999 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -4,259 +4,272 @@
 require("babel-register");
 require("babel-polyfill");
 
-var webpack = require('webpack');
+var webpack = require("webpack");
 
-var ExtractTextPlugin = require('extract-text-webpack-plugin');
-var HtmlWebpackPlugin = require('html-webpack-plugin');
-var HtmlWebpackHarddiskPlugin = require('html-webpack-harddisk-plugin');
+var ExtractTextPlugin = require("extract-text-webpack-plugin");
+var HtmlWebpackPlugin = require("html-webpack-plugin");
+var HtmlWebpackHarddiskPlugin = require("html-webpack-harddisk-plugin");
 var UnusedFilesWebpackPlugin = require("unused-files-webpack-plugin").default;
-var BannerWebpackPlugin = require('banner-webpack-plugin');
-var UglifyJSPlugin = require('uglifyjs-webpack-plugin')
+var BannerWebpackPlugin = require("banner-webpack-plugin");
+var UglifyJSPlugin = require("uglifyjs-webpack-plugin");
 
-var fs = require('fs');
+var fs = require("fs");
 
 var chevrotain = require("chevrotain");
-var allTokens = require("./frontend/src/metabase/lib/expressions/tokens").allTokens;
+var allTokens = require("./frontend/src/metabase/lib/expressions/tokens")
+  .allTokens;
 
-var SRC_PATH = __dirname + '/frontend/src/metabase';
-var LIB_SRC_PATH = __dirname + '/frontend/src/metabase-lib';
-var TEST_SUPPORT_PATH = __dirname + '/frontend/test/__support__';
-var BUILD_PATH = __dirname + '/resources/frontend_client';
+var SRC_PATH = __dirname + "/frontend/src/metabase";
+var LIB_SRC_PATH = __dirname + "/frontend/src/metabase-lib";
+var TEST_SUPPORT_PATH = __dirname + "/frontend/test/__support__";
+var BUILD_PATH = __dirname + "/resources/frontend_client";
 
 // default NODE_ENV to development
 var NODE_ENV = process.env["NODE_ENV"] || "development";
 
 // Babel:
 var BABEL_CONFIG = {
-    cacheDirectory: process.env.BABEL_DISABLE_CACHE ? null : ".babel_cache"
+  cacheDirectory: process.env.BABEL_DISABLE_CACHE ? null : ".babel_cache",
 };
 
 var CSS_CONFIG = {
-    localIdentName: NODE_ENV !== "production" ?
-        "[name]__[local]___[hash:base64:5]" :
-        "[hash:base64:5]",
-    url: false, // disabled because we need to use relative url()
-    importLoaders: 1
-}
+  localIdentName:
+    NODE_ENV !== "production"
+      ? "[name]__[local]___[hash:base64:5]"
+      : "[hash:base64:5]",
+  url: false, // disabled because we need to use relative url()
+  importLoaders: 1,
+};
 
-var config = module.exports = {
-    context: SRC_PATH,
+var config = (module.exports = {
+  context: SRC_PATH,
 
-    // output a bundle for the app JS and a bundle for styles
-    // eventually we should have multiple (single file) entry points for various pieces of the app to enable code splitting
-    entry: {
-        "app-main": './app-main.js',
-        "app-public": './app-public.js',
-        "app-embed": './app-embed.js',
-        styles: './css/index.css',
-    },
+  // output a bundle for the app JS and a bundle for styles
+  // eventually we should have multiple (single file) entry points for various pieces of the app to enable code splitting
+  entry: {
+    "app-main": "./app-main.js",
+    "app-public": "./app-public.js",
+    "app-embed": "./app-embed.js",
+    styles: "./css/index.css",
+  },
 
-    // output to "dist"
-    output: {
-        path: BUILD_PATH + '/app/dist',
-        // NOTE: the filename on disk won't include "?[chunkhash]" but the URL in index.html generated by HtmlWebpackPlugin will:
-        filename: '[name].bundle.js?[hash]',
-        publicPath: 'app/dist/'
-    },
+  // output to "dist"
+  output: {
+    path: BUILD_PATH + "/app/dist",
+    // NOTE: the filename on disk won't include "?[chunkhash]" but the URL in index.html generated by HtmlWebpackPlugin will:
+    filename: "[name].bundle.js?[hash]",
+    publicPath: "app/dist/",
+  },
 
-    module: {
-        rules: [
-            {
-                test: /\.(js|jsx)$/,
-                exclude: /node_modules/,
-                use: [
-                    { loader: "babel-loader", options: BABEL_CONFIG }
-                ]
-            },
-            {
-                test: /\.(js|jsx)$/,
-                exclude: /node_modules|\.spec\.js/,
-                use: [
-                    { loader: "eslint-loader" }
-                ]
-            },
-            {
-                test: /\.(eot|woff2?|ttf|svg|png)$/,
-                use: [
-                    { loader: "file-loader" }
-                ]
+  module: {
+    rules: [
+      {
+        test: /\.(js|jsx)$/,
+        exclude: /node_modules/,
+        use: [{ loader: "babel-loader", options: BABEL_CONFIG }],
+      },
+      {
+        test: /\.(js|jsx)$/,
+        exclude: /node_modules|\.spec\.js/,
+        use: [
+          {
+            loader: "eslint-loader",
+            options: {
+              rulePaths: [__dirname + "/frontend/lint/eslint-rules"],
             },
-            {
-                test: /\.css$/,
-                use: ExtractTextPlugin.extract({
-                    fallback: "style-loader",
-                    use: [
-                        { loader: "css-loader", options: CSS_CONFIG },
-                        { loader: "postcss-loader" }
-                    ]
-                })
-            }
-        ]
-    },
-    resolve: {
-        extensions: [".webpack.js", ".web.js", ".js", ".jsx", ".css"],
-        alias: {
-            'metabase': SRC_PATH,
-            'metabase-lib': LIB_SRC_PATH,
-            '__support__': TEST_SUPPORT_PATH,
-            'style': SRC_PATH + '/css/core/index',
-            'ace': __dirname + '/node_modules/ace-builds/src-min-noconflict',
-        }
+          },
+        ],
+      },
+      {
+        test: /\.(eot|woff2?|ttf|svg|png)$/,
+        use: [{ loader: "file-loader" }],
+      },
+      {
+        test: /\.css$/,
+        use: ExtractTextPlugin.extract({
+          fallback: "style-loader",
+          use: [
+            { loader: "css-loader", options: CSS_CONFIG },
+            { loader: "postcss-loader" },
+          ],
+        }),
+      },
+    ],
+  },
+  resolve: {
+    extensions: [".webpack.js", ".web.js", ".js", ".jsx", ".css"],
+    alias: {
+      metabase: SRC_PATH,
+      "metabase-lib": LIB_SRC_PATH,
+      __support__: TEST_SUPPORT_PATH,
+      style: SRC_PATH + "/css/core/index",
+      ace: __dirname + "/node_modules/ace-builds/src-min-noconflict",
+      // NOTE @kdoh - 7/24/18
+      // 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"
     },
+  },
 
-    plugins: [
-        new webpack.optimize.CommonsChunkPlugin({
-            name: 'vendor',
-            minChunks(module) {
-                return module.context && module.context.indexOf('node_modules') >= 0
-            }
-        }),
-        new UnusedFilesWebpackPlugin({
-            globOptions: {
-                ignore: [
-                    "**/types.js",
-                    "**/types/*.js",
-                    "**/*.spec.*",
-                    "**/__support__/*.js",
-                    "**/__mocks__/*.js*",
-                    "internal/lib/components-node.js"
-                ]
-            }
-        }),
-        // Extracts initial CSS into a standard stylesheet that can be loaded in parallel with JavaScript
-        // NOTE: the filename on disk won't include "?[chunkhash]" but the URL in index.html generated by HtmlWebpackPlugin will:
-        new ExtractTextPlugin({
-            filename: '[name].bundle.css?[contenthash]'
-        }),
-        new HtmlWebpackPlugin({
-            filename: '../../index.html',
-            chunksSortMode: 'manual',
-            chunks: ["vendor", "styles", "app-main"],
-            template: __dirname + '/resources/frontend_client/index_template.html',
-            inject: 'head',
-            alwaysWriteToDisk: true,
-        }),
-        new HtmlWebpackPlugin({
-            filename: '../../public.html',
-            chunksSortMode: 'manual',
-            chunks: ["vendor", "styles", "app-public"],
-            template: __dirname + '/resources/frontend_client/index_template.html',
-            inject: 'head',
-            alwaysWriteToDisk: true,
-        }),
-        new HtmlWebpackPlugin({
-            filename: '../../embed.html',
-            chunksSortMode: 'manual',
-            chunks: ["vendor", "styles", "app-embed"],
-            template: __dirname + '/resources/frontend_client/index_template.html',
-            inject: 'head',
-            alwaysWriteToDisk: true,
-        }),
-        new HtmlWebpackHarddiskPlugin({
-            outputPath: __dirname + '/resources/frontend_client/app/dist'
-        }),
-        new webpack.DefinePlugin({
-            'process.env': {
-                NODE_ENV: JSON.stringify(NODE_ENV)
-            }
-        }),
-        new BannerWebpackPlugin({
-            chunks: {
-                'app-main': {
-                    beforeContent: "/*\n* This file is subject to the terms and conditions defined in\n * file 'LICENSE.txt', which is part of this source code package.\n */\n",
-                },
-                'app-public': {
-                    beforeContent: "/*\n* This file is subject to the terms and conditions defined in\n * file 'LICENSE.txt', which is part of this source code package.\n */\n",
-                },
-                'app-embed': {
-                    beforeContent: "/*\n* This file is subject to the terms and conditions defined in\n * file 'LICENSE-EMBEDDING.txt', which is part of this source code package.\n */\n",
-                },
-            }
-        }),
-    ]
-};
+  plugins: [
+    new webpack.optimize.CommonsChunkPlugin({
+      name: "vendor",
+      minChunks(module) {
+        return module.context && module.context.indexOf("node_modules") >= 0;
+      },
+    }),
+    new UnusedFilesWebpackPlugin({
+      globOptions: {
+        ignore: [
+          "**/types.js",
+          "**/types/*.js",
+          "**/*.spec.*",
+          "**/__support__/*.js",
+          "**/__mocks__/*.js*",
+          "internal/lib/components-node.js",
+        ],
+      },
+    }),
+    // Extracts initial CSS into a standard stylesheet that can be loaded in parallel with JavaScript
+    // NOTE: the filename on disk won't include "?[chunkhash]" but the URL in index.html generated by HtmlWebpackPlugin will:
+    new ExtractTextPlugin({
+      filename: "[name].bundle.css?[contenthash]",
+    }),
+    new HtmlWebpackPlugin({
+      filename: "../../index.html",
+      chunksSortMode: "manual",
+      chunks: ["vendor", "styles", "app-main"],
+      template: __dirname + "/resources/frontend_client/index_template.html",
+      inject: "head",
+      alwaysWriteToDisk: true,
+    }),
+    new HtmlWebpackPlugin({
+      filename: "../../public.html",
+      chunksSortMode: "manual",
+      chunks: ["vendor", "styles", "app-public"],
+      template: __dirname + "/resources/frontend_client/index_template.html",
+      inject: "head",
+      alwaysWriteToDisk: true,
+    }),
+    new HtmlWebpackPlugin({
+      filename: "../../embed.html",
+      chunksSortMode: "manual",
+      chunks: ["vendor", "styles", "app-embed"],
+      template: __dirname + "/resources/frontend_client/index_template.html",
+      inject: "head",
+      alwaysWriteToDisk: true,
+    }),
+    new HtmlWebpackHarddiskPlugin({
+      outputPath: __dirname + "/resources/frontend_client/app/dist",
+    }),
+    new webpack.DefinePlugin({
+      "process.env": {
+        NODE_ENV: JSON.stringify(NODE_ENV),
+      },
+    }),
+    new BannerWebpackPlugin({
+      chunks: {
+        "app-main": {
+          beforeContent:
+            "/*\n* This file is subject to the terms and conditions defined in\n * file 'LICENSE.txt', which is part of this source code package.\n */\n",
+        },
+        "app-public": {
+          beforeContent:
+            "/*\n* This file is subject to the terms and conditions defined in\n * file 'LICENSE.txt', which is part of this source code package.\n */\n",
+        },
+        "app-embed": {
+          beforeContent:
+            "/*\n* This file is subject to the terms and conditions defined in\n * file 'LICENSE-EMBEDDING.txt', which is part of this source code package.\n */\n",
+        },
+      },
+    }),
+  ],
+});
 
 if (NODE_ENV === "hot") {
-    // suffixing with ".hot" allows us to run both `yarn run build-hot` and `yarn run test` or `yarn run test-watch` simultaneously
-    config.output.filename = "[name].hot.bundle.js?[hash]";
+  // suffixing with ".hot" allows us to run both `yarn run build-hot` and `yarn run test` or `yarn run test-watch` simultaneously
+  config.output.filename = "[name].hot.bundle.js?[hash]";
 
-    // point the publicPath (inlined in index.html by HtmlWebpackPlugin) to the hot-reloading server
-    config.output.publicPath = "http://localhost:8080/" + config.output.publicPath;
+  // point the publicPath (inlined in index.html by HtmlWebpackPlugin) to the hot-reloading server
+  config.output.publicPath =
+    "http://localhost:8080/" + config.output.publicPath;
 
-    config.module.rules.unshift({
-        test: /\.jsx$/,
-        // NOTE: our verison of react-hot-loader doesn't play nice with react-dnd's DragLayer, so we exclude files named `*DragLayer.jsx`
-        exclude: /node_modules|DragLayer\.jsx$/,
-        use: [
-            // NOTE Atte Keinänen 10/19/17: We are currently sticking to an old version of react-hot-loader
-            // because newer versions would require us to upgrade to react-router v4 and possibly deal with
-            // asynchronous route issues as well. See https://github.com/gaearon/react-hot-loader/issues/249
-            { loader: 'react-hot-loader' },
-            { loader: 'babel-loader', options: BABEL_CONFIG }
-        ]
-    });
+  config.module.rules.unshift({
+    test: /\.jsx$/,
+    // NOTE: our verison of react-hot-loader doesn't play nice with react-dnd's DragLayer, so we exclude files named `*DragLayer.jsx`
+    exclude: /node_modules|DragLayer\.jsx$/,
+    use: [
+      // NOTE Atte Keinänen 10/19/17: We are currently sticking to an old version of react-hot-loader
+      // because newer versions would require us to upgrade to react-router v4 and possibly deal with
+      // asynchronous route issues as well. See https://github.com/gaearon/react-hot-loader/issues/249
+      { loader: "react-hot-loader" },
+      { loader: "babel-loader", options: BABEL_CONFIG },
+    ],
+  });
 
-    // disable ExtractTextPlugin
-    config.module.rules[config.module.rules.length - 1].use = [
-        { loader: "style-loader" },
-        { loader: "css-loader", options: CSS_CONFIG },
-        { loader: "postcss-loader" }
-    ]
+  // disable ExtractTextPlugin
+  config.module.rules[config.module.rules.length - 1].use = [
+    { loader: "style-loader" },
+    { loader: "css-loader", options: CSS_CONFIG },
+    { loader: "postcss-loader" },
+  ];
 
-    config.devServer = {
-        hot: true,
-        inline: true,
-        contentBase: "frontend",
-        headers: {
-            'Access-Control-Allow-Origin': '*'
-        }
-        // if webpack doesn't reload UI after code change in development
-        // watchOptions: {
-        //     aggregateTimeout: 300,
-        //     poll: 1000
-        // }
-        // if you want to reduce stats noise
-        // stats: 'minimal' // values: none, errors-only, minimal, normal, verbose
-    };
+  config.devServer = {
+    hot: true,
+    inline: true,
+    contentBase: "frontend",
+    headers: {
+      "Access-Control-Allow-Origin": "*",
+    },
+    // if webpack doesn't reload UI after code change in development
+    // watchOptions: {
+    //     aggregateTimeout: 300,
+    //     poll: 1000
+    // }
+    // if you want to reduce stats noise
+    // stats: 'minimal' // values: none, errors-only, minimal, normal, verbose
+  };
 
-    config.plugins.unshift(
-        new webpack.NoEmitOnErrorsPlugin(),
-        new webpack.NamedModulesPlugin(),
-        new webpack.HotModuleReplacementPlugin()
-    );
+  config.plugins.unshift(
+    new webpack.NoEmitOnErrorsPlugin(),
+    new webpack.NamedModulesPlugin(),
+    new webpack.HotModuleReplacementPlugin()
+  );
 }
 
 if (NODE_ENV !== "production") {
-    // replace minified files with un-minified versions
-    for (var name in config.resolve.alias) {
-        var minified = config.resolve.alias[name];
-        var unminified = minified.replace(/[.-\/]min\b/g, '');
-        if (minified !== unminified && fs.existsSync(unminified)) {
-            config.resolve.alias[name] = unminified;
-        }
+  // replace minified files with un-minified versions
+  for (var name in config.resolve.alias) {
+    var minified = config.resolve.alias[name];
+    var unminified = minified.replace(/[.-\/]min\b/g, "");
+    if (minified !== unminified && fs.existsSync(unminified)) {
+      config.resolve.alias[name] = unminified;
     }
+  }
 
-    // enable "cheap" source maps in hot or watch mode since re-build speed overhead is < 1 second
-    config.devtool = "cheap-module-source-map";
+  // 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
-    // config.devtool = "inline-source-map"
+  // works with breakpoints
+  // config.devtool = "inline-source-map"
 
-    // helps with source maps
-    config.output.devtoolModuleFilenameTemplate = '[absolute-resource-path]';
-    config.output.pathinfo = true;
+  // helps with source maps
+  config.output.devtoolModuleFilenameTemplate = "[absolute-resource-path]";
+  config.output.pathinfo = true;
 } else {
-    config.plugins.push(new UglifyJSPlugin({
-        uglifyOptions: {
-            mangle: {
-                // this is required to ensure we don't minify Chevrotain token identifiers
-                // https://github.com/SAP/chevrotain/tree/master/examples/parser/minification
-                except: allTokens.map(function (currTok) {
-                    return chevrotain.tokenName(currTok);
-                })
-            }
-        }
-    }))
+  config.plugins.push(
+    new UglifyJSPlugin({
+      uglifyOptions: {
+        mangle: {
+          // this is required to ensure we don't minify Chevrotain token identifiers
+          // https://github.com/SAP/chevrotain/tree/master/examples/parser/minification
+          except: allTokens.map(function(currTok) {
+            return chevrotain.tokenName(currTok);
+          }),
+        },
+      },
+    })
+  );
 
-    config.devtool = "source-map";
+  config.devtool = "source-map";
 }
diff --git a/webpack.shared.config.js b/webpack.shared.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..1704facefee98d2ff8bf388b24acca85f2863aaa
--- /dev/null
+++ b/webpack.shared.config.js
@@ -0,0 +1,24 @@
+const path = require("path");
+const config = require("./webpack.config.js");
+
+const SHARED_SRC = path.join(__dirname, "frontend", "src", "metabase-shared");
+
+module.exports = {
+  entry: {
+    color_selector: "./frontend/src/metabase-shared/color_selector.js",
+  },
+  module: config.module,
+  resolve: config.resolve,
+  output: {
+    path: path.resolve(__dirname, "resources", "frontend_shared"),
+    filename: "[name].js",
+    library: "shared",
+    libraryTarget: "umd",
+  },
+};
+
+module.exports.resolve.alias["d3"] = path.join(
+  SHARED_SRC,
+  "dependencies",
+  "d3.js",
+);
diff --git a/yarn.lock b/yarn.lock
index 547b0fc686f4ae726b53368f6a22fc44fab5c5f7..1a7e08ab4608cc626f858b2a9893cdde5da7323c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -258,6 +258,10 @@ ansi-styles@^3.2.1:
   dependencies:
     color-convert "^1.9.0"
 
+ansi-styles@~1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.0.0.tgz#cb102df1c56f5123eab8b67cd7b98027a0279178"
+
 ansi-wrap@0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf"
@@ -458,6 +462,10 @@ assign-symbols@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
 
+ast-types@0.11.3:
+  version "0.11.3"
+  resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.11.3.tgz#c20757fe72ee71278ea0ff3d87e5c2ca30d9edf8"
+
 async-each@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
@@ -1193,7 +1201,7 @@ babel-polyfill@^6.26.0, babel-polyfill@^6.6.1:
     core-js "^2.5.0"
     regenerator-runtime "^0.10.5"
 
-babel-preset-es2015@^6.16.0, babel-preset-es2015@^6.6.0:
+babel-preset-es2015@^6.16.0, babel-preset-es2015@^6.6.0, babel-preset-es2015@^6.9.0:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz#d44050d6bc2c9feea702aaf38d727a0210538939"
   dependencies:
@@ -1253,7 +1261,7 @@ babel-preset-stage-0@^6.16.0, babel-preset-stage-0@^6.5.0:
     babel-plugin-transform-function-bind "^6.22.0"
     babel-preset-stage-1 "^6.24.1"
 
-babel-preset-stage-1@^6.24.1:
+babel-preset-stage-1@^6.24.1, babel-preset-stage-1@^6.5.0:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-preset-stage-1/-/babel-preset-stage-1-6.24.1.tgz#7692cd7dcd6849907e6ae4a0a85589cfb9e2bfb0"
   dependencies:
@@ -1280,7 +1288,7 @@ babel-preset-stage-3@^6.24.1:
     babel-plugin-transform-exponentiation-operator "^6.24.1"
     babel-plugin-transform-object-rest-spread "^6.22.0"
 
-babel-register@^6.11.6, babel-register@^6.26.0:
+babel-register@^6.11.6, babel-register@^6.26.0, babel-register@^6.9.0:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071"
   dependencies:
@@ -1369,6 +1377,10 @@ babylon@^6.11.0, babylon@^6.17.0, babylon@^6.17.2, babylon@^6.18.0:
   version "6.18.0"
   resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3"
 
+babylon@^7.0.0-beta.30:
+  version "7.0.0-beta.47"
+  resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.47.tgz#6d1fa44f0abec41ab7c780481e62fd9aafbdea80"
+
 backo2@1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947"
@@ -1917,6 +1929,14 @@ chalk@^2.0.1:
     escape-string-regexp "^1.0.5"
     supports-color "^5.3.0"
 
+chalk@~0.4.0:
+  version "0.4.0"
+  resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.4.0.tgz#5199a3ddcd0c1efe23bc08c1b027b06176e0c64f"
+  dependencies:
+    ansi-styles "~1.0.0"
+    has-color "~0.1.0"
+    strip-ansi "~0.1.0"
+
 change-emitter@^0.1.2:
   version "0.1.6"
   resolved "https://registry.yarnpkg.com/change-emitter/-/change-emitter-0.1.6.tgz#e8b2fe3d7f1ab7d69a32199aff91ea6931409515"
@@ -2033,6 +2053,10 @@ class-utils@^0.3.5:
     lazy-cache "^2.0.2"
     static-extend "^0.1.1"
 
+classlist-polyfill@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/classlist-polyfill/-/classlist-polyfill-1.2.0.tgz#935bc2dfd9458a876b279617514638bcaa964a2e"
+
 classnames@^2.1.3, classnames@^2.2.3, classnames@^2.2.5:
   version "2.2.5"
   resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d"
@@ -2198,6 +2222,16 @@ color-convert@^1.9.1:
   dependencies:
     color-name "^1.1.1"
 
+color-diff@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/color-diff/-/color-diff-1.1.0.tgz#983ae7f936679e94e365dfe44a16aa153bdae88e"
+
+color-harmony@^0.3.0:
+  version "0.3.0"
+  resolved "https://registry.yarnpkg.com/color-harmony/-/color-harmony-0.3.0.tgz#3e19aea2e0baf6aa49563448678fec3b4f37905e"
+  dependencies:
+    onecolor "^2.5.0"
+
 color-name@^1.0.0, color-name@^1.1.1:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
@@ -2757,6 +2791,49 @@ cyclist@~0.2.2:
   version "0.2.2"
   resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640"
 
+d3-array@^1.2.0:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.1.tgz#d1ca33de2f6ac31efadb8e050a021d7e2396d5dc"
+
+d3-collection@1:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/d3-collection/-/d3-collection-1.0.4.tgz#342dfd12837c90974f33f1cc0a785aea570dcdc2"
+
+d3-color@1:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.2.0.tgz#d1ea19db5859c86854586276ec892cf93148459a"
+
+d3-format@1:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.3.0.tgz#a3ac44269a2011cdb87c7b5693040c18cddfff11"
+
+d3-interpolate@1:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-1.2.0.tgz#40d81bd8e959ff021c5ea7545bc79b8d22331c41"
+  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"
+  dependencies:
+    d3-array "^1.2.0"
+    d3-collection "1"
+    d3-format "1"
+    d3-interpolate "1"
+    d3-time "1"
+    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"
+  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"
+
 d3@^3, d3@^3.5.17:
   version "3.5.17"
   resolved "https://registry.yarnpkg.com/d3/-/d3-3.5.17.tgz#bc46748004378b21a360c9fc7cf5231790762fb8"
@@ -3660,7 +3737,7 @@ esprima@^3.1.3:
   version "3.1.3"
   resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
 
-esprima@^4.0.0:
+esprima@^4.0.0, esprima@~4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804"
 
@@ -4065,6 +4142,10 @@ flow-bin@^0.37.4:
   version "0.37.4"
   resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.37.4.tgz#3d8da2ef746e80e730d166e09040f4198969b76b"
 
+flow-parser@^0.*:
+  version "0.75.0"
+  resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.75.0.tgz#9a1891c48051c73017b6b5cc07b3681fda3fdfb0"
+
 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"
@@ -4582,6 +4663,10 @@ has-binary@0.1.7:
   dependencies:
     isarray "0.0.1"
 
+has-color@~0.1.0:
+  version "0.1.7"
+  resolved "https://registry.yarnpkg.com/has-color/-/has-color-0.1.7.tgz#67144a5260c34fc3cca677d041daf52fe7b78b2f"
+
 has-cors@1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39"
@@ -4937,7 +5022,7 @@ humanize-plus@^1.8.1:
   version "1.8.2"
   resolved "https://registry.yarnpkg.com/humanize-plus/-/humanize-plus-1.8.2.tgz#a65b34459ad6367adbb3707a82a3c9f916167030"
 
-icepick@2:
+icepick@2.3.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/icepick/-/icepick-2.3.0.tgz#1d1b0b28b80c1ff720a1f62359dab46392806f5c"
 
@@ -5747,6 +5832,10 @@ jest-jasmine2@^19.0.2:
     jest-message-util "^19.0.0"
     jest-snapshot "^19.0.2"
 
+jest-localstorage-mock@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/jest-localstorage-mock/-/jest-localstorage-mock-2.2.0.tgz#ce9a9de01dfdde2ad8aa08adf73acc7e5cc394cf"
+
 jest-matcher-utils@^19.0.0:
   version "19.0.0"
   resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-19.0.0.tgz#5ecd9b63565d2b001f61fbf7ec4c7f537964564d"
@@ -5902,6 +5991,26 @@ jsbn@~0.1.0:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
 
+jscodeshift@0.5.0:
+  version "0.5.0"
+  resolved "https://registry.yarnpkg.com/jscodeshift/-/jscodeshift-0.5.0.tgz#bdb7b6cc20dd62c16aa728c3fa2d2fe66ca7c748"
+  dependencies:
+    babel-plugin-transform-flow-strip-types "^6.8.0"
+    babel-preset-es2015 "^6.9.0"
+    babel-preset-stage-1 "^6.5.0"
+    babel-register "^6.9.0"
+    babylon "^7.0.0-beta.30"
+    colors "^1.1.2"
+    flow-parser "^0.*"
+    lodash "^4.13.1"
+    micromatch "^2.3.7"
+    neo-async "^2.5.0"
+    node-dir "0.1.8"
+    nomnom "^1.8.1"
+    recast "^0.14.1"
+    temp "^0.8.1"
+    write-file-atomic "^1.2.0"
+
 jsdom@^9.11.0:
   version "9.12.0"
   resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-9.12.0.tgz#e8c546fffcb06c00d4833ca84410fed7f8a097d4"
@@ -7126,6 +7235,10 @@ negotiator@0.6.1:
   version "0.6.1"
   resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
 
+neo-async@^2.5.0:
+  version "2.5.1"
+  resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.5.1.tgz#acb909e327b1e87ec9ef15f41b8a269512ad41ee"
+
 next-tick@1:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c"
@@ -7140,6 +7253,10 @@ no-case@^2.2.0:
   dependencies:
     lower-case "^1.1.1"
 
+node-dir@0.1.8:
+  version "0.1.8"
+  resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.8.tgz#55fb8deb699070707fb67f91a460f0448294c77d"
+
 node-fetch-npm@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz#7258c9046182dca345b4208eda918daf33697ff7"
@@ -7238,6 +7355,13 @@ node-uuid@~1.4.7:
   version "1.4.8"
   resolved "https://registry.yarnpkg.com/node-uuid/-/node-uuid-1.4.8.tgz#b040eb0923968afabf8d32fb1f17f1167fdab907"
 
+nomnom@^1.8.1:
+  version "1.8.1"
+  resolved "https://registry.yarnpkg.com/nomnom/-/nomnom-1.8.1.tgz#2151f722472ba79e50a76fc125bb8c8f2e4dc2a7"
+  dependencies:
+    chalk "~0.4.0"
+    underscore "~1.6.0"
+
 "nopt@2 || 3":
   version "3.0.6"
   resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
@@ -7658,6 +7782,10 @@ once@^1.3.0, once@^1.3.1, once@^1.3.3, once@^1.4.0, once@~1.4.0:
   dependencies:
     wrappy "1"
 
+onecolor@^2.5.0:
+  version "2.5.0"
+  resolved "https://registry.yarnpkg.com/onecolor/-/onecolor-2.5.0.tgz#2256b651dc807c101f00aedbd49925c57a4431c1"
+
 onecolor@~2.4.0:
   version "2.4.2"
   resolved "https://registry.yarnpkg.com/onecolor/-/onecolor-2.4.2.tgz#a53ec3ff171c3446016dd5210d1a1b544bf7d874"
@@ -8653,7 +8781,7 @@ pretty-format@^19.0.0:
   dependencies:
     ansi-styles "^3.0.0"
 
-private@^0.1.6, private@^0.1.7:
+private@^0.1.6, private@^0.1.7, private@~0.1.5:
   version "0.1.8"
   resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
 
@@ -9322,6 +9450,15 @@ readline2@^1.0.1:
     is-fullwidth-code-point "^1.0.0"
     mute-stream "0.0.5"
 
+recast@^0.14.1:
+  version "0.14.7"
+  resolved "https://registry.yarnpkg.com/recast/-/recast-0.14.7.tgz#4f1497c2b5826d42a66e8e3c9d80c512983ff61d"
+  dependencies:
+    ast-types "0.11.3"
+    esprima "~4.0.0"
+    private "~0.1.5"
+    source-map "~0.6.1"
+
 rechoir@^0.6.2:
   version "0.6.2"
   resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384"
@@ -9831,6 +9968,10 @@ rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.
   dependencies:
     glob "^7.0.5"
 
+rimraf@~2.2.6:
+  version "2.2.8"
+  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582"
+
 ripemd160@^2.0.0, ripemd160@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.1.tgz#0f4584295c53a3628af7e6d79aca21ce57d1c6e7"
@@ -10114,7 +10255,7 @@ slice-ansi@0.0.4:
   version "0.0.4"
   resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35"
 
-slide@^1.1.3, slide@^1.1.6, slide@~1.1.3, slide@~1.1.6:
+slide@^1.1.3, slide@^1.1.5, slide@^1.1.6, slide@~1.1.3, slide@~1.1.6:
   version "1.1.6"
   resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707"
 
@@ -10545,6 +10686,10 @@ strip-ansi@^4.0.0, strip-ansi@~4.0.0:
   dependencies:
     ansi-regex "^3.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"
+
 strip-bom-stream@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz#e7144398577d51a6bed0fa1994fa05f43fd988ee"
@@ -10748,6 +10893,13 @@ tar@^4.4.0:
     safe-buffer "^5.1.1"
     yallist "^3.0.2"
 
+temp@^0.8.1:
+  version "0.8.3"
+  resolved "https://registry.yarnpkg.com/temp/-/temp-0.8.3.tgz#e0c6bc4d26b903124410e4fed81103014dfc1f59"
+  dependencies:
+    os-tmpdir "^1.0.0"
+    rimraf "~2.2.6"
+
 term-size@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69"
@@ -11083,6 +11235,10 @@ underscore@^1.8.3:
   version "1.8.3"
   resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022"
 
+underscore@~1.6.0:
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.6.0.tgz#8b38b10cacdef63337b8b24e4ff86d45aea529a8"
+
 underscore@~1.7.0:
   version "1.7.0"
   resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.7.0.tgz#6bbaf0877500d36be34ecaa584e0db9fef035209"
@@ -11737,6 +11893,14 @@ wrappy@1, wrappy@~1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
 
+write-file-atomic@^1.2.0:
+  version "1.3.4"
+  resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-1.3.4.tgz#f807a4f0b1d9e913ae7a48112e6cc3af1991b45f"
+  dependencies:
+    graceful-fs "^4.1.11"
+    imurmurhash "^0.1.4"
+    slide "^1.1.5"
+
 write-file-atomic@^2.0.0, write-file-atomic@^2.3.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.3.0.tgz#1ff61575c2e2a4e8e510d6fa4e243cce183999ab"