diff --git a/docs/developers-guide.md b/docs/developers-guide.md
index 75d60d761c015e8c03e1a122a5d0d8114c2219ca..64958e5cfc3df9b13f9bf64769df6d3e81792d98 100644
--- a/docs/developers-guide.md
+++ b/docs/developers-guide.md
@@ -14,6 +14,11 @@ For significant feature additions, it is expected that discussion will have take
 
 We don't like getting sued, so before merging any pull request, we'll need each person contributing code to sign a Contributor License Agreement [here](https://docs.google.com/a/metabase.com/forms/d/1oV38o7b9ONFSwuzwmERRMi9SYrhYeOrkbmNaq9pOJ_E/viewform)
 
+# Development on Windows
+
+The development scripts are designed for Linux/Mac environment, so we recommend using the latest Windows 10 version with [WSL (Windows Subsystem for Linux)](https://msdn.microsoft.com/en-us/commandline/wsl/about) and [Ubuntu on Windows](https://www.microsoft.com/store/p/ubuntu/9nblggh4msv6). The Ubuntu Bash shell works well for both backend and frontend development.
+
+If you have problems with your development environment, make sure that you are not using any development commands outside the Bash shell. As an example, Node dependencies installed in normal Windows environment will not work inside Ubuntu Bash environment.
 
 # Install Prerequisites
 
@@ -24,6 +29,7 @@ These are the set of tools which are required in order to complete any build of
 3. [Yarn package manager for Node.js](https://yarnpkg.com/)
 3. [Leiningen (http://leiningen.org/)](http://leiningen.org/)
 
+If you are developing on Windows, make sure to use Ubuntu on Windows and follow instructions for Ubuntu/Linux instead of installing ordinary Windows versions.
 
 # Build Metabase
 
@@ -70,13 +76,6 @@ Start the frontend build process with
 
     yarn run build-hot
 
-Caveat - Yarn does not properly support `build-hot` on Windows 8/10. You will need to manually build the frontend client with
-    
-    yarn run build
-
-This will get you a full development server running on port :3000 by default.
-
-
 ## Frontend development
 We use these technologies for our FE build process to allow us to use modules, es6 syntax, and css variables.
 
@@ -132,7 +131,7 @@ The way integration tests are written is a little unconventional so here is an e
 
 ```
 import {
-    login,
+    useSharedAdminLogin,
     createTestStore,
 } from "__support__/integrated_tests";
 import {
@@ -149,8 +148,7 @@ describe("Query builder", () => {
     beforeAll(async () => {
         // Usually you want to test stuff where user is already logged in
         // so it is convenient to login before any test case.
-        // Remember `await` here!
-        await login()
+        useSharedAdminLogin()
     })
 
     it("should let you run a new query", async () => {
diff --git a/frontend/src/metabase/components/AddButton.jsx b/frontend/src/metabase/components/AddButton.jsx
deleted file mode 100644
index 7bcb4b0e4e01c1a20f14d9b0e6e1cf6cc8a8b01f..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/components/AddButton.jsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import React from "react";
-import IconBorder from "metabase/components/IconBorder";
-import Icon from "metabase/components/Icon";
-
-const AddButton = ({ text, onClick, targetRefName }) => {
-    const addIcon =
-        <IconBorder borderRadius="3px" ref={targetRefName}>
-            <Icon name="add" size={14}/>
-        </IconBorder>;
-
-    let className = "AddButton text-grey-2 text-bold flex align-center text-grey-4-hover cursor-pointer no-decoration transition-color";
-    if (onClick) {
-        return (
-            <a className={className} onClick={onClick}>
-                { text && <span className="mr1">{text}</span> }
-                { addIcon }
-            </a>
-        );
-    } else {
-        return (
-            <span className={className}>
-                    { text && <span className="mr1">{text}</span> }
-                { addIcon }
-                </span>
-        );
-    }
-};
-
-export default AddButton;
diff --git a/frontend/src/metabase/components/AdminAwareEmptyState.jsx b/frontend/src/metabase/components/AdminAwareEmptyState.jsx
index 6567dbcc5f5a1e6fa0059f6e252a7ebeffa341a2..073205bfbc827c6c67c2bb32a6281331cde56eb0 100644
--- a/frontend/src/metabase/components/AdminAwareEmptyState.jsx
+++ b/frontend/src/metabase/components/AdminAwareEmptyState.jsx
@@ -1,32 +1,45 @@
-import React from "react";
+import React, { Component } from "react";
 import EmptyState from "metabase/components/EmptyState.jsx";
+import { getUser } from "metabase/selectors/user";
+import { connect } from "react-redux";
+
 /*
  * AdminAwareEmptyState is a component that can
  *  1) Produce a custom message for admins in empty results
  */
 
-const AdminAwareEmptyState = ({user, title, message, adminMessage, icon, image, imageHeight, imageClassName, action, adminAction, link, adminLink, onActionClick, smallDescription = false}) =>
-         <EmptyState 
-            title={title}
-            message={user && user.is_superuser ?
-                adminMessage || message :
-                message
-            }
-            icon={icon}
-            image={image}
-            action={user && user.is_superuser ?
-                adminAction || action :
-                action
-            }
-            link={user && user.is_superuser ?
-                adminLink || link :
-                link
-            }
-            imageHeight={imageHeight}
-            imageClassName={imageClassName}
-            onActionClick={onActionClick}
-            smallDescription={smallDescription}
-        />
+const mapStateToProps = (state, props) => ({
+    user: getUser(state, props),
+});
 
+@connect(mapStateToProps, null)
+class AdminAwareEmptyState extends Component {
+    render() {
+        const {user, title, message, adminMessage, icon, image, imageHeight, imageClassName, action, adminAction, link, adminLink, onActionClick, smallDescription = false} = this.props;
+         return (
+             <EmptyState
+                title={title}
+                message={user && user.is_superuser ?
+                    adminMessage || message :
+                    message
+                }
+                icon={icon}
+                image={image}
+                action={user && user.is_superuser ?
+                    adminAction || action :
+                    action
+                }
+                link={user && user.is_superuser ?
+                    adminLink || link :
+                    link
+                }
+                imageHeight={imageHeight}
+                imageClassName={imageClassName}
+                onActionClick={onActionClick}
+                smallDescription={smallDescription}
+            />
+         )
+    }
+}
 
 export default AdminAwareEmptyState;
\ No newline at end of file
diff --git a/frontend/src/metabase/components/ConstrainToScreen.jsx b/frontend/src/metabase/components/ConstrainToScreen.jsx
deleted file mode 100644
index aee7690bd1872e23a3a615809aab9ad2efa66d9b..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/components/ConstrainToScreen.jsx
+++ /dev/null
@@ -1,37 +0,0 @@
-/* @flow */
-
-import React, { Component } from "react";
-import ReactDOM from "react-dom";
-
-import { constrainToScreen } from "metabase/lib/dom";
-
-type Props = {
-    directions: Array<"top"|"bottom">,
-    padding: number,
-    children: React$Element<any>
-};
-
-export default class ConstrainToScreen extends Component {
-    props: Props;
-
-    static defaultProps = {
-        directions: ["top", "bottom"],
-        padding: 10
-    }
-
-    componentDidMount() {
-        this.componentDidUpdate();
-    }
-
-    componentDidUpdate() {
-        const { directions, padding } = this.props;
-        const element = ReactDOM.findDOMNode(this);
-        for (const direction of directions) {
-            constrainToScreen(element, direction, padding);
-        }
-    }
-
-    render() {
-        return React.Children.only(this.props.children);
-    }
-}
diff --git a/frontend/src/metabase/components/DeleteQuestionModal.jsx b/frontend/src/metabase/components/DeleteQuestionModal.jsx
deleted file mode 100644
index 510693b1fa80724a1a6b673a548934652c1737d1..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/components/DeleteQuestionModal.jsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import React, { Component } from "react";
-import PropTypes from "prop-types";
-
-import ModalContent from "metabase/components/ModalContent.jsx";
-
-import inflection from "inflection";
-
-export default class DeleteQuestionModal extends Component {
-    constructor(props, context) {
-        super(props, context);
-        this.state = {
-            error: null
-        };
-    }
-
-    static propTypes = {
-        card: PropTypes.object.isRequired,
-        deleteCardFn: PropTypes.func.isRequired,
-        onClose: PropTypes.func
-    };
-
-    async deleteCard() {
-        try {
-            await this.props.deleteCardFn(this.props.card);
-        } catch (error) {
-            this.setState({ error });
-        }
-    }
-
-    render() {
-        var formError;
-        if (this.state.error) {
-            var errorMessage = "Server error encountered";
-            if (this.state.error.data &&
-                this.state.error.data.message) {
-                errorMessage = this.error.errors.data.message;
-            }
-
-            // TODO: timeout display?
-            formError = (
-                <span className="text-error px2">{errorMessage}</span>
-            );
-        }
-
-        var dashboardCount = this.props.card.dashboard_count + " " + inflection.inflect("dashboard", this.props.card.dashboard_count);
-
-        return (
-            <ModalContent
-                title="Delete Question"
-                onClose={this.props.onClose}
-            >
-                <div className="Form-inputs mb4">
-                    <p>Are you sure you want to do this?</p>
-                    { this.props.card.dashboard_count > 0 ?
-                        <p>This question will be deleted from Metabase, and will also be removed from {dashboardCount}.</p>
-                    : null }
-                </div>
-
-                <div className="Form-actions">
-                    <button className="Button Button--danger" onClick={() => this.deleteCard()}>Yes</button>
-                    <button className="Button Button--primary ml1" onClick={this.props.onClose}>No</button>
-                    {formError}
-                </div>
-            </ModalContent>
-        );
-    }
-}
diff --git a/frontend/src/metabase/components/Sidebar.jsx b/frontend/src/metabase/components/Sidebar.jsx
deleted file mode 100644
index 2d5d89794c76150df5bde3fa377a28d7e3fbec56..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/components/Sidebar.jsx
+++ /dev/null
@@ -1,77 +0,0 @@
-/* eslint "react/prop-types": "warn" */
-import React from "react";
-import PropTypes from "prop-types";
-import { Link } from "react-router";
-import S from "./Sidebar.css";
-
-import Breadcrumbs from "./Breadcrumbs.jsx";
-
-import LabelIcon from "./LabelIcon.jsx";
-
-import cx from 'classnames';
-import pure from "recompose/pure";
-
-const Sidebar = ({
-    sections,
-    labels,
-    breadcrumbs,
-    labelsLoading,
-    labelsError,
-    style,
-    className
-}) =>
-    <div className={cx(S.sidebar, className)} style={style}>
-        <ul>
-            <div className={S.breadcrumbs}>
-                <Breadcrumbs
-                    className="py4"
-                    crumbs={breadcrumbs}
-                    inSidebar={true}
-                    placeholder="Data Reference"
-                />
-            </div>
-            {Object.values(sections)
-                .filter(section => !section.hidden)
-                .map(section =>
-                    <SidebarItem key={section.id} href={section.id} {...section} />
-                )
-            }
-        </ul>
-    </div>
-
-Sidebar.propTypes = {
-    className:      PropTypes.string,
-    style:          PropTypes.object,
-    breadcrumbs:    PropTypes.array,
-    sections:       PropTypes.object.isRequired,
-    labels:         PropTypes.array,
-    labelsLoading:  PropTypes.bool,
-    labelsError:    PropTypes.any,
-};
-
-const SidebarSectionTitle = ({ name, href }) =>
-    <li>
-        <Link to={href} className={S.sectionTitle} activeClassName={S.selected}>{name}</Link>
-    </li>
-
-SidebarSectionTitle.propTypes = {
-    name:  PropTypes.string.isRequired,
-    href:  PropTypes.string.isRequired,
-};
-
-const SidebarItem = ({ name, sidebar, icon, href }) =>
-    <li>
-        <Link to={href} className={S.item} activeClassName={S.selected}>
-            <LabelIcon className={S.icon} icon={icon}/>
-            <span className={S.name}>{sidebar || name}</span>
-        </Link>
-    </li>
-
-SidebarItem.propTypes = {
-    name:  PropTypes.string.isRequired,
-    sidebar:  PropTypes.string,
-    icon:  PropTypes.string.isRequired,
-    href:  PropTypes.string.isRequired,
-};
-
-export default pure(Sidebar);
diff --git a/frontend/src/metabase/css/core/rounded.css b/frontend/src/metabase/css/core/rounded.css
index 7a49794c7255b4f326248535dd5fa1fc15b7c781..3d27f218a99693f0b7ac25c85682c36f9b647dff 100644
--- a/frontend/src/metabase/css/core/rounded.css
+++ b/frontend/src/metabase/css/core/rounded.css
@@ -6,10 +6,6 @@
   border-radius: var(--default-border-radius);
 }
 
-.rounded-med, :local(.rounded-med) {
-  border-radius: var(--med-border-radius);
-}
-
 .rounded-top {
   border-top-left-radius:  var(--default-border-radius);
   border-top-right-radius: var(--default-border-radius);
diff --git a/frontend/src/metabase/css/query_builder.css b/frontend/src/metabase/css/query_builder.css
index 7162c0e97cfb780683ab9775b41785436fdd00a5..a19fa4d669c36ec8d5aa052b26504e5f28ced3a7 100644
--- a/frontend/src/metabase/css/query_builder.css
+++ b/frontend/src/metabase/css/query_builder.css
@@ -87,7 +87,7 @@
 }
 
 .Query-filter.selected {
-    border-color: var(--purple-light-color);
+    border-color: var(--purple-color);
 }
 
 .Filter-section {
diff --git a/frontend/src/metabase/new_query/containers/NewQueryOptions.jsx b/frontend/src/metabase/new_query/containers/NewQueryOptions.jsx
index 32e3b2db47bb2d9ebdd1ef5e050b639a17c20ad2..1a7ad341c7346bbfd5340a934fcc212d8cfd4de0 100644
--- a/frontend/src/metabase/new_query/containers/NewQueryOptions.jsx
+++ b/frontend/src/metabase/new_query/containers/NewQueryOptions.jsx
@@ -8,7 +8,7 @@ import {
 } from 'metabase/redux/metadata'
 
 import { withBackground } from 'metabase/hoc/Background'
-import { resetQuery } from '../new_query'
+import { determineWhichOptionsToShow, resetQuery } from '../new_query'
 
 import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
 import StructuredQuery from "metabase-lib/lib/queries/StructuredQuery"
@@ -16,19 +16,22 @@ import Metadata from "metabase-lib/lib/metadata/Metadata";
 import { getMetadata, getMetadataFetched } from "metabase/selectors/metadata";
 import NewQueryOption from "metabase/new_query/components/NewQueryOption";
 import NativeQuery from "metabase-lib/lib/queries/NativeQuery";
-import { getCurrentQuery, getPlainNativeQuery } from "metabase/new_query/selectors";
+import { getCurrentQuery, getNewQueryOptions, getPlainNativeQuery } from "metabase/new_query/selectors";
 import { getUserIsAdmin } from "metabase/selectors/user";
 import { push } from "react-router-redux";
+import NoDatabasesEmptyState from "metabase/reference/databases/NoDatabasesEmptyState";
 
 const mapStateToProps = state => ({
     query: getCurrentQuery(state),
     plainNativeQuery: getPlainNativeQuery(state),
     metadata: getMetadata(state),
     metadataFetched: getMetadataFetched(state),
-    isAdmin: getUserIsAdmin(state)
+    isAdmin: getUserIsAdmin(state),
+    newQueryOptions: getNewQueryOptions(state)
 })
 
 const mapDispatchToProps = {
+    determineWhichOptionsToShow,
     fetchDatabases,
     fetchMetrics,
     fetchSegments,
@@ -49,51 +52,40 @@ type Props = {
     isAdmin: boolean,
 
     resetQuery: () => void,
+    determineWhichOptionsToShow: () => void,
 
     fetchDatabases: () => void,
     fetchMetrics: () => void,
     fetchSegments: () => void,
 }
 
+const allOptionsVisibleState = {
+    loaded: true,
+    hasDatabases: true,
+    showMetricOption: true,
+    showTableOption: true,
+    showSQLOption: true
+}
+
 export class NewQueryOptions extends Component {
     props: Props
 
-    state = {
-        showMetricOption: false,
-        showSegmentOption: false,
-        showSQLOption: false
-    }
-
-    determinePaths () {
-        const { isAdmin, metadata, push } = this.props
-        const showMetricOption = isAdmin || metadata.metricsList().length > 0
-        const showSegmentOption = isAdmin || metadata.segmentsList().length > 0
-
-        // util to check if the user has write permission to a db
-        const hasSQLPermission = (db) => db.native_permissions === "write"
-
-        // to be able to use SQL the user must have write permissions on at least one db
-        const showSQLOption = isAdmin || metadata.databasesList().filter(hasSQLPermission).length > 0
+    constructor(props) {
+        super(props)
 
-        // if we can only show one option then we should just redirect
-        if(!showMetricOption && !showSQLOption && !showSegmentOption) {
-            push(this.getGuiQueryUrl())
+        // By default, show all options instantly to admins
+        this.state = props.isAdmin ? allOptionsVisibleState : {
+            loaded: false,
+            hasDatabases: false,
+            showMetricOption: false,
+            showTableOption: false,
+            showSQLOption: false
         }
-
-        this.setState({
-            showMetricOption,
-            showSegmentOption,
-            showSQLOption,
-        })
     }
 
     async componentWillMount() {
-        await this.props.fetchDatabases()
-        await this.props.fetchMetrics()
-        await this.props.fetchSegments()
-        await this.props.resetQuery();
-
-        this.determinePaths()
+        this.props.resetQuery();
+        this.props.determineWhichOptionsToShow(this.getGuiQueryUrl);
     }
 
     getGuiQueryUrl = () => {
@@ -105,20 +97,28 @@ export class NewQueryOptions extends Component {
     }
 
     render() {
-        const { query, metadataFetched, isAdmin, metricSearchUrl } = this.props
-        const { showMetricOption, showSQLOption } = this.state
+        const { isAdmin, metricSearchUrl, newQueryOptions } = this.props
+        const { loaded, hasDatabases, showMetricOption, showSQLOption } = newQueryOptions
         const showCustomInsteadOfNewQuestionText = showMetricOption || isAdmin
 
-        if (!query || (!isAdmin && (!metadataFetched.metrics || !metadataFetched.segments))) {
+        if (!loaded) {
             return <LoadingAndErrorWrapper loading={true}/>
         }
 
+        if (!hasDatabases) {
+            return (
+                <div className="full-height flex align-center justify-center">
+                    <NoDatabasesEmptyState/>
+                </div>
+            )
+        }
+
         return (
             <div className="full-height flex">
                 <div className="wrapper wrapper--trim lg-wrapper--trim xl-wrapper--trim flex-full px1 mt4 mb2 align-center">
                      <div className="flex align-center justify-center" style={{minHeight: "100%"}}>
                         <ol className="flex-full Grid Grid--guttersXl Grid--full sm-Grid--normal">
-                            { (showMetricOption || isAdmin) &&
+                            { showMetricOption &&
                                 <li className="Grid-cell">
                                     <NewQueryOption
                                         image="/app/img/questions_illustration"
@@ -138,7 +138,7 @@ export class NewQueryOptions extends Component {
                                     to={this.getGuiQueryUrl}
                                 />
                             </li>
-                            { (showSQLOption || isAdmin) &&
+                            { showSQLOption &&
                                 <li className="Grid-cell">
                                     <NewQueryOption
                                         image="/app/img/sql_illustration"
diff --git a/frontend/src/metabase/new_query/new_query.js b/frontend/src/metabase/new_query/new_query.js
index bb911dde0bb581ebfcc9faabd83524befc833d31..3b30a4cb4e4757be8534ea108f0b0fff2b634396 100644
--- a/frontend/src/metabase/new_query/new_query.js
+++ b/frontend/src/metabase/new_query/new_query.js
@@ -4,8 +4,17 @@
  */
 
 import { handleActions, combineReducers } from "metabase/lib/redux";
+import {
+    fetchDatabases,
+    fetchMetrics,
+    fetchSegments,
+} from 'metabase/redux/metadata'
+
 import { STRUCTURED_QUERY_TEMPLATE } from "metabase-lib/lib/queries/StructuredQuery";
 import type { DatasetQuery } from "metabase/meta/types/Card";
+import { getMetadata } from "metabase/selectors/metadata";
+import { getUserIsAdmin } from "metabase/selectors/user";
+import { push } from "react-router-redux";
 
 /**
  * Initializes the new query flow for a given question
@@ -17,14 +26,88 @@ export function resetQuery() {
     }
 }
 
+const newQueryOptionsDefault = {
+    loaded: false,
+    hasDatabases: false,
+    showMetricOption: false,
+    showTableOption: false,
+    showSQLOption: false
+}
+
+const newQueryOptionsAllVisible = {
+    loaded: true,
+    hasDatabases: true,
+    showMetricOption: true,
+    showTableOption: true,
+    showSQLOption: true
+}
+
+export const DETERMINE_OPTIONS_STARTED = "metabase/new_query/DETERMINE_OPTIONS_STARTED"
+export const DETERMINE_OPTIONS = "metabase/new_query/DETERMINE_OPTIONS"
+export function determineWhichOptionsToShow(getGuiQueryUrl) {
+    return async (dispatch, getState) => {
+        // By default, show all options instantly to admins
+        const isAdmin = getUserIsAdmin(getState())
+        dispatch.action(DETERMINE_OPTIONS_STARTED, isAdmin ? newQueryOptionsAllVisible : {
+            loaded: false,
+            hasDatabases: false,
+            showMetricOption: false,
+            showTableOption: false,
+            showSQLOption: false
+        })
+
+        await Promise.all([
+            dispatch(fetchDatabases()),
+            dispatch(fetchMetrics()),
+            dispatch(fetchSegments())
+        ])
+
+        const metadata = getMetadata(getState())
+        const hasDatabases = metadata.databasesList().length > 0
+
+        if (!hasDatabases) {
+            return dispatch.action(DETERMINE_OPTIONS, { loaded: true, hasDatabases: false })
+        } else if (isAdmin) {
+            return dispatch.action(DETERMINE_OPTIONS, newQueryOptionsAllVisible)
+        } else {
+            const showMetricOption = metadata.metricsList().length > 0
+
+            // to be able to use SQL the user must have write permissions on at least one db
+            const hasSQLPermission = (db) => db.native_permissions === "write"
+            const showSQLOption = metadata.databasesList().filter(hasSQLPermission).length > 0
+
+            // if we can only show one option then we should just redirect
+            const redirectToQueryBuilder =
+                !showMetricOption && !showSQLOption
+
+            if (redirectToQueryBuilder) {
+                dispatch(push(getGuiQueryUrl()))
+            } else {
+                return dispatch.action(DETERMINE_OPTIONS, {
+                    loaded: true,
+                    hasDatabases: true,
+                    showMetricOption,
+                    showSQLOption,
+                })
+            }
+        }
+    }
+}
+
 /**
  * The current query that we are creating
  */
-// something like const query = handleActions<DatasetQuery>({
+
+const newQueryOptions = handleActions({
+    [DETERMINE_OPTIONS_STARTED]: (state, { payload }): DatasetQuery => payload,
+    [DETERMINE_OPTIONS]: (state, { payload }): DatasetQuery => payload
+}, newQueryOptionsDefault)
+
 const datasetQuery = handleActions({
     [RESET_QUERY]: (state, { payload }): DatasetQuery => payload,
 }, STRUCTURED_QUERY_TEMPLATE);
 
 export default combineReducers({
+    newQueryOptions,
     datasetQuery
 });
diff --git a/frontend/src/metabase/new_query/selectors.js b/frontend/src/metabase/new_query/selectors.js
index 7b82948e8cfb42e153187ed2568678d2fa4b88ac..56e7cb40d83e96930b12067e5dbaca8b1a9d098b 100644
--- a/frontend/src/metabase/new_query/selectors.js
+++ b/frontend/src/metabase/new_query/selectors.js
@@ -27,5 +27,7 @@ export const getPlainNativeQuery = state => {
     } else {
         return new NativeQuery(question)
     }
-
 }
+
+export const getNewQueryOptions = state =>
+    state.new_query.newQueryOptions
diff --git a/frontend/src/metabase/query_builder/components/CardFiltersWidget.js b/frontend/src/metabase/query_builder/components/CardFiltersWidget.js
deleted file mode 100644
index 380918fe9ad873d9a5851a3327035a5eaf1c292b..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/query_builder/components/CardFiltersWidget.js
+++ /dev/null
@@ -1,36 +0,0 @@
-import React, { Component } from "react";
-
-import FilterList from './filters/FilterList.jsx';
-
-import Query from "metabase/lib/query";
-
-import type { DatasetQuery } from "metabase/meta/types/Card";
-import type { TableMetadata } from "metabase/meta/types/Metadata";
-import type { Filter } from "metabase/meta/types/Query";
-
-export default class CardFiltersWidget extends Component {
-    props: {
-        datasetQuery: DatasetQuery,
-        tableMetadata: TableMetadata,
-        removeQueryFilter: (index: number) => void,
-        updateQueryFilter: (index: number, filter: Filter) => void,
-        addQueryFilter: () => void
-    };
-
-    render() {
-        if (this.props.tableMetadata) {
-            let filters = Query.getFilters(this.props.datasetQuery.query);
-            if (filters && filters.length > 0) {
-                return (
-                    <FilterList
-                        filters={filters}
-                        tableMetadata={this.props.tableMetadata}
-                        updateFilter={this.props.updateQueryFilter}
-                    />
-                );
-            }
-        }
-
-        return null;
-    }
-}
diff --git a/frontend/src/metabase/reference/databases/DatabaseList.jsx b/frontend/src/metabase/reference/databases/DatabaseList.jsx
index 1aa94382027e87d23bcddc92e4da7e7fae13a868..8d5e4bc78a8f72704ac1afa9f4cf192273e2e0d6 100644
--- a/frontend/src/metabase/reference/databases/DatabaseList.jsx
+++ b/frontend/src/metabase/reference/databases/DatabaseList.jsx
@@ -9,7 +9,6 @@ import S from "metabase/components/List.css";
 
 import List from "metabase/components/List.jsx";
 import ListItem from "metabase/components/ListItem.jsx";
-import AdminAwareEmptyState from "metabase/components/AdminAwareEmptyState.jsx";
 
 import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper.jsx";
 
@@ -22,15 +21,7 @@ import {
 } from "../selectors";
 
 import * as metadataActions from "metabase/redux/metadata";
-
-const emptyStateData = {
-    title: "Metabase is no fun without any data",
-    adminMessage: "Your databases will appear here once you connect one",
-    message: "Databases will appear here once your admins have added some",
-    image: "app/assets/img/databases-list",
-    adminAction: "Connect a database",
-    adminLink: "/admin/databases/create"
-}
+import NoDatabasesEmptyState from "metabase/reference/databases/NoDatabasesEmptyState";
 
 const mapStateToProps = (state, props) => ({
     entities: getDatabases(state, props),
@@ -88,7 +79,7 @@ export default class DatabaseList extends Component {
                     </div>
                     :
                     <div className={S.empty}>
-                        <AdminAwareEmptyState {...emptyStateData}/>
+                        <NoDatabasesEmptyState />
                     </div>
                 }
                 </LoadingAndErrorWrapper>
diff --git a/frontend/src/metabase/reference/databases/NoDatabasesEmptyState.jsx b/frontend/src/metabase/reference/databases/NoDatabasesEmptyState.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..3d43a056193c513663efd7405ea3f8caca8d8419
--- /dev/null
+++ b/frontend/src/metabase/reference/databases/NoDatabasesEmptyState.jsx
@@ -0,0 +1,15 @@
+import * as React from "react";
+import AdminAwareEmptyState from "metabase/components/AdminAwareEmptyState";
+
+const NoDatabasesEmptyState = (user) =>
+    <AdminAwareEmptyState
+        title={"Metabase is no fun without any data"}
+        adminMessage={"Your databases will appear here once you connect one"}
+        message={"Databases will appear here once your admins have added some"}
+        image={"app/assets/img/databases-list"}
+        adminAction={"Connect a database"}
+        adminLink={"/admin/databases/create"}
+        user={user}
+    />
+
+export default NoDatabasesEmptyState
\ No newline at end of file
diff --git a/frontend/src/metabase/reference/metrics/MetricList.jsx b/frontend/src/metabase/reference/metrics/MetricList.jsx
index c19a6ecbcd3536de35eda2946b3f8dd6dbef13b3..b61989b147b1b6fa10900dbb28ff4a5ee7feb329 100644
--- a/frontend/src/metabase/reference/metrics/MetricList.jsx
+++ b/frontend/src/metabase/reference/metrics/MetricList.jsx
@@ -30,7 +30,7 @@ const emptyStateData = {
     message: "Metrics will appear here once your admins have created some",
     image: "app/assets/img/metrics-list",
     adminAction: "Learn how to create metrics",
-    adminLink: "http://www.metabase.com/docs/latest/administration-guide/06-segments-and-metrics.html"
+    adminLink: "http://www.metabase.com/docs/latest/administration-guide/07-segments-and-metrics.html"
 }
 
 const mapStateToProps = (state, props) => ({
diff --git a/frontend/src/metabase/reference/segments/SegmentList.jsx b/frontend/src/metabase/reference/segments/SegmentList.jsx
index 6ba991140df7fee579b662fc0440ab1858be8aa0..15859838279199d286cfadc398bcbba47e77fc35 100644
--- a/frontend/src/metabase/reference/segments/SegmentList.jsx
+++ b/frontend/src/metabase/reference/segments/SegmentList.jsx
@@ -29,7 +29,7 @@ const emptyStateData = {
             message: "Segments will appear here once your admins have created some",
             image: "app/assets/img/segments-list",
             adminAction: "Learn how to create segments",
-            adminLink: "http://www.metabase.com/docs/latest/administration-guide/06-segments-and-metrics.html"
+            adminLink: "http://www.metabase.com/docs/latest/administration-guide/07-segments-and-metrics.html"
         }
 
 const mapStateToProps = (state, props) => ({
diff --git a/frontend/src/metabase/routes.jsx b/frontend/src/metabase/routes.jsx
index d41bcaddb8669ab9101c1b44076ba4a81103cd2a..207b3e3769858e3ba554fd4c72d37ab20d7a14bc 100644
--- a/frontend/src/metabase/routes.jsx
+++ b/frontend/src/metabase/routes.jsx
@@ -258,14 +258,8 @@ export const getRoutes = (store) =>
                     <Route path="table/:tableId/:cost" component={TableXRay} />
                     <Route path="field/:fieldId/:cost" component={FieldXRay} />
                     <Route path="card/:cardId/:cost" component={CardXRay} />
-                    <Route path="compare" title="Compare">
-                        <Route path="segments/:segmentId1/:segmentId2">
-                            <Route path=":cost" component={SegmentComparison} />
-                        </Route>
-                        <Route path="segment/:segmentId/table/:tableId">
-                            <Route path=":cost" component={SegmentTableComparison} />
-                        </Route>
-                    </Route>
+                    <Route path="compare/segments/:segmentId1/:segmentId2/:cost" component={SegmentComparison} />
+                    <Route path="compare/segment/:segmentId/table/:tableId/:cost" component={SegmentTableComparison} />
                 </Route>
 
                 {/* PULSE */}
diff --git a/frontend/src/metabase/xray/SimpleStat.jsx b/frontend/src/metabase/xray/SimpleStat.jsx
index e291a7ffb814876e45b365d9f7da9b7c84849672..bc17543e5d378269516b510fa4c78a6c521d72a4 100644
--- a/frontend/src/metabase/xray/SimpleStat.jsx
+++ b/frontend/src/metabase/xray/SimpleStat.jsx
@@ -14,7 +14,7 @@ const SimpleStat = ({ stat, showDescription }) =>
         </div>
         { /* call toString to ensure that values like true / false show up */ }
         <h1 className="my1">
-            {stat.value.toString()}
+            {stat.value ? stat.value.toString() : "No value"}
         </h1>
     </div>
 
diff --git a/frontend/src/metabase/xray/components/XrayFieldComparison.jsx b/frontend/src/metabase/xray/components/XrayFieldComparison.jsx
deleted file mode 100644
index 1fcf0b95b9de6405f33ba0a50d076082c564850b..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/xray/components/XrayFieldComparison.jsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import React from 'react'
-
-import ItemLink from 'metabase/xray/components/ItemLink'
-import ComparisonHeader from 'metabase/xray/components/ComparisonHeader'
-
-import { XRayPageWrapper } from 'metabase/xray/components/XRayLayout'
-
-const XRayFieldComparison = ({
-    itemA,
-    itemB,
-    cost
-}) =>
-    <XRayPageWrapper>
-        <ComparisonHeader cost={cost} />
-        <div className="flex">
-            <ItemLink
-                item={itemA}
-                link=''
-            />
-            <ItemLink
-                item={itemB}
-                link=''
-            />
-        </div>
-    </XRayPageWrapper>
-
-export default XRayFieldComparison
diff --git a/frontend/test/__runner__/run_integrated_tests.js b/frontend/test/__runner__/run_integrated_tests.js
index ddaf6ee356e2187a08694c93a8a7ca8552e84c9b..0851a9e3977754471bb6110c064457dac4a01b21 100755
--- a/frontend/test/__runner__/run_integrated_tests.js
+++ b/frontend/test/__runner__/run_integrated_tests.js
@@ -10,7 +10,6 @@ import chalk from "chalk";
 const BackendResource = require("./backend.js").BackendResource
 
 // Backend that uses a test fixture database
-// If you need to update the fixture, you can run Metabase with `MB_DB_TYPE=h2 MB_DB_FILE=frontend/test/legacy-selenium/support/fixtures/metabase.db`
 const serverWithTestDbFixture = BackendResource.get({});
 const testFixtureBackendHost = serverWithTestDbFixture.host;
 
@@ -29,14 +28,14 @@ function readFile(fileName) {
     });
 }
 
-const login = async (apiHost) => {
+const login = async (apiHost, user) => {
     const loginFetchOptions = {
         method: "POST",
         headers: new Headers({
             "Accept": "application/json",
             "Content-Type": "application/json"
         }),
-        body: JSON.stringify({ username: "bob@metabase.com", password: "12341234"})
+        body: JSON.stringify(user)
     };
     const result = await fetch(apiHost + "/api/session", loginFetchOptions);
 
@@ -79,14 +78,22 @@ const init = async() => {
     await BackendResource.start(serverWithPlainDb)
 
     console.log(chalk.bold('3/4 Creating a shared login session for backend 1'));
-    const sharedLoginSession = await login(testFixtureBackendHost)
+    const sharedAdminLoginSession = await login(
+        testFixtureBackendHost,
+        { username: "bob@metabase.com", password: "12341234" }
+    )
+    const sharedNormalLoginSession = await login(
+        testFixtureBackendHost,
+        { username: "robert@metabase.com", password: "12341234" }
+    )
 
     console.log(chalk.bold('4/4 Starting Jest'));
     const env = {
         ...process.env,
         "TEST_FIXTURE_BACKEND_HOST": testFixtureBackendHost,
         "PLAIN_BACKEND_HOST": plainBackendHost,
-        "TEST_FIXTURE_SHARED_LOGIN_SESSION_ID": sharedLoginSession.id
+        "TEST_FIXTURE_SHARED_ADMIN_LOGIN_SESSION_ID": sharedAdminLoginSession.id,
+        "TEST_FIXTURE_SHARED_NORMAL_LOGIN_SESSION_ID": sharedNormalLoginSession.id,
     }
 
     const jestProcess = spawn(
diff --git a/frontend/test/__runner__/test_db_fixture.db.h2.db b/frontend/test/__runner__/test_db_fixture.db.h2.db
index 2d2c551db50a10d1335aeb2017ba9fcab5a69d34..0f5a62a12ae1abc3e7ddb10c3fff7d325b500177 100644
Binary files a/frontend/test/__runner__/test_db_fixture.db.h2.db and b/frontend/test/__runner__/test_db_fixture.db.h2.db differ
diff --git a/frontend/test/__support__/integrated_tests.js b/frontend/test/__support__/integrated_tests.js
index fa936d68c4ec545fa63f007f98bd42698aff5366..30be032a9020a4868a038dd7e940c47dc57df83c 100644
--- a/frontend/test/__support__/integrated_tests.js
+++ b/frontend/test/__support__/integrated_tests.js
@@ -43,23 +43,36 @@ let simulateOfflineMode = false;
 let apiRequestCompletedCallback = null;
 let skippedApiRequests = [];
 
-/**
- * Login to the Metabase test instance with default credentials
- */
-export async function login({ username = "bob@metabase.com", password = "12341234" } = {}) {
-    if (hasStartedCreatingStore) {
+const warnAboutCreatingStoreBeforeLogin = () => {
+    if (!loginSession && hasStartedCreatingStore) {
         console.warn(
-            "Warning: You have created a test store before calling login() which means that up-to-date site settings " +
+            "Warning: You have created a test store before calling logging in which means that up-to-date site settings " +
             "won't be in the store unless you call `refreshSiteSettings` action manually. Please prefer " +
             "logging in before all tests and creating the store inside an individual test or describe block."
         )
     }
+}
+/**
+ * Login to the Metabase test instance with default credentials
+ */
+export async function login({ username = "bob@metabase.com", password = "12341234" } = {}) {
+    warnAboutCreatingStoreBeforeLogin()
+    loginSession = await SessionApi.create({ username, password });
+}
 
-    if (isTestFixtureDatabase() && process.env.TEST_FIXTURE_SHARED_LOGIN_SESSION_ID) {
-        loginSession = { id: process.env.TEST_FIXTURE_SHARED_LOGIN_SESSION_ID }
-    } else {
-        loginSession = await SessionApi.create({ username, password });
-    }
+export function useSharedAdminLogin() {
+    warnAboutCreatingStoreBeforeLogin()
+    loginSession = { id: process.env.TEST_FIXTURE_SHARED_ADMIN_LOGIN_SESSION_ID }
+}
+export function useSharedNormalLogin() {
+    warnAboutCreatingStoreBeforeLogin()
+    loginSession = { id: process.env.TEST_FIXTURE_SHARED_NORMAL_LOGIN_SESSION_ID }
+}
+export const forBothAdminsAndNormalUsers = async (tests) => {
+    useSharedAdminLogin()
+    await tests()
+    useSharedNormalLogin()
+    await tests()
 }
 
 export function logout() {
@@ -353,6 +366,37 @@ export const waitForRequestToComplete = (method, urlRegex, { timeout = 5000 } =
     })
 }
 
+/**
+ * Lets you replace given API endpoints with mocked implementations for the lifetime of a test
+ */
+export async function withApiMocks(mocks, test) {
+    if (!mocks.every(([apiService, endpointName, mockMethod]) =>
+            _.isObject(apiService) && _.isString(endpointName) && _.isFunction(mockMethod)
+        )
+    ) {
+        throw new Error(
+            "Seems that you are calling \`withApiMocks\` with invalid parameters. " +
+            "The calls should be in format \`withApiMocks([[ApiService, endpointName, mockMethod], ...], tests)\`."
+        )
+    }
+
+    const originals = mocks.map(([apiService, endpointName]) => apiService[endpointName])
+
+    // Replace real API endpoints with mocks
+    mocks.forEach(([apiService, endpointName, mockMethod]) => {
+        apiService[endpointName] = mockMethod
+    })
+
+    try {
+        await test();
+    } finally {
+        // Restore original endpoints after tests, even in case of an exception
+        mocks.forEach(([apiService, endpointName], index) => {
+            apiService[endpointName] = originals[index]
+        })
+    }
+}
+
 // Patches the metabase/lib/api module so that all API queries contain the login credential cookie.
 // Needed because we are not in a real web browser environment.
 api._makeRequest = async (method, url, headers, requestBody, data, options) => {
diff --git a/frontend/test/__support__/sample_dataset_fixture.js b/frontend/test/__support__/sample_dataset_fixture.js
index 1f2b221837ab72a836d5be7ac7a5e89b48a5e222..3dbaa2a5f50d7eb4f6249f4731a9f12b20735527 100644
--- a/frontend/test/__support__/sample_dataset_fixture.js
+++ b/frontend/test/__support__/sample_dataset_fixture.js
@@ -1561,14 +1561,14 @@ export const productQuestion = new Question(metadata, product_card);
 const NoFieldsMetadata = getMetadata(assocIn(state, ["metadata", "tables", ORDERS_TABLE_ID, "fields"], []))
 export const questionNoFields = new Question(NoFieldsMetadata, card);
 
-export const orders_past_30_days_segment = {
+export const orders_past_300_days_segment = {
     "id": null,
-    "name": "Past 30 days",
-    "description": "Past 30 days created at",
+    "name": "Past 300 days",
+    "description": "Past 300 days created at",
     "table_id": 1,
     "definition": {
         "source_table": 1,
-        "filter": ["time-interval", ["field-id", 1], -30, "day"]
+        "filter": ["time-interval", ["field-id", 1], -300, "day"]
     }
 };
 
diff --git a/frontend/test/admin/databases/DatabaseEditApp.integ.spec.js b/frontend/test/admin/databases/DatabaseEditApp.integ.spec.js
index 4b2b8c6c5acf341733286885c65142beff93c8cd..683a5007b03f47ef0dc38a6bb39383220a03de65 100644
--- a/frontend/test/admin/databases/DatabaseEditApp.integ.spec.js
+++ b/frontend/test/admin/databases/DatabaseEditApp.integ.spec.js
@@ -1,5 +1,5 @@
 import {
-    login,
+    useSharedAdminLogin,
     createTestStore
 } from "__support__/integrated_tests";
 
@@ -31,7 +31,7 @@ import _ from "underscore";
 // Currently a lot of duplication with SegmentPane tests
 describe("DatabaseEditApp", () => {
     beforeAll(async () => {
-        await login();
+        useSharedAdminLogin();
     })
 
     describe("Connection tab", () => {
diff --git a/frontend/test/admin/databases/DatabaseListApp.integ.spec.js b/frontend/test/admin/databases/DatabaseListApp.integ.spec.js
index 4b458017addb87ea1637fbe649f61671067bb0d4..246efbc177ce730f6ec9fed7314d28b93a941cda 100644
--- a/frontend/test/admin/databases/DatabaseListApp.integ.spec.js
+++ b/frontend/test/admin/databases/DatabaseListApp.integ.spec.js
@@ -1,5 +1,5 @@
 import {
-    login,
+    useSharedAdminLogin,
     createTestStore
 } from "__support__/integrated_tests";
 import {
@@ -38,7 +38,7 @@ import DatabaseSchedulingForm, { SyncOption } from "metabase/admin/databases/com
 describe('dashboard list', () => {
 
     beforeAll(async () => {
-        await login()
+        useSharedAdminLogin()
     })
 
     it('should render', async () => {
diff --git a/frontend/test/admin/datamodel/FieldApp.integ.spec.js b/frontend/test/admin/datamodel/FieldApp.integ.spec.js
index d39765569eb51c5be73ecd282b61aa3e3ea28527..d38778f4c088dbb857a7e03b84a52e38c1309542 100644
--- a/frontend/test/admin/datamodel/FieldApp.integ.spec.js
+++ b/frontend/test/admin/datamodel/FieldApp.integ.spec.js
@@ -1,5 +1,5 @@
 import {
-    login,
+    useSharedAdminLogin,
     createTestStore,
 } from "__support__/integrated_tests";
 
@@ -70,7 +70,7 @@ const initFieldApp = async ({ tableId = 1, fieldId }) => {
 
 describe("FieldApp", () => {
     beforeAll(async () => {
-        await login()
+        useSharedAdminLogin()
     })
 
     describe("name settings", () => {
diff --git a/frontend/test/admin/datamodel/datamodel.integ.spec.js b/frontend/test/admin/datamodel/datamodel.integ.spec.js
index bf9ab6dc76efdbfce49ddd696754d6e1220ca0a6..333c3d00a77ba0a22a30154bb5d31219fdfd0866 100644
--- a/frontend/test/admin/datamodel/datamodel.integ.spec.js
+++ b/frontend/test/admin/datamodel/datamodel.integ.spec.js
@@ -1,6 +1,6 @@
 // Converted from an old Selenium E2E test
 import {
-    login,
+    useSharedAdminLogin,
     createTestStore
 } from "__support__/integrated_tests";
 import {
@@ -35,7 +35,7 @@ import { MetabaseApi } from "metabase/services";
 
 describe("admin/datamodel", () => {
     beforeAll(async () =>
-        await login()
+        useSharedAdminLogin()
     );
 
     describe("data model editor", () => {
diff --git a/frontend/test/admin/people/people.integ.spec.js b/frontend/test/admin/people/people.integ.spec.js
index c949a561f7dc69a7a843cb5ad208341ca4445b2c..9d594f6af8c806b85e97c1edcec6a1de5d4e77aa 100644
--- a/frontend/test/admin/people/people.integ.spec.js
+++ b/frontend/test/admin/people/people.integ.spec.js
@@ -1,7 +1,7 @@
 // Converted from a Selenium E2E test
 import {
     createTestStore,
-    login
+    useSharedAdminLogin
 } from "__support__/integrated_tests";
 import {
     click,
@@ -27,7 +27,7 @@ describe("admin/people", () => {
     let createdUserId = null;
 
     beforeAll(async () => {
-        await login();
+        useSharedAdminLogin();
     })
 
     describe("user management", () => {
diff --git a/frontend/test/admin/settings/SettingsAuthenticationOptions.integ.spec.js b/frontend/test/admin/settings/SettingsAuthenticationOptions.integ.spec.js
index 4e6410577552ca39ae5fa1a32de7e1ccfdf089d3..18e9b7da8127480b55affe179cc997c116e9d57a 100644
--- a/frontend/test/admin/settings/SettingsAuthenticationOptions.integ.spec.js
+++ b/frontend/test/admin/settings/SettingsAuthenticationOptions.integ.spec.js
@@ -1,5 +1,5 @@
 import {
-    login,
+    useSharedAdminLogin,
     createTestStore
 } from "__support__/integrated_tests";
 import { click } from "__support__/enzyme_utils"
@@ -15,7 +15,7 @@ import { INITIALIZE_SETTINGS } from "metabase/admin/settings/settings"
 
 describe('Admin Auth Options', () => {
     beforeAll(async () => {
-        await login()
+        useSharedAdminLogin()
     })
 
     it('it should render the proper configuration form', async () => {
diff --git a/frontend/test/admin/settings/settings.integ.spec.js b/frontend/test/admin/settings/settings.integ.spec.js
index b7be2fba0b908d5af379b3e3e7580c0140cd4925..df4a7b4349ba4b674122bef9aad6e1d7c9f4741c 100644
--- a/frontend/test/admin/settings/settings.integ.spec.js
+++ b/frontend/test/admin/settings/settings.integ.spec.js
@@ -1,6 +1,6 @@
 // Converted from an old Selenium E2E test
 import {
-    login,
+    useSharedAdminLogin,
     createTestStore,
 } from "__support__/integrated_tests";
 import { mount } from "enzyme";
@@ -11,7 +11,7 @@ import { setInputValue } from "__support__/enzyme_utils";
 
 describe("admin/settings", () => {
     beforeAll(async () =>
-        await login()
+        useSharedAdminLogin()
     );
 
     // TODO Atte Keinänen 6/22/17: Disabled because we already have converted this to Jest&Enzyme in other branch
diff --git a/frontend/test/components/Logs.unit.spec.js b/frontend/test/components/Logs.unit.spec.js
index b7c34ad0c0505b45cafe8b08ce31377c2bcdbc78..cdd3b3f42ae6e004ab393264a130321703589b7e 100644
--- a/frontend/test/components/Logs.unit.spec.js
+++ b/frontend/test/components/Logs.unit.spec.js
@@ -1,29 +1,20 @@
 import React from 'react'
 import Logs from '../../src/metabase/components/Logs'
 import { mount } from 'enzyme'
-import sinon from 'sinon'
 
 import { UtilApi } from 'metabase/services'
 
 describe('Logs', () => {
     describe('log fetching', () => {
-        let timer
-
-        beforeEach(() => {
-            timer = sinon.useFakeTimers()
-        })
-
-        afterEach(() => {
-            timer.restore()
-        })
 
         it('should call UtilApi.logs after 1 second', () => {
+            jest.useFakeTimers()
             const wrapper = mount(<Logs />)
-            const utilSpy = sinon.spy(UtilApi, "logs")
+            const utilSpy = jest.spyOn(UtilApi, "logs")
 
             expect(wrapper.state().logs.length).toEqual(0)
-            timer.tick(1001)
-            expect(utilSpy.called).toEqual(true)
+            jest.runTimersToTime(1001)
+            expect(utilSpy).toHaveBeenCalled()
         })
     })
 })
diff --git a/frontend/test/components/StepIndicators.unit.spec.js b/frontend/test/components/StepIndicators.unit.spec.js
index b2f6dd71665bc6f2432b3a88ba8ef88acad1fef5..1f1048d27731df9cc2c88a1d23d2110813df2a01 100644
--- a/frontend/test/components/StepIndicators.unit.spec.js
+++ b/frontend/test/components/StepIndicators.unit.spec.js
@@ -2,7 +2,6 @@ import { click } from "__support__/enzyme_utils";
 
 import React from 'react'
 import { shallow } from 'enzyme'
-import sinon from 'sinon'
 
 import { normal } from 'metabase/lib/colors'
 
@@ -25,14 +24,14 @@ describe('Step indicators', () => {
 
     describe('goToStep', () => {
         it('should call goToStep with the proper number when a step is clicked', () => {
-            const goToStep = sinon.spy()
+            const goToStep = jest.fn()
             const wrapper = shallow(
                 <StepIndicators steps={steps} goToStep={goToStep} currentStep={1} />
             )
 
             const targetIndicator = wrapper.find('li').first()
             click(targetIndicator);
-            expect(goToStep.calledWith(1)).toEqual(true)
+            expect(goToStep).toHaveBeenCalledWith(1)
         })
     })
 })
diff --git a/frontend/test/dashboard/dashboard.integ.spec.js b/frontend/test/dashboard/dashboard.integ.spec.js
index b43f2fc00caa641908dd6a391fb49b738c696e1a..d743e95979d5509496f8df78093f91cb379d3fd5 100644
--- a/frontend/test/dashboard/dashboard.integ.spec.js
+++ b/frontend/test/dashboard/dashboard.integ.spec.js
@@ -1,6 +1,6 @@
 import {
     createTestStore,
-    login
+    useSharedAdminLogin
 } from "__support__/integrated_tests";
 import {
     click, clickButton,
@@ -76,7 +76,7 @@ PublicApi.dashboard = async () => {
 
 describe("Dashboard", () => {
     beforeAll(async () => {
-        await login();
+        useSharedAdminLogin();
     })
 
     describe("redux actions", () => {
diff --git a/frontend/test/dashboards/dashboards.integ.spec.js b/frontend/test/dashboards/dashboards.integ.spec.js
index 6df983d47973ee0dafef2950506b7a115d4e18e9..0a4413a55e587dea67c1515cd9b732d97c8feb19 100644
--- a/frontend/test/dashboards/dashboards.integ.spec.js
+++ b/frontend/test/dashboards/dashboards.integ.spec.js
@@ -1,6 +1,6 @@
 import {
     createTestStore,
-    login
+    useSharedAdminLogin
 } from "__support__/integrated_tests";
 import {
     click,
@@ -22,7 +22,7 @@ import ArchivedItem from "metabase/components/ArchivedItem";
 
 describe("dashboards list", () => {
     beforeAll(async () => {
-        await login();
+        useSharedAdminLogin();
     })
 
     afterAll(async () => {
diff --git a/frontend/test/home/HomepageApp.integ.spec.js b/frontend/test/home/HomepageApp.integ.spec.js
index 3aecd44370ed11f83553f856bdd3f9c21022260f..a0a382443e9c5ba02de589eb23753b9ddb05d92e 100644
--- a/frontend/test/home/HomepageApp.integ.spec.js
+++ b/frontend/test/home/HomepageApp.integ.spec.js
@@ -1,5 +1,5 @@
 import {
-    login,
+    useSharedAdminLogin,
     createTestStore,
     createSavedQuestion
 } from "__support__/integrated_tests";
@@ -8,7 +8,7 @@ import { click } from "__support__/enzyme_utils"
 import React from 'react';
 import { mount } from "enzyme";
 import {
-    orders_past_30_days_segment,
+    orders_past_300_days_segment,
     unsavedOrderCountQuestion,
     vendor_count_metric
 } from "__support__/sample_dataset_fixture";
@@ -30,14 +30,14 @@ describe("HomepageApp", () => {
     let metricId = null;
 
     beforeAll(async () => {
-        await login()
+        useSharedAdminLogin()
 
         // Create some entities that will show up in the top of activity feed
         // This test doesn't care if there already are existing items in the feed or not
         // Delays are required for having separable creation times for each entity
         questionId = (await createSavedQuestion(unsavedOrderCountQuestion)).id()
         await delay(100);
-        segmentId = (await SegmentApi.create(orders_past_30_days_segment)).id;
+        segmentId = (await SegmentApi.create(orders_past_300_days_segment)).id;
         await delay(100);
         metricId = (await MetricApi.create(vendor_count_metric)).id;
         await delay(100);
@@ -67,8 +67,8 @@ describe("HomepageApp", () => {
             expect(activityItems.at(0).text()).toMatch(/Vendor count/);
             expect(activityStories.at(0).text()).toMatch(/Tells how many vendors we have/);
 
-            expect(activityItems.at(1).text()).toMatch(/Past 30 days/);
-            expect(activityStories.at(1).text()).toMatch(/Past 30 days created at/);
+            expect(activityItems.at(1).text()).toMatch(/Past 300 days/);
+            expect(activityStories.at(1).text()).toMatch(/Past 300 days created at/);
 
             // eslint-disable-next-line no-irregular-whitespace
             expect(activityItems.at(2).text()).toMatch(/You saved a question about Orders/);
diff --git a/frontend/test/home/NewUserOnboardingModal.unit.spec.js b/frontend/test/home/NewUserOnboardingModal.unit.spec.js
index 5fc7b6868f33da9f907aa24a45183fff996f2d68..2ea520d9b5287e24cca85286c83a4027bc3f888c 100644
--- a/frontend/test/home/NewUserOnboardingModal.unit.spec.js
+++ b/frontend/test/home/NewUserOnboardingModal.unit.spec.js
@@ -2,7 +2,6 @@ import { click } from "__support__/enzyme_utils";
 
 import React from 'react'
 import { shallow } from 'enzyme'
-import sinon from 'sinon'
 import NewUserOnboardingModal from '../../src/metabase/home/components/NewUserOnboardingModal'
 
 describe('new user onboarding modal', () => {
@@ -19,7 +18,7 @@ describe('new user onboarding modal', () => {
         })
 
         it('should close if on the last step', () => {
-            const onClose = sinon.spy()
+            const onClose = jest.fn()
             const wrapper = shallow(
                 <NewUserOnboardingModal onClose={onClose} />
             )
@@ -29,7 +28,7 @@ describe('new user onboarding modal', () => {
             const nextButton = wrapper.find('a')
             expect(nextButton.text()).toEqual('Let\'s go')
             click(nextButton);
-            expect(onClose.called).toEqual(true)
+            expect(onClose.mock.calls.length).toEqual(1)
         })
     })
 })
diff --git a/frontend/test/metabase-lib/Dimension.integ.spec.js b/frontend/test/metabase-lib/Dimension.integ.spec.js
index 6833bbdf7579c68fd370fd5c33da01b9ff26d834..f247a6182b2f49714003826ee792743fda07062c 100644
--- a/frontend/test/metabase-lib/Dimension.integ.spec.js
+++ b/frontend/test/metabase-lib/Dimension.integ.spec.js
@@ -1,4 +1,4 @@
-import { createTestStore, login } from "__support__/integrated_tests";
+import { createTestStore, useSharedAdminLogin } from "__support__/integrated_tests";
 
 import {
     ORDERS_TOTAL_FIELD_ID,
@@ -19,7 +19,7 @@ describe("Dimension classes", () => {
     let metadata = null;
 
     beforeAll(async () => {
-        await login();
+        useSharedAdminLogin();
         const store = await createTestStore();
         await store.dispatch(fetchDatabaseMetadata(1));
         await store.dispatch(fetchTableMetadata(1));
diff --git a/frontend/test/metabase-lib/Question.integ.spec.js b/frontend/test/metabase-lib/Question.integ.spec.js
index cc1d29ab161c4b83cb94d47d7a8dca9c9d38c9ba..d787bf8983fc68147ac36b9f662680481b386efb 100644
--- a/frontend/test/metabase-lib/Question.integ.spec.js
+++ b/frontend/test/metabase-lib/Question.integ.spec.js
@@ -4,7 +4,7 @@ import {
     metadata
 } from "__support__/sample_dataset_fixture";
 import Question from "metabase-lib/lib/Question";
-import { login } from "__support__/integrated_tests";
+import { useSharedAdminLogin } from "__support__/integrated_tests";
 import { NATIVE_QUERY_TEMPLATE } from "metabase-lib/lib/queries/NativeQuery";
 
 // TODO Atte Keinänen 6/22/17: This could include tests that run each "question drill action" (summarize etc)
@@ -12,7 +12,7 @@ import { NATIVE_QUERY_TEMPLATE } from "metabase-lib/lib/queries/NativeQuery";
 
 describe("Question", () => {
     beforeAll(async () => {
-        await login();
+        useSharedAdminLogin();
     });
 
     describe("with SQL questions", () => {
diff --git a/frontend/test/modes/drills/PivotByCategoryDrill.integ.spec.js b/frontend/test/modes/drills/PivotByCategoryDrill.integ.spec.js
index ac7ce1f60052095a7d71bb249fc5b161084866bc..be7318bbb8b0ef348a347a93c17e3a4567e08047 100644
--- a/frontend/test/modes/drills/PivotByCategoryDrill.integ.spec.js
+++ b/frontend/test/modes/drills/PivotByCategoryDrill.integ.spec.js
@@ -6,11 +6,11 @@ import {
     metadata
 } from "__support__/sample_dataset_fixture";
 import Question from "metabase-lib/lib/Question";
-import { login } from "__support__/integrated_tests";
+import { useSharedAdminLogin } from "__support__/integrated_tests";
 
 describe("PivotByCategoryDrill", () => {
     beforeAll(async () => {
-        await login();
+        useSharedAdminLogin();
     });
 
     it("should return a result for Order count pivoted by Subtotal", async () => {
diff --git a/frontend/test/parameters/parameters.integ.spec.js b/frontend/test/parameters/parameters.integ.spec.js
index 4fa3d350303b94c4ae3c01ac2ebce87a47b5797d..5b957b042eca903ebafe6025a360e82545e7cadf 100644
--- a/frontend/test/parameters/parameters.integ.spec.js
+++ b/frontend/test/parameters/parameters.integ.spec.js
@@ -1,6 +1,6 @@
 // Converted from an old Selenium E2E test
 import {
-    login,
+    useSharedAdminLogin,
     logout,
     createTestStore,
     restorePreviousLogin,
@@ -65,7 +65,7 @@ const COUNT_GADGET = "43";
 
 describe("parameters", () => {
     beforeAll(async () =>
-        await login()
+        useSharedAdminLogin()
     );
 
     describe("questions", () => {
diff --git a/frontend/test/query_builder/NewQueryOptions.unit.spec.js b/frontend/test/query_builder/NewQueryOptions.unit.spec.js
deleted file mode 100644
index 8f86704108016ca66e57a537c5d7884c34152f3c..0000000000000000000000000000000000000000
--- a/frontend/test/query_builder/NewQueryOptions.unit.spec.js
+++ /dev/null
@@ -1,103 +0,0 @@
-import React from 'react'
-import { mount } from 'enzyme'
-
-import sinon from 'sinon'
-
-import { NewQueryOptions } from 'metabase/new_query/containers/NewQueryOptions'
-
-import NewQueryOption from "metabase/new_query/components/NewQueryOption";
-
-import { state, DATABASE_ID } from "../__support__/sample_dataset_fixture";
-
-const DB = state.metadata.databases[DATABASE_ID]
-
-const ACCESSIBLE_SQL_DB = {
-    ...DB,
-    native_permissions: "write"
-}
-
-const METADATA = {
-    metrics: {},
-    segments: {},
-    databases: {
-        [DATABASE_ID]: ACCESSIBLE_SQL_DB
-    },
-    databasesList: () => [ACCESSIBLE_SQL_DB],
-    segmentsList: () => [],
-    metricsList: () => [],
-}
-
-const mockFn = () => Promise.resolve({})
-
-describe('New Query Options', () => {
-    describe('a non admin on a fresh instance', () => {
-        describe('with SQL access on a single DB', () => {
-            it('should show the SQL option', (done) => {
-
-                sinon.spy(NewQueryOptions.prototype, 'determinePaths')
-
-                const wrapper = mount(
-                    <NewQueryOptions
-                        isAdmin={false}
-                        query={{}}
-                        metadataFetched={{
-                            databases: true,
-                            metrics: true,
-                            segments: true
-                        }}
-                        metadata={METADATA}
-                        fetchDatabases={mockFn}
-                        fetchMetrics={mockFn}
-                        fetchSegments={mockFn}
-                        resetQuery={mockFn}
-                        getUrlForQuery={() => 'query'}
-                        push={() => {} }
-                    />
-                )
-
-                setImmediate(() => {
-                    expect(NewQueryOptions.prototype.determinePaths.calledOnce).toEqual(true)
-                    expect(wrapper.find(NewQueryOption).length).toEqual(2)
-                    done()
-                })
-            })
-        })
-
-        describe('with no SQL access', () => {
-            it('should redirect', (done) => {
-                const mockedPush = sinon.spy()
-                const mockQueryUrl = 'query'
-
-                mount(
-                    <NewQueryOptions
-                        isAdmin={false}
-                        query={{}}
-                        metadataFetched={{
-                            databases: true,
-                            metrics: true,
-                            segments: true
-                        }}
-                        metadata={{
-                            ...METADATA,
-                            databases: {},
-                            databasesList: () => []
-                        }}
-                        fetchDatabases={mockFn}
-                        fetchMetrics={mockFn}
-                        fetchSegments={mockFn}
-                        resetQuery={mockFn}
-                        push={mockedPush}
-                        getUrlForQuery={() => mockQueryUrl}
-                    />
-                )
-
-                setImmediate(() => {
-                    expect(mockedPush.called).toEqual(true)
-                    expect(mockedPush.calledWith(mockQueryUrl)).toEqual(true)
-                    done()
-                })
-            })
-        })
-    })
-})
-
diff --git a/frontend/test/query_builder/actions.integ.spec.js b/frontend/test/query_builder/actions.integ.spec.js
index 2b30afb5d9c313eb42bbd75c8d81c72df3189b28..dd1a2b6a735715e8f0e249ef1c16d64176da926a 100644
--- a/frontend/test/query_builder/actions.integ.spec.js
+++ b/frontend/test/query_builder/actions.integ.spec.js
@@ -7,7 +7,7 @@ import { parse as urlParse } from "url";
 import {
     createSavedQuestion,
     createTestStore,
-    login
+    useSharedAdminLogin
 } from "__support__/integrated_tests";
 import { initializeQB } from "metabase/query_builder/actions";
 import { getCard, getOriginalCard, getQueryResults } from "metabase/query_builder/selectors";
@@ -23,7 +23,7 @@ describe("QueryBuilder", () => {
     let store = null;
 
     beforeAll(async () => {
-        await login();
+        useSharedAdminLogin();
         store = await createTestStore()
     })
 
diff --git a/frontend/test/query_builder/components/ActionsWidget.integ.spec.js b/frontend/test/query_builder/components/ActionsWidget.integ.spec.js
index 8f60bb331d53338b7f6cff2aa2e390ac7d021f31..17d5eff1899b2c15eb494f918d9201aba64327dc 100644
--- a/frontend/test/query_builder/components/ActionsWidget.integ.spec.js
+++ b/frontend/test/query_builder/components/ActionsWidget.integ.spec.js
@@ -1,5 +1,5 @@
 import {
-    login,
+    useSharedAdminLogin,
     createTestStore
 } from "__support__/integrated_tests";
 import {
@@ -29,7 +29,7 @@ const getActionsWidget = (question) =>
 
 describe('ActionsWidget', () => {
     beforeAll(async () => {
-        await login();
+        useSharedAdminLogin();
     })
 
     it("is visible for an empty question", () => {
@@ -45,7 +45,7 @@ describe('ActionsWidget', () => {
         let activeMetricId;
 
         beforeAll(async () => {
-            await login()
+            useSharedAdminLogin()
 
             const metricDef = {name: "A Metric", description: "For testing new question flow", table_id: 1,show_in_getting_started: true,
                 definition: {database: 1, query: {aggregation: ["count"]}}}
@@ -66,7 +66,6 @@ describe('ActionsWidget', () => {
                 .question()
                 .getUrl()
 
-            console.log(url)
             const store = await createTestStore()
             store.pushPath(url)
             const app = mount(store.getAppContainer());
diff --git a/frontend/test/query_builder/components/FieldList.integ.spec.js b/frontend/test/query_builder/components/FieldList.integ.spec.js
index 5e7860cabf66b27016a925f1ce4fee1f4a6f1569..e779acf5969718a045e4a7aec3fcedf47ea01228 100644
--- a/frontend/test/query_builder/components/FieldList.integ.spec.js
+++ b/frontend/test/query_builder/components/FieldList.integ.spec.js
@@ -1,5 +1,5 @@
 // Important: import of integrated_tests always comes first in tests because of mocked modules
-import { createTestStore, login } from "__support__/integrated_tests";
+import { createTestStore, useSharedAdminLogin } from "__support__/integrated_tests";
 
 import React from 'react'
 import { mount } from 'enzyme'
@@ -9,7 +9,7 @@ import Question from "metabase-lib/lib/Question";
 import {
     DATABASE_ID,
     ORDERS_TABLE_ID,
-    orders_past_30_days_segment
+    orders_past_300_days_segment
 } from "__support__/sample_dataset_fixture";
 
 import StructuredQuery from "metabase-lib/lib/queries/StructuredQuery";
@@ -31,7 +31,7 @@ const getFieldList = (query, fieldOptions, segmentOptions) =>
 
 describe('FieldList', () => {
     beforeAll(async () => {
-        await login();
+        useSharedAdminLogin();
     })
 
     it("should allow using expression as aggregation dimension", async () => {
@@ -55,7 +55,7 @@ describe('FieldList', () => {
 
     it("should show the query definition tooltip correctly for a segment", async () => {
         // TODO Atte Keinänen 6/27/17: Check why the result is wrapped in a promise that needs to be resolved manually
-        const segment = await (await createSegment(orders_past_30_days_segment)).payload;
+        const segment = await (await createSegment(orders_past_300_days_segment)).payload;
 
         const store = await createTestStore()
         await store.dispatch(fetchDatabases());
@@ -80,6 +80,6 @@ describe('FieldList', () => {
         expect(tooltipContent.length).toBe(1)
 
         // eslint-disable-next-line no-irregular-whitespace
-        expect(tooltipContent.find(FilterWidget).last().text()).toMatch(/Created At -30day/);
+        expect(tooltipContent.find(FilterWidget).last().text()).toMatch(/Created At -300day/);
     })
 });
\ No newline at end of file
diff --git a/frontend/test/query_builder/components/dataref/FieldPane.integ.spec.js b/frontend/test/query_builder/components/dataref/FieldPane.integ.spec.js
index f3af49a786891a77a4ddaee4ec0ed5a3dba62736..47247976265f8df90df61dc5ce02899f2a800197 100644
--- a/frontend/test/query_builder/components/dataref/FieldPane.integ.spec.js
+++ b/frontend/test/query_builder/components/dataref/FieldPane.integ.spec.js
@@ -1,5 +1,5 @@
 import {
-    login,
+    useSharedAdminLogin,
     createTestStore
 } from "__support__/integrated_tests";
 import { click } from "__support__/enzyme_utils";
@@ -27,7 +27,7 @@ describe("FieldPane", () => {
     let queryBuilder = null;
 
     beforeAll(async () => {
-        await login();
+        useSharedAdminLogin();
         store = await createTestStore()
 
         store.pushPath(Urls.plainQuestion());
diff --git a/frontend/test/query_builder/components/dataref/MetricPane.integ.spec.js b/frontend/test/query_builder/components/dataref/MetricPane.integ.spec.js
index 44dfe0f771ecdac37ef7f3520c146b48cc5cef60..6ad06b81687383ba37c0acf71058f3d57477f9df 100644
--- a/frontend/test/query_builder/components/dataref/MetricPane.integ.spec.js
+++ b/frontend/test/query_builder/components/dataref/MetricPane.integ.spec.js
@@ -1,5 +1,5 @@
 import {
-    login,
+    useSharedAdminLogin,
     createTestStore
 } from "__support__/integrated_tests";
 import { click } from "__support__/enzyme_utils"
@@ -26,7 +26,7 @@ describe("MetricPane", () => {
     let metricId = null;
 
     beforeAll(async () => {
-        await login();
+        useSharedAdminLogin();
         metricId = (await MetricApi.create(vendor_count_metric)).id;
         store = await createTestStore()
 
diff --git a/frontend/test/query_builder/components/dataref/SegmentPane.integ.spec.js b/frontend/test/query_builder/components/dataref/SegmentPane.integ.spec.js
index 171b1aae8d28146b0ab962baa02e01c28be34385..320dd35c184f9818f57d52fcbc37b41c516bc26f 100644
--- a/frontend/test/query_builder/components/dataref/SegmentPane.integ.spec.js
+++ b/frontend/test/query_builder/components/dataref/SegmentPane.integ.spec.js
@@ -1,5 +1,5 @@
 import {
-    login,
+    useSharedAdminLogin,
     createTestStore
 } from "__support__/integrated_tests";
 import { click } from "__support__/enzyme_utils";
@@ -15,7 +15,7 @@ import { delay } from "metabase/lib/promise"
 
 import QueryBuilder from "metabase/query_builder/containers/QueryBuilder";
 import DataReference from "metabase/query_builder/components/dataref/DataReference";
-import { orders_past_30_days_segment } from "__support__/sample_dataset_fixture";
+import { orders_past_300_days_segment } from "__support__/sample_dataset_fixture";
 import { FETCH_TABLE_METADATA } from "metabase/redux/metadata";
 import QueryDefinition from "metabase/query_builder/components/dataref/QueryDefinition";
 import QueryButton from "metabase/components/QueryButton";
@@ -31,8 +31,8 @@ describe("SegmentPane", () => {
     let segment = null;
 
     beforeAll(async () => {
-        await login();
-        segment = await SegmentApi.create(orders_past_30_days_segment);
+        useSharedAdminLogin();
+        segment = await SegmentApi.create(orders_past_300_days_segment);
         store = await createTestStore()
 
         store.pushPath(Urls.plainQuestion());
@@ -64,7 +64,7 @@ describe("SegmentPane", () => {
         // then we can replace this with `store.waitForActions([FETCH_TABLE_FOREIGN_KEYS])` or similar
         await delay(3000)
 
-        click(dataReference.find(`a[children="${orders_past_30_days_segment.name}"]`).first())
+        click(dataReference.find(`a[children="${orders_past_300_days_segment.name}"]`).first())
 
         await store.waitForActions([FETCH_TABLE_METADATA]);
     });
@@ -72,7 +72,7 @@ describe("SegmentPane", () => {
     it("shows you the correct segment definition", () => {
         const queryDefinition = queryBuilder.find(DataReference).find(QueryDefinition);
         // eslint-disable-next-line no-irregular-whitespace
-        expect(queryDefinition.text()).toMatch(/Created At -30day/);
+        expect(queryDefinition.text()).toMatch(/Created At -300day/);
     })
 
     it("lets you apply the filter to your current query", async () => {
@@ -87,7 +87,7 @@ describe("SegmentPane", () => {
         expect(queryBuilder.find(DataReference).find(UseForButton).length).toBe(0);
     });
 
-    it("lets you see count of rows for past 30 days", async () => {
+    it("lets you see count of rows for past 300 days", async () => {
         const numberQueryButton = queryBuilder.find(DataReference).find(QueryButton).at(0);
 
         try {
@@ -103,7 +103,7 @@ describe("SegmentPane", () => {
         // expect(queryBuilder.find(Scalar).text()).toBe("1,236")
     });
 
-    it("lets you see raw data for past 30 days", async () => {
+    it("lets you see raw data for past 300 days", async () => {
         const allQueryButton = queryBuilder.find(DataReference).find(QueryButton).at(1);
 
         try {
diff --git a/frontend/test/query_builder/new_question.integ.spec.js b/frontend/test/query_builder/new_question.integ.spec.js
index a7c84a39efaa74c09857d15e260575905f7a9bf2..ed8e6c33f723a442babb85a424ee7e632514385c 100644
--- a/frontend/test/query_builder/new_question.integ.spec.js
+++ b/frontend/test/query_builder/new_question.integ.spec.js
@@ -1,8 +1,8 @@
 import { mount } from "enzyme"
 
 import {
-    login,
-    createTestStore,
+    useSharedAdminLogin,
+    createTestStore, useSharedNormalLogin, forBothAdminsAndNormalUsers, withApiMocks, BROWSER_HISTORY_REPLACE,
 } from "__support__/integrated_tests";
 
 import EntitySearch, {
@@ -16,14 +16,12 @@ import {
     click,
 } from "__support__/enzyme_utils"
 
-import { RESET_QUERY } from "metabase/new_query/new_query";
+import { DETERMINE_OPTIONS } from "metabase/new_query/new_query";
 
 import { getQuery } from "metabase/query_builder/selectors";
 import DataSelector from "metabase/query_builder/components/DataSelector";
 
 import {
-    FETCH_METRICS,
-    FETCH_SEGMENTS,
     FETCH_DATABASES
 } from "metabase/redux/metadata"
 import NativeQuery from "metabase-lib/lib/queries/NativeQuery";
@@ -38,13 +36,14 @@ import {
     QUERY_COMPLETED,
 } from "metabase/query_builder/actions";
 
-import { MetricApi, SegmentApi } from "metabase/services";
+import { MetabaseApi, MetricApi, SegmentApi } from "metabase/services";
 import { SET_REQUEST_STATE } from "metabase/redux/requests";
 
 import StructuredQuery from "metabase-lib/lib/queries/StructuredQuery";
 
 import NativeQueryEditor from "metabase/query_builder/components/NativeQueryEditor";
 import NewQueryOption from "metabase/new_query/components/NewQueryOption";
+import NoDatabasesEmptyState from "metabase/reference/databases/NoDatabasesEmptyState";
 
 describe("new question flow", async () => {
     // test an instance with segments, metrics, etc as an admin
@@ -53,7 +52,6 @@ describe("new question flow", async () => {
         let segmentId = null;
 
         beforeAll(async () => {
-            await login()
             // TODO: Move these test metric/segment definitions to a central place
             const metricDef = {name: "A Metric", description: "For testing new question flow", table_id: 1,show_in_getting_started: true,
                 definition: {database: 1, query: {aggregation: ["count"]}}}
@@ -61,40 +59,120 @@ describe("new question flow", async () => {
                 definition: {database: 1, query: {filter: ["abc"]}}}
 
             // Needed for question creation flow
+            useSharedAdminLogin()
             metricId = (await MetricApi.create(metricDef)).id;
             segmentId = (await SegmentApi.create(segmentDef)).id;
 
         })
 
         afterAll(async () => {
+            useSharedAdminLogin()
             await MetricApi.delete({ metricId, revision_message: "The lifetime of this metric was just a few seconds" })
             await SegmentApi.delete({ segmentId, revision_message: "Sadly this segment didn't enjoy a long life either" })
         })
 
         it("redirects /question to /question/new", async () => {
+            useSharedNormalLogin()
             const store = await createTestStore()
             store.pushPath("/question");
             mount(store.getAppContainer());
             await store.waitForActions([REDIRECT_TO_NEW_QUESTION_FLOW])
             expect(store.getPath()).toBe("/question/new")
         })
-        it("renders normally on page load", async () => {
-            const store = await createTestStore()
 
-            store.pushPath(Urls.newQuestion());
-            const app = mount(store.getAppContainer());
-            await store.waitForActions([RESET_QUERY, FETCH_METRICS, FETCH_SEGMENTS]);
-            await store.waitForActions([SET_REQUEST_STATE]);
+        it("renders all options for both admins and normal users if metrics & segments exist", async () => {
+            await forBothAdminsAndNormalUsers(async () => {
+                const store = await createTestStore()
+
+                store.pushPath(Urls.newQuestion());
+                const app = mount(store.getAppContainer());
+                await store.waitForActions([DETERMINE_OPTIONS]);
+
+                expect(app.find(NewQueryOption).length).toBe(3)
+            })
+        });
+
+        it("does not show Metrics option for normal users if there are no metrics", async () => {
+            useSharedNormalLogin()
+
+            await withApiMocks([
+                [MetricApi, "list", () => []],
+            ], async () => {
+                const store = await createTestStore()
 
-            expect(app.find(NewQueryOption).length).toBe(3)
+                store.pushPath(Urls.newQuestion());
+                const app = mount(store.getAppContainer());
+                await store.waitForActions([DETERMINE_OPTIONS]);
+
+                expect(app.find(NewQueryOption).filterWhere((c) => c.prop('title') === "Metrics").length).toBe(0)
+                expect(app.find(NewQueryOption).length).toBe(2)
+            })
         });
+
+        it("does not show SQL option for normal user if SQL write permissions are missing", async () => {
+            useSharedNormalLogin()
+
+            const disableWritePermissionsForDb = (db) => ({ ...db, native_permissions: "read" })
+            const realDbListWithTables = MetabaseApi.db_list_with_tables
+
+            await withApiMocks([
+                [MetabaseApi, "db_list_with_tables", async () =>
+                    (await realDbListWithTables()).map(disableWritePermissionsForDb)
+                ],
+            ], async () => {
+                const store = await createTestStore()
+
+                store.pushPath(Urls.newQuestion());
+                const app = mount(store.getAppContainer());
+                await store.waitForActions([DETERMINE_OPTIONS]);
+
+                expect(app.find(NewQueryOption).length).toBe(2)
+            })
+        })
+
+        it("redirects to query builder if there are no segments/metrics and no write sql permissions", async () => {
+            useSharedNormalLogin()
+
+            const disableWritePermissionsForDb = (db) => ({ ...db, native_permissions: "read" })
+            const realDbListWithTables = MetabaseApi.db_list_with_tables
+
+            await withApiMocks([
+                [MetricApi, "list", () => []],
+                [MetabaseApi, "db_list_with_tables", async () =>
+                    (await realDbListWithTables()).map(disableWritePermissionsForDb)
+                ],
+            ], async () => {
+                const store = await createTestStore()
+                store.pushPath(Urls.newQuestion());
+                mount(store.getAppContainer());
+                await store.waitForActions(BROWSER_HISTORY_REPLACE, INITIALIZE_QB);
+            })
+        })
+
+        it("shows an empty state if there are no databases", async () => {
+            await forBothAdminsAndNormalUsers(async () => {
+                await withApiMocks([
+                    [MetabaseApi, "db_list_with_tables", () => []]
+                ], async () => {
+                    const store = await createTestStore()
+
+                    store.pushPath(Urls.newQuestion());
+                    const app = mount(store.getAppContainer());
+                    await store.waitForActions([DETERMINE_OPTIONS]);
+
+                    expect(app.find(NewQueryOption).length).toBe(0)
+                    expect(app.find(NoDatabasesEmptyState).length).toBe(1)
+                })
+            })
+        })
+
         it("lets you start a custom gui question", async () => {
+            useSharedNormalLogin()
             const store = await createTestStore()
 
             store.pushPath(Urls.newQuestion());
             const app = mount(store.getAppContainer());
-            await store.waitForActions([RESET_QUERY, FETCH_METRICS, FETCH_SEGMENTS]);
-            await store.waitForActions([SET_REQUEST_STATE]);
+            await store.waitForActions([DETERMINE_OPTIONS]);
 
             click(app.find(NewQueryOption).filterWhere((c) => c.prop('title') === "Custom"))
             await store.waitForActions(INITIALIZE_QB, UPDATE_URL, LOAD_METADATA_FOR_CARD);
@@ -102,6 +180,7 @@ describe("new question flow", async () => {
         })
 
         it("lets you start a custom native question", async () => {
+            useSharedNormalLogin()
             // Don't render Ace editor in tests because it uses many DOM methods that aren't supported by jsdom
             // see also parameters.integ.js for more notes about Ace editor testing
             NativeQueryEditor.prototype.loadAceEditor = () => {}
@@ -110,10 +189,9 @@ describe("new question flow", async () => {
 
             store.pushPath(Urls.newQuestion());
             const app = mount(store.getAppContainer());
-            await store.waitForActions([RESET_QUERY, FETCH_METRICS, FETCH_SEGMENTS, FETCH_DATABASES]);
-            await store.waitForActions([SET_REQUEST_STATE]);
+            await store.waitForActions([DETERMINE_OPTIONS]);
 
-            click(app.find(NewQueryOption).filterWhere((c) => c.prop('title') === "SQL"))
+            click(app.find(NewQueryOption).filterWhere((c) => c.prop('title') === "Native query"))
             await store.waitForActions(INITIALIZE_QB);
             expect(getQuery(store.getState()) instanceof NativeQuery).toBe(true)
 
@@ -126,12 +204,12 @@ describe("new question flow", async () => {
         })
 
         it("lets you start a question from a metric", async () => {
+            useSharedNormalLogin()
             const store = await createTestStore()
 
             store.pushPath(Urls.newQuestion());
             const app = mount(store.getAppContainer());
-            await store.waitForActions([RESET_QUERY, FETCH_METRICS, FETCH_SEGMENTS]);
-            await store.waitForActions([SET_REQUEST_STATE]);
+            await store.waitForActions([DETERMINE_OPTIONS]);
 
             click(app.find(NewQueryOption).filterWhere((c) => c.prop('title') === "Metrics"))
             await store.waitForActions(FETCH_DATABASES);
diff --git a/frontend/test/query_builder/query_builder.integ.spec.js b/frontend/test/query_builder/query_builder.integ.spec.js
index bd12ac131a14366fb42263b745b1f0f31bbcca53..23545041bd41ccf39ed1dab97b91c8a2c0de2f79 100644
--- a/frontend/test/query_builder/query_builder.integ.spec.js
+++ b/frontend/test/query_builder/query_builder.integ.spec.js
@@ -1,5 +1,5 @@
 import {
-    login,
+    useSharedAdminLogin,
     whenOffline,
     createSavedQuestion,
     createTestStore
@@ -87,7 +87,7 @@ const initQBWithReviewsTable = initQbWithDbAndTable(1, 4)
 
 describe("QueryBuilder", () => {
     beforeAll(async () => {
-        await login()
+        useSharedAdminLogin()
     })
 
     describe("visualization settings", () => {
diff --git a/frontend/test/redux/metadata.integ.spec.js b/frontend/test/redux/metadata.integ.spec.js
index ffafe1b8e9fb9a82c6f85a572b63ea23d94af0be..d0c0ad6bf3ecbd5ccfac8e858b0467354ecbd72a 100644
--- a/frontend/test/redux/metadata.integ.spec.js
+++ b/frontend/test/redux/metadata.integ.spec.js
@@ -7,7 +7,7 @@
 import { getMetadata } from "metabase/selectors/metadata"
 import {
     createTestStore,
-    login,
+    useSharedAdminLogin,
 } from "__support__/integrated_tests";
 import {
     fetchMetrics,
@@ -19,7 +19,7 @@ const metadata = (store) => getMetadata(store.getState())
 
 describe("metadata/redux", () => {
     beforeAll(async () => {
-        await login();
+        useSharedAdminLogin();
     });
 
     describe("METRIC ACTIONS", () => {
diff --git a/frontend/test/reference/databases.integ.spec.js b/frontend/test/reference/databases.integ.spec.js
index 09bf4f93b939a45465776b3be4ee1a66af738415..5bb45759126e92de55c0d96a86213330232902c4 100644
--- a/frontend/test/reference/databases.integ.spec.js
+++ b/frontend/test/reference/databases.integ.spec.js
@@ -1,5 +1,5 @@
 import {
-    login,
+    useSharedAdminLogin,
     createTestStore
 } from "__support__/integrated_tests";
 import { click } from "__support__/enzyme_utils"
@@ -42,7 +42,7 @@ describe("The Reference Section", () => {
 
     // Scaffolding
     beforeAll(async () => {
-        await login();
+        useSharedAdminLogin();
 
     })
 
diff --git a/frontend/test/reference/guide.integ.spec.js b/frontend/test/reference/guide.integ.spec.js
index 6800143c1c841a681da02550b7e133cad4c85ba2..ef9a8d74cc22013d94d1f9668609ee975633025b 100644
--- a/frontend/test/reference/guide.integ.spec.js
+++ b/frontend/test/reference/guide.integ.spec.js
@@ -1,5 +1,5 @@
 import {
-    login,
+    useSharedAdminLogin,
     createTestStore
 } from "__support__/integrated_tests";
 
@@ -33,7 +33,7 @@ describe("The Reference Section", () => {
     
     // Scaffolding
     beforeAll(async () => {
-        await login();
+        useSharedAdminLogin();
 
     })
 
diff --git a/frontend/test/reference/metrics.integ.spec.js b/frontend/test/reference/metrics.integ.spec.js
index 417e7ae33e5fcf82625994c9a44ea743e6e69397..62c7ae9be5c0fdcf2561baa025de48ad70d99b06 100644
--- a/frontend/test/reference/metrics.integ.spec.js
+++ b/frontend/test/reference/metrics.integ.spec.js
@@ -1,5 +1,5 @@
 import {
-    login,
+    useSharedAdminLogin,
     createTestStore
 } from "__support__/integrated_tests";
 
@@ -36,7 +36,7 @@ describe("The Reference Section", () => {
 
     // Scaffolding
     beforeAll(async () => {
-        await login();
+        useSharedAdminLogin();
 
     })
 
diff --git a/frontend/test/reference/segments.integ.spec.js b/frontend/test/reference/segments.integ.spec.js
index 2e0897ba9b8b0aa0fd16fc327302dd2a69c737f8..3df654b20666c2b6e427c8c8c83f443f690b2f64 100644
--- a/frontend/test/reference/segments.integ.spec.js
+++ b/frontend/test/reference/segments.integ.spec.js
@@ -1,5 +1,5 @@
 import {
-    login,
+    useSharedAdminLogin,
     createTestStore
 } from "__support__/integrated_tests";
 
@@ -39,7 +39,7 @@ describe("The Reference Section", () => {
 
     // Scaffolding
     beforeAll(async () => {
-        await login();
+        useSharedAdminLogin();
 
     })
 
diff --git a/frontend/test/selectors/metadata.integ.spec.js b/frontend/test/selectors/metadata.integ.spec.js
index d2b8c43480ed863615d41c2e8342030b0ae0bdfb..e7c866182f981169aec4f08d58c60716609d4f8b 100644
--- a/frontend/test/selectors/metadata.integ.spec.js
+++ b/frontend/test/selectors/metadata.integ.spec.js
@@ -1,4 +1,4 @@
-import { createTestStore, login } from "__support__/integrated_tests";
+import { createTestStore, useSharedAdminLogin } from "__support__/integrated_tests";
 import {
     deleteFieldDimension, fetchTableMetadata,
     updateFieldDimension,
@@ -14,7 +14,7 @@ const PRODUCT_CATEGORY_ID = 21;
 
 describe('makeGetMergedParameterFieldValues', () => {
     beforeAll(async () => {
-        await login();
+        useSharedAdminLogin();
 
         // add remapping
         const store = await createTestStore()
diff --git a/frontend/test/visualizations/components/ObjectDetail.integ.spec.js b/frontend/test/visualizations/components/ObjectDetail.integ.spec.js
index 18538b07d250cd1380547c1a67895156c173adf8..87eea634ba071db48718f63870d4fb0ad62eb8d2 100644
--- a/frontend/test/visualizations/components/ObjectDetail.integ.spec.js
+++ b/frontend/test/visualizations/components/ObjectDetail.integ.spec.js
@@ -1,5 +1,5 @@
 import {
-    login,
+    useSharedAdminLogin,
     createSavedQuestion,
     createTestStore
 } from "__support__/integrated_tests";
@@ -24,7 +24,7 @@ import { getMetadata } from "metabase/selectors/metadata";
 describe('ObjectDetail', () => {
 
     beforeAll(async () => {
-        await login()
+        useSharedAdminLogin()
     })
 
     describe('Increment and Decrement', () => {
diff --git a/frontend/test/visualizations/drillthroughs.integ.spec.js b/frontend/test/visualizations/drillthroughs.integ.spec.js
index 90d56d9c0fa6124c388bff6b3993069bd3b3fc26..b4de8bcf5545b39f1e823fb95326cc441a40c0da 100644
--- a/frontend/test/visualizations/drillthroughs.integ.spec.js
+++ b/frontend/test/visualizations/drillthroughs.integ.spec.js
@@ -6,7 +6,7 @@ import { initializeQB, navigateToNewCardInsideQB } from "metabase/query_builder/
 import { parse as urlParse } from "url";
 
 import {
-    login,
+    useSharedAdminLogin,
     createTestStore
 } from "__support__/integrated_tests";
 
@@ -36,7 +36,7 @@ const question = Question.create({databaseId: DATABASE_ID, tableId: ORDERS_TABLE
 
 describe('Visualization drill-through', () => {
     beforeAll(async () => {
-        await login();
+        useSharedAdminLogin();
     });
 
     // NOTE: Should this be here or somewhere in QB directory?
diff --git a/frontend/test/xray/xray.integ.spec.js b/frontend/test/xray/xray.integ.spec.js
index 1f9cd64d2c1d8292ba9b69136cb293ec54385e26..69b3757742d0b73a595ee32be0faee57f84e1fb9 100644
--- a/frontend/test/xray/xray.integ.spec.js
+++ b/frontend/test/xray/xray.integ.spec.js
@@ -1,5 +1,5 @@
 import {
-    login,
+    useSharedAdminLogin,
     createTestStore,
     createSavedQuestion
 } from "__support__/integrated_tests";
@@ -48,7 +48,7 @@ describe("xray integration tests", () => {
     let segmentQuestion = null;
 
     beforeAll(async () => {
-        await login()
+        useSharedAdminLogin()
 
         const segmentDef = {name: "A Segment", description: "For testing xrays", table_id: 1, show_in_getting_started: true,
             definition: { source_table: 1, filter: ["time-interval", ["field-id", 1], -30, "day"] }}
diff --git a/package.json b/package.json
index 37a53a2d570774152aab0622be7c694f357292d8..f3e10ba63df08cf8c512678c0d97b67f17d720bf 100644
--- a/package.json
+++ b/package.json
@@ -138,7 +138,6 @@
     "react-test-renderer": "^15.5.4",
     "sauce-connect-launcher": "^1.1.1",
     "selenium-webdriver": "^2.53.3",
-    "sinon": "^2.3.1",
     "style-loader": "^0.16.1",
     "unused-files-webpack-plugin": "^3.0.0",
     "webchauffeur": "^1.2.0",
diff --git a/yarn.lock b/yarn.lock
index 62a0f65b596006b75bcd15475d79fdc46dc384f3..6fd269815685b91c9c5d19d746723ce995f7d768 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2386,7 +2386,7 @@ diff@^1.3.2:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/diff/-/diff-1.4.0.tgz#7f28d2eb9ee7b15a97efd89ce63dcfdaa3ccbabf"
 
-diff@^3.0.0, diff@^3.1.0, diff@^3.2.0:
+diff@^3.0.0, diff@^3.2.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9"
 
@@ -3340,12 +3340,6 @@ form-data@~2.1.1:
     combined-stream "^1.0.5"
     mime-types "^2.1.12"
 
-formatio@1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/formatio/-/formatio-1.2.0.tgz#f3b2167d9068c4698a8d51f4f760a39a54d818eb"
-  dependencies:
-    samsam "1.x"
-
 forwarded@~0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.0.tgz#19ef9874c4ae1c297bcf078fde63a09b66a84363"
@@ -5207,10 +5201,6 @@ log4js@^0.6.31:
     readable-stream "~1.0.2"
     semver "~4.3.3"
 
-lolex@^1.6.0:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.6.0.tgz#3a9a0283452a47d7439e72731b9e07d7386e49f6"
-
 longest-streak@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.1.tgz#42d291b5411e40365c00e63193497e2247316e35"
@@ -5503,10 +5493,6 @@ nan@^2.3.0:
   version "2.6.2"
   resolved "https://registry.yarnpkg.com/nan/-/nan-2.6.2.tgz#e4ff34e6c95fdfb5aecc08de6596f43605a7db45"
 
-native-promise-only@^0.8.1:
-  version "0.8.1"
-  resolved "https://registry.yarnpkg.com/native-promise-only/-/native-promise-only-0.8.1.tgz#20a318c30cb45f71fe7adfbf7b21c99c1472ef11"
-
 natural-compare@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
@@ -6101,12 +6087,6 @@ path-to-regexp@0.1.7:
   version "0.1.7"
   resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
 
-path-to-regexp@^1.7.0:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d"
-  dependencies:
-    isarray "0.0.1"
-
 path-type@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
@@ -7565,10 +7545,6 @@ safe-json-parse@~1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/safe-json-parse/-/safe-json-parse-1.0.1.tgz#3e76723e38dfdda13c9b1d29a1e07ffee4b30b57"
 
-samsam@1.x, samsam@^1.1.3:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.2.1.tgz#edd39093a3184370cb859243b2bdf255e7d8ea67"
-
 sane@~1.5.0:
   version "1.5.0"
   resolved "https://registry.yarnpkg.com/sane/-/sane-1.5.0.tgz#a4adeae764d048621ecb27d5f9ecf513101939f3"
@@ -7730,19 +7706,6 @@ simple-swizzle@^0.2.2:
   dependencies:
     is-arrayish "^0.3.1"
 
-sinon@^2.3.1:
-  version "2.3.1"
-  resolved "https://registry.yarnpkg.com/sinon/-/sinon-2.3.1.tgz#48c9c758b4d0bb86327486833f1c4298919ce9ee"
-  dependencies:
-    diff "^3.1.0"
-    formatio "1.2.0"
-    lolex "^1.6.0"
-    native-promise-only "^0.8.1"
-    path-to-regexp "^1.7.0"
-    samsam "^1.1.3"
-    text-encoding "0.6.4"
-    type-detect "^4.0.0"
-
 slash@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
@@ -8169,10 +8132,6 @@ tether@^1.2.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/tether/-/tether-1.4.0.tgz#0f9fa171f75bf58485d8149e94799d7ae74d1c1a"
 
-text-encoding@0.6.4:
-  version "0.6.4"
-  resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19"
-
 text-table@~0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
@@ -8360,10 +8319,6 @@ type-check@~0.3.2:
   dependencies:
     prelude-ls "~1.1.2"
 
-type-detect@^4.0.0:
-  version "4.0.3"
-  resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.3.tgz#0e3f2670b44099b0b46c284d136a7ef49c74c2ea"
-
 type-is@~1.6.14:
   version "1.6.15"
   resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410"