Skip to content
Snippets Groups Projects
Commit 0eb5045f authored by Tom Robinson's avatar Tom Robinson
Browse files

Replace Angular routing with react-router, react-router-redux, redux-auth-wrapper. :tada::tada::tada: FINALLY :tada::tada::tada:

parent 7dd68d5f
No related branches found
No related tags found
No related merge requests found
Showing
with 318 additions and 435 deletions
import React, { Component, PropTypes } from "react"; import React, { Component, PropTypes } from "react";
import { Route } from 'react-router'; import { Route, IndexRoute, IndexRedirect, Redirect } from 'react-router';
import { ReduxRouter } from 'redux-router';
// auth containers // auth containers
import ForgotPasswordApp from "metabase/auth/containers/ForgotPasswordApp.jsx"; import ForgotPasswordApp from "metabase/auth/containers/ForgotPasswordApp.jsx";
...@@ -45,100 +44,170 @@ import ReferenceGettingStartedGuide from "metabase/reference/containers/Referenc ...@@ -45,100 +44,170 @@ import ReferenceGettingStartedGuide from "metabase/reference/containers/Referenc
import Navbar from "metabase/nav/containers/Navbar.jsx"; import Navbar from "metabase/nav/containers/Navbar.jsx";
export default class Routes extends Component { import { UserAuthWrapper } from 'redux-auth-wrapper';
// this lets us forward props we've injected from the Angular controller
_forwardProps(ComposedComponent, propNames) { // START react-router-redux
let forwarededProps = {}; import { routerActions } from 'react-router-redux';
for (const propName of propNames) { const redirectAction = routerActions.replace;
forwarededProps[propName] = this.props[propName]; // END react-router-redux
}
return (props) => <ComposedComponent {...props} {...forwarededProps} />; // START redux-router
} // import { push } from 'redux-router';
// const redirectAction = ({ pathname, query }) => {
// console.log("REDIRECT", pathname, query);
// if (query.redirect) {
// return push(`${pathname}?next=${query.redirect}`)
// } else {
// return push(pathname)
// }
// };
// END redux-router
// Create the wrapper that checks if user is authenticated.
const UserIsAuthenticated = UserAuthWrapper({
// Select the field of the state with auth data
authSelector: state => state.currentUser,
redirectAction: redirectAction,
// Choose the url to redirect not authenticated users.
failureRedirectPath: '/auth/login',
wrapperDisplayName: 'UserIsAuthenticated'
})
// Do the same to create the wrapper that checks if user is NOT authenticated.
const UserIsNotAuthenticated = UserAuthWrapper({
authSelector: state => state.currentUser,
redirectAction: redirectAction,
failureRedirectPath: '/',
// Choose what exactly you need to check in the selected field of the state
// (in the previous wrapper it checks by default).
predicate: currentUser => !currentUser,
wrapperDisplayName: 'UserIsNotAuthenticated'
})
import { connect } from "react-redux";
import { push } from "react-router-redux";
function FIXME_forwardOnChangeLocation(Component) {
return connect(null, { onChangeLocation: push })(Component)
}
const NotAuthenticated = UserIsNotAuthenticated(({ children }) => children);
const Authenticated = UserIsAuthenticated(({ children }) => children);
class App extends Component {
componentWillMount() {
console.log('will mount app')
}
render() { render() {
const { children, location } = this.props;
return ( return (
<ReduxRouter> <div className="spread flex flex-column">
<Route component={({ children }) => <Navbar location={location} className="flex-no-shrink" />
<div className="spread flex flex-column"> {children}
<Navbar className="flex-no-shrink" onChangeLocation={this.props.onChangeLocation} /> </div>
{children} )
</div>
}>
<Route path="/" component={this._forwardProps(HomepageApp, ["onChangeLocation"])} />
<Route path="/admin">
<Route path="databases" component={DatabaseListApp} />
<Route path="databases/create" component={this._forwardProps(DatabaseEditApp, ["onChangeLocation"])} />
<Route path="databases/:databaseId" component={this._forwardProps(DatabaseEditApp, ["onChangeLocation"])} />
<Route path="datamodel">
<Route path="database" component={this._forwardProps(MetadataEditorApp, ["onChangeLocation"])} />
<Route path="database/:databaseId" component={this._forwardProps(MetadataEditorApp, ["onChangeLocation"])} />
<Route path="database/:databaseId/:mode" component={this._forwardProps(MetadataEditorApp, ["onChangeLocation"])} />
<Route path="database/:databaseId/:mode/:tableId" component={this._forwardProps(MetadataEditorApp, ["onChangeLocation"])} />
<Route path="metric/create" component={this._forwardProps(MetricApp, ["onChangeLocation"])} />
<Route path="metric/:id" component={this._forwardProps(MetricApp, ["onChangeLocation"])} />
<Route path="segment/create" component={this._forwardProps(SegmentApp, ["onChangeLocation"])} />
<Route path="segment/:id" component={this._forwardProps(SegmentApp, ["onChangeLocation"])} />
<Route path=":entity/:id/revisions" component={RevisionHistoryApp} />
</Route>
<Route path="people" component={this._forwardProps(AdminPeopleApp, ["onChangeLocation"])} />
<Route path="settings" component={this._forwardProps(SettingsEditorApp, ["refreshSiteSettings"])} />
</Route>
<Route path="/reference" component={ReferenceApp}>
<Route path="guide" component={ReferenceGettingStartedGuide} />
<Route path="metrics" component={ReferenceEntityList} />
<Route path="metrics/:metricId" component={ReferenceEntity} />
<Route path="metrics/:metricId/questions" component={ReferenceEntityList} />
<Route path="metrics/:metricId/revisions" component={ReferenceRevisionsList} />
<Route path="segments" component={ReferenceEntityList} />
<Route path="segments/:segmentId" component={ReferenceEntity} />
<Route path="segments/:segmentId/fields" component={ReferenceFieldsList} />
<Route path="segments/:segmentId/fields/:fieldId" component={ReferenceEntity} />
<Route path="segments/:segmentId/questions" component={ReferenceEntityList} />
<Route path="segments/:segmentId/revisions" component={ReferenceRevisionsList} />
<Route path="databases" component={ReferenceEntityList} />
<Route path="databases/:databaseId" component={ReferenceEntity} />
<Route path="databases/:databaseId/tables" component={ReferenceEntityList} />
<Route path="databases/:databaseId/tables/:tableId" component={ReferenceEntity} />
<Route path="databases/:databaseId/tables/:tableId/fields" component={ReferenceFieldsList} />
<Route path="databases/:databaseId/tables/:tableId/fields/:fieldId" component={ReferenceEntity} />
<Route path="databases/:databaseId/tables/:tableId/questions" component={ReferenceEntityList} />
</Route>
<Route path="/auth">
<Route path="forgot_password" component={ForgotPasswordApp} />
<Route path="login" component={this._forwardProps(LoginApp, ["onChangeLocation"])} />
<Route path="logout" component={this._forwardProps(LogoutApp, ["onChangeLocation"])} />
<Route path="reset_password/:token" component={this._forwardProps(PasswordResetApp, ["onChangeLocation"])} />
<Route path="google_no_mb_account" component={GoogleNoAccount} />
</Route>
<Route path="/dash/:dashboardId" component={this._forwardProps(DashboardApp, ["onChangeLocation"])} />
<Route path="/pulse" component={this._forwardProps(PulseListApp, ["onChangeLocation"])} />
<Route path="/pulse/create" component={this._forwardProps(PulseEditApp, ["onChangeLocation"])} />
<Route path="/pulse/:pulseId" component={this._forwardProps(PulseEditApp, ["onChangeLocation"])} />
<Route path="/card/:cardId" component={this._forwardProps(QueryBuilder, ["onChangeLocation", "updateUrl"])} />
<Route path="/q" component={this._forwardProps(QueryBuilder, ["onChangeLocation", "updateUrl"])} />
<Route path="/questions" component={EntityBrowser}>
<Route path="edit/labels" component={EditLabels} />
<Route path=":section" component={EntityList} />
<Route path=":section/:slug" component={EntityList} />
</Route>
<Route path="/setup" component={SetupApp} />
<Route path="/user/edit_current" component={UserSettingsApp} />
<Route path="/unauthorized" component={Unauthorized} />
<Route path="/*" component={NotFound} />
</Route>
</ReduxRouter>
);
} }
} }
const Routes =
<Route component={App}>
{/* AUTH */}
<Route path="/auth">
<IndexRedirect to="/auth/login" />
<Route path="forgot_password" component={ForgotPasswordApp} />
<Route path="login" component={LoginApp} />
<Route path="logout" component={LogoutApp} />
<Route path="reset_password/:token" component={PasswordResetApp} />
<Route path="google_no_mb_account" component={GoogleNoAccount} />
</Route>
{/* SETUP */}
<Route path="/setup" component={SetupApp} />
{/* MAIN */}
<Route component={Authenticated}>
{/* HOME */}
<Route path="/" component={HomepageApp} />
{/* DASHBOARD */}
<Route path="/dash/:dashboardId" component={FIXME_forwardOnChangeLocation(DashboardApp)} />
{/* QUERY BUILDER */}
<Route path="/card/:cardId" component={FIXME_forwardOnChangeLocation(QueryBuilder, ["onChangeLocation", "updateUrl"])} />
<Route path="/q" component={FIXME_forwardOnChangeLocation(QueryBuilder, ["onChangeLocation", "updateUrl"])} />
{/* QUESTIONS */}
<Route path="/questions" component={EntityBrowser}>
<Route path="edit/labels" component={EditLabels} />
<Route path=":section" component={EntityList} />
<Route path=":section/:slug" component={EntityList} />
</Route>
{/* REFERENCE */}
<Route path="/reference" component={ReferenceApp}>
<IndexRedirect to="/reference/guide" />
<Route path="guide" component={ReferenceGettingStartedGuide} />
<Route path="metrics" component={ReferenceEntityList} />
<Route path="metrics/:metricId" component={ReferenceEntity} />
<Route path="metrics/:metricId/questions" component={ReferenceEntityList} />
<Route path="metrics/:metricId/revisions" component={ReferenceRevisionsList} />
<Route path="segments" component={ReferenceEntityList} />
<Route path="segments/:segmentId" component={ReferenceEntity} />
<Route path="segments/:segmentId/fields" component={ReferenceFieldsList} />
<Route path="segments/:segmentId/fields/:fieldId" component={ReferenceEntity} />
<Route path="segments/:segmentId/questions" component={ReferenceEntityList} />
<Route path="segments/:segmentId/revisions" component={ReferenceRevisionsList} />
<Route path="databases" component={ReferenceEntityList} />
<Route path="databases/:databaseId" component={ReferenceEntity} />
<Route path="databases/:databaseId/tables" component={ReferenceEntityList} />
<Route path="databases/:databaseId/tables/:tableId" component={ReferenceEntity} />
<Route path="databases/:databaseId/tables/:tableId/fields" component={ReferenceFieldsList} />
<Route path="databases/:databaseId/tables/:tableId/fields/:fieldId" component={ReferenceEntity} />
<Route path="databases/:databaseId/tables/:tableId/questions" component={ReferenceEntityList} />
</Route>
{/* PULSE */}
<Route path="/pulse" component={FIXME_forwardOnChangeLocation(PulseListApp)} />
<Route path="/pulse/create" component={FIXME_forwardOnChangeLocation(PulseEditApp)} />
<Route path="/pulse/:pulseId" component={FIXME_forwardOnChangeLocation(PulseEditApp)} />
{/* USER */}
<Route path="/user/edit_current" component={UserSettingsApp} />
</Route>
{/* ADMIN */}
<Route path="/admin" component={Authenticated}>
<IndexRedirect to="/admin/settings" />
<Route path="databases" component={DatabaseListApp} />
<Route path="databases/create" component={FIXME_forwardOnChangeLocation(DatabaseEditApp)} />
<Route path="databases/:databaseId" component={FIXME_forwardOnChangeLocation(DatabaseEditApp)} />
<Route path="datamodel">
<Route path="database" component={FIXME_forwardOnChangeLocation(MetadataEditorApp)} />
<Route path="database/:databaseId" component={FIXME_forwardOnChangeLocation(MetadataEditorApp)} />
<Route path="database/:databaseId/:mode" component={FIXME_forwardOnChangeLocation(MetadataEditorApp)} />
<Route path="database/:databaseId/:mode/:tableId" component={FIXME_forwardOnChangeLocation(MetadataEditorApp)} />
<Route path="metric/create" component={FIXME_forwardOnChangeLocation(MetricApp)} />
<Route path="metric/:id" component={FIXME_forwardOnChangeLocation(MetricApp)} />
<Route path="segment/create" component={FIXME_forwardOnChangeLocation(SegmentApp)} />
<Route path="segment/:id" component={FIXME_forwardOnChangeLocation(SegmentApp)} />
<Route path=":entity/:id/revisions" component={RevisionHistoryApp} />
</Route>
<Route path="people" component={FIXME_forwardOnChangeLocation(AdminPeopleApp)} />
<Route path="settings" component={SettingsEditorApp} />
<Route path="settings/:section" component={SettingsEditorApp} />
</Route>
{/* MISC */}
<Route path="/unauthorized" component={Unauthorized} />
<Route path="/*" component={NotFound} />
{/* LEGACY */}
<Redirect from="/card" to="/questions" />
<Redirect from="/card/:cardId/:serializedCard" to="/questions/:cardId#:serializedCard" />
<Redirect from="/q/:serializedCard" to="/q#:serializedCard" />
</Route>
export default Routes;
...@@ -17,7 +17,7 @@ import * as databaseActions from "../database"; ...@@ -17,7 +17,7 @@ import * as databaseActions from "../database";
const mapStateToProps = (state, props) => { const mapStateToProps = (state, props) => {
return { return {
databaseId: state.router && state.router.params && state.router.params.databaseId, databaseId: props.params.databaseId,
database: getEditingDatabase(state), database: getEditingDatabase(state),
formState: getFormState(state), formState: getFormState(state),
onChangeLocation: props.onChangeLocation onChangeLocation: props.onChangeLocation
......
import React, { Component, PropTypes } from "react"; import React, { Component, PropTypes } from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link } from "react-router";
import cx from "classnames"; import cx from "classnames";
import MetabaseSettings from "metabase/lib/settings"; import MetabaseSettings from "metabase/lib/settings";
import ModalWithTrigger from "metabase/components/ModalWithTrigger.jsx"; import ModalWithTrigger from "metabase/components/ModalWithTrigger.jsx";
...@@ -17,7 +19,7 @@ import * as databaseActions from "../database"; ...@@ -17,7 +19,7 @@ import * as databaseActions from "../database";
const mapStateToProps = (state, props) => { const mapStateToProps = (state, props) => {
return { return {
created: state.router && state.router.params && state.router.params.created, created: props.params.created,
databases: getDatabasesSorted(state), databases: getDatabasesSorted(state),
hasSampleDataset: hasSampleDataset(state), hasSampleDataset: hasSampleDataset(state),
engines: MetabaseSettings.get('engines') engines: MetabaseSettings.get('engines')
...@@ -46,7 +48,7 @@ export default class DatabaseList extends Component { ...@@ -46,7 +48,7 @@ export default class DatabaseList extends Component {
return ( return (
<div className="wrapper"> <div className="wrapper">
<section className="PageHeader px2 clearfix"> <section className="PageHeader px2 clearfix">
<a className="Button Button--primary float-right" href="/admin/databases/create">Add database</a> <Link to="/admin/databases/create" className="Button Button--primary float-right">Add database</Link>
<h2 className="PageTitle">Databases</h2> <h2 className="PageTitle">Databases</h2>
</section> </section>
<section> <section>
...@@ -63,7 +65,7 @@ export default class DatabaseList extends Component { ...@@ -63,7 +65,7 @@ export default class DatabaseList extends Component {
databases.map(database => databases.map(database =>
<tr key={database.id}> <tr key={database.id}>
<td> <td>
<a className="text-bold link" href={"/admin/databases/"+database.id}>{database.name}</a> <Link to={"/admin/databases/"+database.id} className="text-bold link">{database.name}</Link>
</td> </td>
<td> <td>
{engines && engines[database.engine] ? engines[database.engine]['driver-name'] : database.engine} {engines && engines[database.engine] ? engines[database.engine]['driver-name'] : database.engine}
......
import React, { Component, PropTypes } from "react"; import React, { Component, PropTypes } from "react";
import { Link } from "react-router";
import Icon from "metabase/components/Icon.jsx"; import Icon from "metabase/components/Icon.jsx";
import PopoverWithTrigger from "metabase/components/PopoverWithTrigger.jsx"; import PopoverWithTrigger from "metabase/components/PopoverWithTrigger.jsx";
...@@ -30,14 +31,14 @@ export default class ObjectActionsSelect extends Component { ...@@ -30,14 +31,14 @@ export default class ObjectActionsSelect extends Component {
> >
<ul className="UserActionsSelect"> <ul className="UserActionsSelect">
<li> <li>
<a data-metabase-event={"Data Model;"+objectType+" Edit Page"}href={"/admin/datamodel/" + objectType + "/" + object.id} className="py1 px2 block bg-brand-hover text-white-hover no-decoration cursor-pointer"> <Link to={"/admin/datamodel/" + objectType + "/" + object.id} data-metabase-event={"Data Model;"+objectType+" Edit Page"} className="py1 px2 block bg-brand-hover text-white-hover no-decoration cursor-pointer">
Edit {capitalize(objectType)} Edit {capitalize(objectType)}
</a> </Link>
</li> </li>
<li> <li>
<a data-metabase-event={"Data Model;"+objectType+" History"} href={"/admin/datamodel/" + objectType + "/" + object.id + "/revisions"} className="py1 px2 block bg-brand-hover text-white-hover no-decoration cursor-pointer"> <Link to={"/admin/datamodel/" + objectType + "/" + object.id + "/revisions"} data-metabase-event={"Data Model;"+objectType+" History"} className="py1 px2 block bg-brand-hover text-white-hover no-decoration cursor-pointer">
Revision History Revision History
</a> </Link>
</li> </li>
<li className="mt1 border-top"> <li className="mt1 border-top">
<ModalWithTrigger <ModalWithTrigger
......
...@@ -43,7 +43,7 @@ export default class MetadataTableList extends Component { ...@@ -43,7 +43,7 @@ export default class MetadataTableList extends Component {
_.each(tables, (table) => { _.each(tables, (table) => {
var row = ( var row = (
<li key={table.id}> <li key={table.id}>
<a href="#" className={cx("AdminList-item flex align-center no-decoration", { selected: this.props.tableId === table.id })} onClick={this.props.selectTable.bind(null, table)}> <a className={cx("AdminList-item flex align-center no-decoration", { selected: this.props.tableId === table.id })} onClick={this.props.selectTable.bind(null, table)}>
{table.display_name} {table.display_name}
<ProgressBar className="ProgressBar ProgressBar--mini flex-align-right" percentage={table.metadataStrength} /> <ProgressBar className="ProgressBar ProgressBar--mini flex-align-right" percentage={table.metadataStrength} />
</a> </a>
......
import React, { Component, PropTypes } from "react"; import React, { Component, PropTypes } from "react";
import { Link } from "react-router";
import MetricItem from "./MetricItem.jsx"; import MetricItem from "./MetricItem.jsx";
...@@ -18,7 +19,7 @@ export default class MetricsList extends Component { ...@@ -18,7 +19,7 @@ export default class MetricsList extends Component {
<div className="my3"> <div className="my3">
<div className="flex mb1"> <div className="flex mb1">
<h2 className="px1 text-green">Metrics</h2> <h2 className="px1 text-green">Metrics</h2>
<a data-metabase-event="Data Model;Add Metric Page" className="flex-align-right float-right text-bold text-brand no-decoration" href={"/admin/datamodel/metric/create?table="+tableMetadata.id}>+ Add a Metric</a> <Link to={"/admin/datamodel/metric/create?table="+tableMetadata.id} data-metabase-event="Data Model;Add Metric Page" className="flex-align-right float-right text-bold text-brand no-decoration">+ Add a Metric</Link>
</div> </div>
<table className="AdminTable"> <table className="AdminTable">
<thead> <thead>
......
import React, { Component, PropTypes } from "react"; import React, { Component, PropTypes } from "react";
import { Link } from "react-router";
import SegmentItem from "./SegmentItem.jsx"; import SegmentItem from "./SegmentItem.jsx";
...@@ -18,7 +19,7 @@ export default class SegmentsList extends Component { ...@@ -18,7 +19,7 @@ export default class SegmentsList extends Component {
<div className="my3"> <div className="my3">
<div className="flex mb1"> <div className="flex mb1">
<h2 className="px1 text-purple">Segments</h2> <h2 className="px1 text-purple">Segments</h2>
<a data-metabase-event="Data Model;Add Segment Page" className="flex-align-right float-right text-bold text-brand no-decoration" href={"/admin/datamodel/segment/create?table="+tableMetadata.id}>+ Add a Segment</a> <Link to={"/admin/datamodel/segment/create?table="+tableMetadata.id} data-metabase-event="Data Model;Add Segment Page" className="flex-align-right float-right text-bold text-brand no-decoration">+ Add a Segment</Link>
</div> </div>
<table className="AdminTable"> <table className="AdminTable">
<thead> <thead>
......
...@@ -21,13 +21,13 @@ import * as metadataActions from "../metadata"; ...@@ -21,13 +21,13 @@ import * as metadataActions from "../metadata";
const mapStateToProps = (state, props) => { const mapStateToProps = (state, props) => {
return { return {
databaseId: state.router && state.router.params && parseInt(state.router.params.databaseId), databaseId: parseInt(props.params.databaseId),
tableId: state.router && state.router.params && parseInt(state.router.params.tableId), tableId: parseInt(props.params.tableId),
onChangeLocation: props.onChangeLocation, onChangeLocation: props.onChangeLocation,
databases: getDatabases(state), databases: getDatabases(state, props),
idfields: getDatabaseIdfields(state), idfields: getDatabaseIdfields(state, props),
databaseMetadata: getEditingDatabaseWithTableMetadataStrengths(state), databaseMetadata: getEditingDatabaseWithTableMetadataStrengths(state, props),
editingTable: getEditingTable(state) editingTable: getEditingTable(state, props)
} }
} }
......
import React, { Component, PropTypes } from "react"; import React, { Component, PropTypes } from "react";
import { Link } from "react-router";
import FormLabel from "../components/FormLabel.jsx"; import FormLabel from "../components/FormLabel.jsx";
import FormInput from "../components/FormInput.jsx"; import FormInput from "../components/FormInput.jsx";
...@@ -53,7 +54,7 @@ export default class MetricForm extends Component { ...@@ -53,7 +54,7 @@ export default class MetricForm extends Component {
return ( return (
<div> <div>
<button className={cx("Button", { "Button--primary": !invalid, "disabled": invalid })} onClick={handleSubmit}>Save changes</button> <button className={cx("Button", { "Button--primary": !invalid, "disabled": invalid })} onClick={handleSubmit}>Save changes</button>
<a className="Button Button--borderless mx1" href={"/admin/datamodel/database/" + tableMetadata.db_id + "/table/" + tableMetadata.id}>Cancel</a> <Link to={"/admin/datamodel/database/" + tableMetadata.db_id + "/table/" + tableMetadata.id} className="Button Button--borderless mx1">Cancel</Link>
</div> </div>
) )
} }
......
...@@ -9,9 +9,9 @@ import { connect } from "react-redux"; ...@@ -9,9 +9,9 @@ import { connect } from "react-redux";
const mapStateToProps = (state, props) => { const mapStateToProps = (state, props) => {
return { return {
...revisionHistorySelectors(state), ...revisionHistorySelectors(state, props),
entity: state.router && state.router.params && state.router.params.entity, entity: props.params.entity,
id: state.router && state.router.params && state.router.params.id id: props.params.id
} }
} }
......
import React, { Component, PropTypes } from "react"; import React, { Component, PropTypes } from "react";
import { Link } from "react-router";
import FormLabel from "../components/FormLabel.jsx"; import FormLabel from "../components/FormLabel.jsx";
import FormInput from "../components/FormInput.jsx"; import FormInput from "../components/FormInput.jsx";
...@@ -54,7 +55,7 @@ export default class SegmentForm extends Component { ...@@ -54,7 +55,7 @@ export default class SegmentForm extends Component {
return ( return (
<div> <div>
<button className={cx("Button", { "Button--primary": !invalid, "disabled": invalid })} onClick={handleSubmit}>Save changes</button> <button className={cx("Button", { "Button--primary": !invalid, "disabled": invalid })} onClick={handleSubmit}>Save changes</button>
<a className="Button Button--borderless mx1" href={"/admin/datamodel/database/" + tableMetadata.db_id + "/table/" + tableMetadata.id}>Cancel</a> <Link to={"/admin/datamodel/database/" + tableMetadata.db_id + "/table/" + tableMetadata.id} className="Button Button--borderless mx1">Cancel</Link>
</div> </div>
) )
} }
......
...@@ -3,17 +3,17 @@ import { createSelector } from 'reselect'; ...@@ -3,17 +3,17 @@ import { createSelector } from 'reselect';
import { computeMetadataStrength } from "metabase/lib/schema_metadata"; import { computeMetadataStrength } from "metabase/lib/schema_metadata";
const segmentsSelector = state => state.datamodel.segments; const segmentsSelector = (state, props) => state.datamodel.segments;
const metricsSelector = state => state.datamodel.metrics; const metricsSelector = (state, props) => state.datamodel.metrics;
const tableMetadataSelector = state => state.datamodel.tableMetadata; const tableMetadataSelector = (state, props) => state.datamodel.tableMetadata;
const previewSummarySelector = state => state.datamodel.previewSummary; const previewSummarySelector = (state, props) => state.datamodel.previewSummary;
const revisionObjectSelector = state => state.datamodel.revisionObject; const revisionObjectSelector = (state, props) => state.datamodel.revisionObject;
const idSelector = state => state.router.params.id == null ? null : parseInt(state.router.params.id); const idSelector = (state, props) => props.params.id == null ? null : parseInt(props.params.id);
const tableIdSelector = state => state.router.location.query.table == null ? null : parseInt(state.router.location.query.table); const tableIdSelector = (state, props) => props.location.query.table == null ? null : parseInt(props.location.query.table);
const userSelector = state => state.currentUser; const userSelector = (state, props) => state.currentUser;
export const segmentEditSelectors = createSelector( export const segmentEditSelectors = createSelector(
segmentsSelector, segmentsSelector,
...@@ -73,9 +73,9 @@ export const revisionHistorySelectors = createSelector( ...@@ -73,9 +73,9 @@ export const revisionHistorySelectors = createSelector(
); );
export const getDatabases = state => state.datamodel.databases; export const getDatabases = (state, props) => state.datamodel.databases;
export const getDatabaseIdfields = state => state.datamodel.idfields; export const getDatabaseIdfields = (state, props) => state.datamodel.idfields;
export const getEditingTable = state => state.datamodel.editingTable; export const getEditingTable = (state, props) => state.datamodel.editingTable;
export const getEditingDatabaseWithTableMetadataStrengths = createSelector( export const getEditingDatabaseWithTableMetadataStrengths = createSelector(
...@@ -93,4 +93,3 @@ export const getEditingDatabaseWithTableMetadataStrengths = createSelector( ...@@ -93,4 +93,3 @@ export const getEditingDatabaseWithTableMetadataStrengths = createSelector(
return database; return database;
} }
); );
import React, { Component, PropTypes } from "react"; import React, { Component, PropTypes } from "react";
import { Link } from "react-router";
import _ from "underscore"; import _ from "underscore";
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper.jsx"; import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper.jsx";
...@@ -183,7 +184,7 @@ export default class AdminPeople extends Component { ...@@ -183,7 +184,7 @@ export default class AdminPeople extends Component {
<PasswordReveal password={user.password} /> <PasswordReveal password={user.password} />
<div style={{paddingLeft: "5em", paddingRight: "5em"}} className="pt4 text-centered">If you want to be able to send email invites, just go to the <a className="link text-bold" href="/admin/settings/?section=Email">Email Settings</a> page.</div> <div style={{paddingLeft: "5em", paddingRight: "5em"}} className="pt4 text-centered">If you want to be able to send email invites, just go to the <Link to="/admin/settings/email" className="link text-bold">Email Settings</Link> page.</div>
</div> </div>
<div className="Form-actions"> <div className="Form-actions">
......
import React, { Component, PropTypes } from "react"; import React, { Component, PropTypes } from "react";
import { Link } from "react-router";
import { connect } from "react-redux"; import { connect } from "react-redux";
import MetabaseAnalytics from "metabase/lib/analytics"; import MetabaseAnalytics from "metabase/lib/analytics";
...@@ -24,11 +25,10 @@ import * as settingsActions from "../settings"; ...@@ -24,11 +25,10 @@ import * as settingsActions from "../settings";
const mapStateToProps = (state, props) => { const mapStateToProps = (state, props) => {
return { return {
refreshSiteSettings: props.refreshSiteSettings, settings: getSettings(state, props),
settings: getSettings(state), sections: getSections(state, props),
sections: getSections(state), activeSection: getActiveSection(state, props),
activeSection: getActiveSection(state), newVersionAvailable: getNewVersionAvailable(state, props)
newVersionAvailable: getNewVersionAvailable(state)
} }
} }
...@@ -54,7 +54,7 @@ export default class SettingsEditorApp extends Component { ...@@ -54,7 +54,7 @@ export default class SettingsEditorApp extends Component {
}; };
componentWillMount() { componentWillMount() {
this.props.initializeSettings(this.props.refreshSiteSettings); this.props.initializeSettings();
} }
updateSetting(setting, value) { updateSetting(setting, value) {
...@@ -160,10 +160,10 @@ export default class SettingsEditorApp extends Component { ...@@ -160,10 +160,10 @@ export default class SettingsEditorApp extends Component {
return ( return (
<li key={section.name}> <li key={section.name}>
<a href={"/admin/settings/?section=" + section.name} className={classes}> <Link to={"/admin/settings/" + section.slug} className={classes}>
<span>{section.name}</span> <span>{section.name}</span>
{newVersionIndicator} {newVersionIndicator}
</a> </Link>
</li> </li>
); );
}); });
......
...@@ -2,6 +2,7 @@ import _ from "underscore"; ...@@ -2,6 +2,7 @@ import _ from "underscore";
import { createSelector } from "reselect"; import { createSelector } from "reselect";
import MetabaseSettings from "metabase/lib/settings"; import MetabaseSettings from "metabase/lib/settings";
import { slugify } from "metabase/lib/formatting";
const SECTIONS = [ const SECTIONS = [
{ {
...@@ -149,6 +150,9 @@ const SECTIONS = [ ...@@ -149,6 +150,9 @@ const SECTIONS = [
] ]
} }
]; ];
for (const section of SECTIONS) {
section.slug = slugify(section.name);
}
export const getSettings = state => state.settings.settings; export const getSettings = state => state.settings.settings;
...@@ -183,14 +187,14 @@ export const getSections = createSelector( ...@@ -183,14 +187,14 @@ export const getSections = createSelector(
} }
); );
export const getActiveSectionName = (state) => state.router && state.router.location && state.router.location.query.section export const getActiveSectionName = (state, props) => props.params.section
export const getActiveSection = createSelector( export const getActiveSection = createSelector(
getActiveSectionName, getActiveSectionName,
getSections, getSections,
(section = "Setup", sections) => { (section = "setup", sections) => {
if (sections) { if (sections) {
return _.findWhere(sections, {name: section}); return _.findWhere(sections, { slug: section });
} else { } else {
return null; return null;
} }
......
import { handleActions, combineReducers, AngularResourceProxy, createThunkAction } from "metabase/lib/redux"; import { handleActions, combineReducers, AngularResourceProxy, createThunkAction } from "metabase/lib/redux";
import MetabaseSettings from 'metabase/lib/settings';
import _ from "underscore";
// resource wrappers // resource wrappers
const SettingsApi = new AngularResourceProxy("Settings", ["list", "put"]); const SettingsApi = new AngularResourceProxy("Settings", ["list", "put"]);
const EmailApi = new AngularResourceProxy("Email", ["updateSettings", "sendTest"]); const EmailApi = new AngularResourceProxy("Email", ["updateSettings", "sendTest"]);
const SlackApi = new AngularResourceProxy("Slack", ["updateSettings"]); const SlackApi = new AngularResourceProxy("Slack", ["updateSettings"]);
const SessionApi = new AngularResourceProxy("Session", ["properties"]);
async function refreshSiteSettings() {
const settings = await SessionApi.properties();
MetabaseSettings.setAll(_.omit(settings, (value, key) => key.indexOf('$') === 0));
}
async function loadSettings() { async function loadSettings() {
try { try {
...@@ -22,14 +30,10 @@ async function loadSettings() { ...@@ -22,14 +30,10 @@ async function loadSettings() {
} }
// initializeSettings // initializeSettings
export const initializeSettings = createThunkAction("INITIALIZE_SETTINGS", function(refreshSiteSettings) { export const initializeSettings = createThunkAction("INITIALIZE_SETTINGS", function() {
return async function(dispatch, getState) { return async function(dispatch, getState) {
try { try {
let settings = await loadSettings(); return await loadSettings();
return {
settings,
refreshSiteSettings
}
} catch(error) { } catch(error) {
console.log("error fetching settings", error); console.log("error fetching settings", error);
throw error; throw error;
...@@ -40,8 +44,6 @@ export const initializeSettings = createThunkAction("INITIALIZE_SETTINGS", funct ...@@ -40,8 +44,6 @@ export const initializeSettings = createThunkAction("INITIALIZE_SETTINGS", funct
// updateSetting // updateSetting
export const updateSetting = createThunkAction("UPDATE_SETTING", function(setting) { export const updateSetting = createThunkAction("UPDATE_SETTING", function(setting) {
return async function(dispatch, getState) { return async function(dispatch, getState) {
const { settings: { refreshSiteSettings } } = getState();
try { try {
await SettingsApi.put({ key: setting.key }, setting); await SettingsApi.put({ key: setting.key }, setting);
refreshSiteSettings(); refreshSiteSettings();
...@@ -56,8 +58,6 @@ export const updateSetting = createThunkAction("UPDATE_SETTING", function(settin ...@@ -56,8 +58,6 @@ export const updateSetting = createThunkAction("UPDATE_SETTING", function(settin
// updateEmailSettings // updateEmailSettings
export const updateEmailSettings = createThunkAction("UPDATE_EMAIL_SETTINGS", function(settings) { export const updateEmailSettings = createThunkAction("UPDATE_EMAIL_SETTINGS", function(settings) {
return async function(dispatch, getState) { return async function(dispatch, getState) {
const { settings: { refreshSiteSettings } } = getState();
try { try {
await EmailApi.updateSettings(settings); await EmailApi.updateSettings(settings);
refreshSiteSettings(); refreshSiteSettings();
...@@ -84,8 +84,6 @@ export const sendTestEmail = createThunkAction("SEND_TEST_EMAIL", function() { ...@@ -84,8 +84,6 @@ export const sendTestEmail = createThunkAction("SEND_TEST_EMAIL", function() {
// updateSlackSettings // updateSlackSettings
export const updateSlackSettings = createThunkAction("UPDATE_SLACK_SETTINGS", function(settings) { export const updateSlackSettings = createThunkAction("UPDATE_SLACK_SETTINGS", function(settings) {
return async function(dispatch, getState) { return async function(dispatch, getState) {
const { settings: { refreshSiteSettings } } = getState();
try { try {
await SlackApi.updateSettings(settings); await SlackApi.updateSettings(settings);
refreshSiteSettings(); refreshSiteSettings();
...@@ -97,22 +95,15 @@ export const updateSlackSettings = createThunkAction("UPDATE_SLACK_SETTINGS", fu ...@@ -97,22 +95,15 @@ export const updateSlackSettings = createThunkAction("UPDATE_SLACK_SETTINGS", fu
}; };
}); });
// reducers // reducers
// this is a backwards compatibility thing with angular to allow programmatic route changes. remove/change this when going to ReduxRouter
const refreshSiteSettings = handleActions({
["INITIALIZE_SETTINGS"]: { next: (state, { payload }) => payload ? payload.refreshSiteSettings : state }
}, () => null);
const settings = handleActions({ const settings = handleActions({
["INITIALIZE_SETTINGS"]: { next: (state, { payload }) => payload ? payload.settings : state }, ["INITIALIZE_SETTINGS"]: { next: (state, { payload }) => payload },
["UPDATE_SETTING"]: { next: (state, { payload }) => payload }, ["UPDATE_SETTING"]: { next: (state, { payload }) => payload },
["UPDATE_EMAIL_SETTINGS"]: { next: (state, { payload }) => payload }, ["UPDATE_EMAIL_SETTINGS"]: { next: (state, { payload }) => payload },
["UPDATE_SLACK_SETTINGS"]: { next: (state, { payload }) => payload } ["UPDATE_SLACK_SETTINGS"]: { next: (state, { payload }) => payload }
}, []); }, []);
export default combineReducers({ export default combineReducers({
settings, settings
refreshSiteSettings
}); });
...@@ -2,263 +2,77 @@ ...@@ -2,263 +2,77 @@
import 'babel-polyfill'; import 'babel-polyfill';
import { registerAnalyticsClickListener } from "metabase/lib/analytics";
// angular: // angular:
import 'angular'; import 'angular';
import 'angular-cookies';
import 'angular-resource'; import 'angular-resource';
import 'angular-route';
// angular 3rd-party:
import 'angular-cookie'; import 'angular-cookie';
import 'angular-http-auth';
import "./controllers";
import "./services"; import "./services";
import React from "react"; angular
import ReactDOM from "react-dom"; .module('metabase', ['ngCookies', 'metabase.controllers'])
.run([function() {
import { Provider } from 'react-redux';
import { combineReducers } from "redux";
import { reducer as form } from "redux-form";
import { reduxReactRouter, routerStateReducer } from "redux-router";
import Routes from "./Routes.jsx";
import auth from "metabase/auth/auth";
/* ducks */
import metadata from "metabase/redux/metadata";
import requests from "metabase/redux/requests";
/* admin */
import settings from "metabase/admin/settings/settings";
import * as people from "metabase/admin/people/reducers";
import databases from "metabase/admin/databases/database";
import datamodel from "metabase/admin/datamodel/metadata";
/* dashboards */
import dashboard from "metabase/dashboard/dashboard";
import * as home from "metabase/home/reducers";
/* questions / query builder */
import questions from "metabase/questions/questions";
import labels from "metabase/questions/labels";
import undo from "metabase/questions/undo";
import * as qb from "metabase/query_builder/reducers";
/* data reference */
import reference from "metabase/reference/reference";
/* pulses */
import * as pulse from "metabase/pulse/reducers";
/* setup */
import * as setup from "metabase/setup/reducers";
/* user */
import * as user from "metabase/user/reducers";
import { currentUser, setUser } from "metabase/user";
import { registerAnalyticsClickListener } from "metabase/lib/analytics";
import { serializeCardForUrl, cleanCopyCard, urlForCardState } from "metabase/lib/card";
import { createStoreWithAngularScope } from "metabase/lib/redux";
const reducers = combineReducers({
form,
router: routerStateReducer,
// global reducers
auth,
currentUser,
metadata,
requests,
// main app reducers
dashboard,
home: combineReducers(home),
labels,
pulse: combineReducers(pulse),
qb: combineReducers(qb),
questions,
reference,
setup: combineReducers(setup),
undo,
user: combineReducers(user),
// admin reducers
databases,
datamodel: datamodel,
people: combineReducers(people),
settings
});
// Declare app level module which depends on filters, and services
angular.module('metabase', [
'ngRoute',
'ngCookies',
'metabase.controllers',
])
.run(["AppState", function(AppState) {
// initialize app state
AppState.init();
// start our analytics click listener
registerAnalyticsClickListener(); registerAnalyticsClickListener();
}]) }])
.config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) {
$locationProvider.html5Mode({
enabled: true,
requireBase: false
});
const route = {
template: '<div id="main" />',
controller: 'AppController',
resolve: {
appState: ["AppState", function(AppState) {
return AppState.init();
}]
}
};
$routeProvider.when('/admin/', { redirectTo: () => ('/admin/settings') });
$routeProvider.when('/auth/', { redirectTo: () => ('/auth/login') });
$routeProvider.when('/card/', { redirectTo: () => ("/questions/all") });
$routeProvider.when('/card/:cardId/:serializedCard', { redirectTo: (routeParams) => ("/card/"+routeParams.cardId+"#"+routeParams.serializedCard) });
$routeProvider.when('/q/:serializedCard', { redirectTo: (routeParams) => ("/q#"+routeParams.serializedCard) });
$routeProvider.otherwise(route);
}])
.controller('AppController', ['$scope', '$location', '$route', '$rootScope', '$timeout', 'ipCookie', 'AppState',
function($scope, $location, $route, $rootScope, $timeout, ipCookie, AppState) {
const props = {
onChangeLocation(url) {
$scope.$apply(() => $location.url(url));
},
refreshSiteSettings() {
$scope.$apply(() => AppState.refreshSiteSettings());
},
updateUrl: (card, isDirty=false, replaceState=false) => {
if (!card) {
return;
}
var copy = cleanCopyCard(card);
var newState = {
card: copy,
cardId: copy.id,
serializedCard: serializeCardForUrl(copy)
};
if (angular.equals(window.history.state, newState)) {
return;
}
var url = urlForCardState(newState, isDirty);
// if the serialized card is identical replace the previous state instead of adding a new one
// e.x. when saving a new card we want to replace the state and URL with one with the new card ID
replaceState = replaceState || (window.history.state && window.history.state.serializedCard === newState.serializedCard);
// ensure the digest cycle is run, otherwise pending location changes will prevent navigation away from query builder on the first click
$scope.$apply(() => {
// prevents infinite digest loop
// https://stackoverflow.com/questions/22914228/successfully-call-history-pushstate-from-angular-without-inifinite-digest
$location.url(url);
$location.replace();
if (replaceState) {
window.history.replaceState(newState, null, $location.absUrl());
} else {
window.history.pushState(newState, null, $location.absUrl());
}
});
}
};
const store = createStoreWithAngularScope($scope, $location, reducers, {currentUser: AppState.model.currentUser});
const element = document.getElementById("main");
ReactDOM.render(
<Provider store={store}>
<Routes {...props} />
</Provider>,
element
);
$scope.$on("$destroy", function() { angular
ReactDOM.unmountComponentAtNode(element); .module('metabase.controllers', ['metabase.services'])
}); .controller('Metabase', [function() {
}]);
// ANGULAR_HACK™: this seems like the easiest way to keep the redux store up to date with the currentUser :-/ import Routes from "./Routes.jsx";
let userSyncTimer = setInterval(() => {
if (store.getState().currentUser !== AppState.model.currentUser) {
store.dispatch(setUser(AppState.model.currentUser));
}
}, 250);
$scope.$on("$destroy", () => clearInterval(userSyncTimer));
// HACK: prevent reloading controllers as the URL changes
let route = $route.current;
$scope.$on('$locationChangeSuccess', function (event) {
$route.current = route;
});
}
])
// async function refreshCurrentUser() {
// let response = await fetch("/api/user/current", { credentials: 'same-origin' });
// if (response.status === 200) {
// return await response.json();
// }
// }
// This is the entry point for our Redux application which is fired once on page load.
// We attempt to:
// 1. Identify the currently authenticated user, if possible
// 2. Create the application Redux store
// 3. Render our React/Redux application using a single Redux `Provider`
// window.onload = async function() {
// // refresh site settings
// // fetch current user
// let user = await refreshCurrentUser();
// // initialize redux store
// // NOTE: we want to initialize the store with the active user because it makes lots of other initialization steps simpler
// let store = createMetabaseStore(reducers, {currentUser: user});
// // route change listener
// // set app context (for navbar)
// // guard admin urls and redirect to auth pages
// // events fired
// // appstate:user - currentUser changed
// // appstate:site-settings - changes made to current app settings
// // appstate:context-changed - app section changed (only used by navbar?)
// // listeners
// // $locationChangeSuccess - analytics route tracking
// // $routeChangeSuccess - route protection logic (updates context and redirects urls based on user perms)
// // appstate:login - refresh the currentUser
// // appstate:logout - null the currentUser and make sure cookie is cleared and session deleted
// // appstate:site-settings - if GA setting changed then update analytics appropriately
// // event:auth-loginRequired - (fired by angular service middleware) lets us know an api call returned a 401
// // event:auth-forbidden - (fired by angular service middleware) lets us know an api call returned a 403
// // start analytics
// let reduxApp = document.getElementById("redux-app"); import React from 'react'
// render( import ReactDOM from 'react-dom'
// <Provider store={store}> import { Provider } from 'react-redux'
// <div className="full full-height"> import { getStore } from './store'
// <div className="Nav"><Navbar /></div>
// <main className="relative full-height z1"><Routes /></main> // START react-router-redux
// </div> import { Router, browserHistory } from "react-router";
// </Provider>, import { syncHistoryWithStore } from 'react-router-redux'
// reduxApp // END react-router-redux
// );
// } // START redux-router
// import { ReduxRouter } from "redux-router";
// END redux-router
import { refreshCurrentUser } from "./user";
async function init() {
// const user = await getCurrentUser();
// START react-router-redux
const store = getStore(browserHistory);
await store.dispatch(refreshCurrentUser());
const history = syncHistoryWithStore(browserHistory, store);
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
{Routes}
</Router>
</Provider>,
document.getElementById('root')
)
// END react-router-redux
// START redux-router
// const store = getStore(Routes);
// ReactDOM.render(
// <Provider store={store}>
// <ReduxRouter>
// {Routes}
// </ReduxRouter>
// </Provider>,
// document.getElementById('root')
// )
// END redux-router
}
if (document.readyState != 'loading') {
init();
} else {
document.addEventListener('DOMContentLoaded', init);
}
import { handleActions, combineReducers, AngularResourceProxy, createThunkAction } from "metabase/lib/redux"; import { handleActions, combineReducers, AngularResourceProxy, createThunkAction } from "metabase/lib/redux";
import { push } from "react-router-redux";
import MetabaseCookies from "metabase/lib/cookies"; import MetabaseCookies from "metabase/lib/cookies";
import MetabaseUtils from "metabase/lib/utils"; import MetabaseUtils from "metabase/lib/utils";
import MetabaseAnalytics from "metabase/lib/analytics"; import MetabaseAnalytics from "metabase/lib/analytics";
import { clearGoogleAuthCredentials } from "metabase/lib/auth"; import { clearGoogleAuthCredentials } from "metabase/lib/auth";
import { refreshCurrentUser } from "metabase/user";
// resource wrappers // resource wrappers
const SessionApi = new AngularResourceProxy("Session", ["create", "createWithGoogleAuth", "delete", "reset_password"]); const SessionApi = new AngularResourceProxy("Session", ["create", "createWithGoogleAuth", "delete", "reset_password"]);
// login // login
export const login = createThunkAction("AUTH_LOGIN", function(credentials, onChangeLocation) { export const login = createThunkAction("AUTH_LOGIN", function(credentials) {
return async function(dispatch, getState) { return async function(dispatch, getState) {
if (!MetabaseUtils.validEmail(credentials.email)) { if (!MetabaseUtils.validEmail(credentials.email)) {
...@@ -29,7 +32,9 @@ export const login = createThunkAction("AUTH_LOGIN", function(credentials, onCha ...@@ -29,7 +32,9 @@ export const login = createThunkAction("AUTH_LOGIN", function(credentials, onCha
MetabaseAnalytics.trackEvent('Auth', 'Login'); MetabaseAnalytics.trackEvent('Auth', 'Login');
// TODO: redirect after login (carry user to intended destination) // TODO: redirect after login (carry user to intended destination)
// this is ridiculously stupid. we have to wait (300ms) for the cookie to actually be set in the browser :( // this is ridiculously stupid. we have to wait (300ms) for the cookie to actually be set in the browser :(
setTimeout(() => onChangeLocation("/"), 300); // setTimeout(() => dispatch(push("/")), 300);
await dispatch(refreshCurrentUser());
dispatch(push("/"));
} catch (error) { } catch (error) {
return error; return error;
...@@ -39,7 +44,7 @@ export const login = createThunkAction("AUTH_LOGIN", function(credentials, onCha ...@@ -39,7 +44,7 @@ export const login = createThunkAction("AUTH_LOGIN", function(credentials, onCha
// login Google // login Google
export const loginGoogle = createThunkAction("AUTH_LOGIN_GOOGLE", function(googleUser, onChangeLocation) { export const loginGoogle = createThunkAction("AUTH_LOGIN_GOOGLE", function(googleUser) {
return async function(dispatch, getState) { return async function(dispatch, getState) {
try { try {
let newSession = await SessionApi.createWithGoogleAuth({ let newSession = await SessionApi.createWithGoogleAuth({
...@@ -53,13 +58,13 @@ export const loginGoogle = createThunkAction("AUTH_LOGIN_GOOGLE", function(googl ...@@ -53,13 +58,13 @@ export const loginGoogle = createThunkAction("AUTH_LOGIN_GOOGLE", function(googl
// TODO: redirect after login (carry user to intended destination) // TODO: redirect after login (carry user to intended destination)
// this is ridiculously stupid. we have to wait (300ms) for the cookie to actually be set in the browser :( // this is ridiculously stupid. we have to wait (300ms) for the cookie to actually be set in the browser :(
setTimeout(() => onChangeLocation("/"), 300); setTimeout(() => dispatch(push("/")), 300);
} catch (error) { } catch (error) {
clearGoogleAuthCredentials(); clearGoogleAuthCredentials();
// If we see a 428 ("Precondition Required") that means we need to show the "No Metabase account exists for this Google Account" page // If we see a 428 ("Precondition Required") that means we need to show the "No Metabase account exists for this Google Account" page
if (error.status === 428) { if (error.status === 428) {
onChangeLocation('/auth/google_no_mb_account'); dispatch(push("/auth/google_no_mb_account"));
} else { } else {
return error; return error;
} }
...@@ -68,7 +73,7 @@ export const loginGoogle = createThunkAction("AUTH_LOGIN_GOOGLE", function(googl ...@@ -68,7 +73,7 @@ export const loginGoogle = createThunkAction("AUTH_LOGIN_GOOGLE", function(googl
}); });
// logout // logout
export const logout = createThunkAction("AUTH_LOGOUT", function(onChangeLocation) { export const logout = createThunkAction("AUTH_LOGOUT", function() {
return async function(dispatch, getState) { return async function(dispatch, getState) {
// TODO: as part of a logout we want to clear out any saved state that we have about anything // TODO: as part of a logout we want to clear out any saved state that we have about anything
...@@ -79,12 +84,12 @@ export const logout = createThunkAction("AUTH_LOGOUT", function(onChangeLocation ...@@ -79,12 +84,12 @@ export const logout = createThunkAction("AUTH_LOGOUT", function(onChangeLocation
} }
MetabaseAnalytics.trackEvent('Auth', 'Logout'); MetabaseAnalytics.trackEvent('Auth', 'Logout');
setTimeout(() => onChangeLocation("/auth/login"), 300); setTimeout(() => dispatch(push("/auth/login")), 300);
}; };
}); });
// passwordReset // passwordReset
export const passwordReset = createThunkAction("AUTH_PASSWORD_RESET", function(token, credentials, onChangeLocation) { export const passwordReset = createThunkAction("AUTH_PASSWORD_RESET", function(token, credentials) {
return async function(dispatch, getState) { return async function(dispatch, getState) {
if (credentials.password !== credentials.password2) { if (credentials.password !== credentials.password2) {
......
import React from 'react' import React from 'react';
import { Link } from "react-router";
const BackToLogin = () => const BackToLogin = () =>
<a className="link block" href="/auth/login">Back to login</a> <Link to="/auth/login" className="link block">Back to login</Link>
export default BackToLogin; export default BackToLogin;
import React, { Component, PropTypes } from "react"; import React, { Component, PropTypes } from "react";
import { findDOMNode } from "react-dom"; import { findDOMNode } from "react-dom";
import { Link } from "react-router";
import { connect } from "react-redux"; import { connect } from "react-redux";
import cx from "classnames"; import cx from "classnames";
...@@ -19,8 +20,7 @@ import * as authActions from "../auth"; ...@@ -19,8 +20,7 @@ import * as authActions from "../auth";
const mapStateToProps = (state, props) => { const mapStateToProps = (state, props) => {
return { return {
loginError: state.auth && state.auth.loginError, loginError: state.auth && state.auth.loginError,
user: state.currentUser, user: state.currentUser
onChangeLocation: props.onChangeLocation
} }
} }
...@@ -57,7 +57,7 @@ export default class LoginApp extends Component { ...@@ -57,7 +57,7 @@ export default class LoginApp extends Component {
this.validateForm(); this.validateForm();
const { loginGoogle, onChangeLocation } = this.props; const { loginGoogle } = this.props;
let ssoLoginButton = findDOMNode(this.refs.ssoLoginButton); let ssoLoginButton = findDOMNode(this.refs.ssoLoginButton);
...@@ -74,7 +74,7 @@ export default class LoginApp extends Component { ...@@ -74,7 +74,7 @@ export default class LoginApp extends Component {
cookiepolicy: 'single_host_origin', cookiepolicy: 'single_host_origin',
}); });
auth2.attachClickHandler(ssoLoginButton, {}, auth2.attachClickHandler(ssoLoginButton, {},
(googleUser) => loginGoogle(googleUser, onChangeLocation), (googleUser) => loginGoogle(googleUser),
(error) => console.error('There was an error logging in', error) (error) => console.error('There was an error logging in', error)
); );
}) })
...@@ -89,14 +89,6 @@ export default class LoginApp extends Component { ...@@ -89,14 +89,6 @@ export default class LoginApp extends Component {
this.validateForm(); this.validateForm();
} }
componentWillReceiveProps(newProps) {
const { user, onChangeLocation } = newProps;
// if we already have a user then we shouldn't be logging in
if (user) {
onChangeLocation("/");
}
}
onChange(fieldName, fieldValue) { onChange(fieldName, fieldValue) {
this.setState({ credentials: { ...this.state.credentials, [fieldName]: fieldValue }}); this.setState({ credentials: { ...this.state.credentials, [fieldName]: fieldValue }});
} }
...@@ -104,10 +96,10 @@ export default class LoginApp extends Component { ...@@ -104,10 +96,10 @@ export default class LoginApp extends Component {
formSubmitted(e) { formSubmitted(e) {
e.preventDefault(); e.preventDefault();
let { login, onChangeLocation } = this.props; let { login } = this.props;
let { credentials } = this.state; let { credentials } = this.state;
login(credentials, onChangeLocation); login(credentials);
} }
render() { render() {
...@@ -158,7 +150,7 @@ export default class LoginApp extends Component { ...@@ -158,7 +150,7 @@ export default class LoginApp extends Component {
<button className={cx("Button Grid-cell", {'Button--primary': this.state.valid})} disabled={!this.state.valid}> <button className={cx("Button Grid-cell", {'Button--primary': this.state.valid})} disabled={!this.state.valid}>
Sign in Sign in
</button> </button>
<a className="Grid-cell py2 sm-py0 text-grey-3 md-text-right text-centered flex-full link" href="/auth/forgot_password" onClick={(e) => { window.OSX ? window.OSX.resetPassword() : null }}>I seem to have forgotten my password</a> <Link to="/auth/forgot_password" className="Grid-cell py2 sm-py0 text-grey-3 md-text-right text-centered flex-full link" onClick={(e) => { window.OSX ? window.OSX.resetPassword() : null }}>I seem to have forgotten my password</Link>
</div> </div>
</form> </form>
</div> </div>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment