Skip to content
Snippets Groups Projects
Commit 450f8ae0 authored by Tom Robinson's avatar Tom Robinson Committed by GitHub
Browse files

Merge pull request #4623 from metabase/cleanup-routes

Cleanup frontend question/dashboard routes
parents 046bc832 2f7b8582
No related merge requests found
Showing
with 104 additions and 89 deletions
......@@ -3,6 +3,8 @@ import { Link } from "react-router";
import ModalContent from "metabase/components/ModalContent.jsx";
import * as Urls from "metabase/lib/urls";
export default class CreatedDatabaseModal extends Component {
static propTypes = {
databaseId: PropTypes.number.isRequired,
......@@ -22,7 +24,7 @@ export default class CreatedDatabaseModal extends Component {
We're analyzing its schema now to make some educated guesses about its
metadata. <Link to={"/admin/datamodel/database/"+databaseId}>View this
database</Link> in the Data Model section to see what we've found and to
make edits, or <Link to={"/q#?db="+databaseId}>ask a question</Link> about
make edits, or <Link to={Urls.question(null, `?db=${databaseId}`)}>ask a question</Link> about
this database.
</p>
</div>
......
......@@ -2,7 +2,7 @@ import React, { Component, PropTypes } from "react";
import GuiQueryEditor from "metabase/query_builder/components/GuiQueryEditor.jsx";
import { serializeCardForUrl } from "metabase/lib/card";
import * as Urls from "metabase/lib/urls";
import cx from "classnames";
......@@ -56,7 +56,7 @@ export default class PartialQueryBuilder extends Component {
}
}
};
let previewUrl = "/q#" + serializeCardForUrl(previewCard);
let previewUrl = Urls.question(null, previewCard);
const onChange = (query) => {
this.props.onChange(query);
......
......@@ -9,7 +9,7 @@ import Confirm from "metabase/components/Confirm";
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
import { CardApi, DashboardApi } from "metabase/services";
import Urls from "metabase/lib/urls";
import * as Urls from "metabase/lib/urls";
import MetabaseAnalytics from "metabase/lib/analytics";
......@@ -161,7 +161,7 @@ export const PublicLinksQuestionListing = () =>
load={CardApi.listPublic}
revoke={CardApi.deletePublicLink}
type='Public Card Listing'
getUrl={({ id }) => Urls.card(id)}
getUrl={({ id }) => Urls.question(id)}
getPublicUrl={({ public_uuid }) => window.location.origin + Urls.publicCard(public_uuid)}
noLinksMessage="No questions have been publicly shared yet."
/>;
......@@ -177,7 +177,7 @@ export const EmbeddedDashboardListing = () =>
export const EmbeddedQuestionListing = () =>
<PublicLinksListing
load={CardApi.listEmbeddable}
getUrl={({ id }) => Urls.card(id)}
getUrl={({ id }) => Urls.question(id)}
type='Embedded Card Listing'
noLinksMessage="No questions have been embedded yet."
/>;
......@@ -5,7 +5,7 @@ import Icon from 'metabase/components/Icon.jsx';
import ModalContent from "metabase/components/ModalContent.jsx";
import SortableItemList from 'metabase/components/SortableItemList.jsx';
import Urls from "metabase/lib/urls";
import * as Urls from "metabase/lib/urls";
import { DashboardApi } from "metabase/services";
import moment from 'moment';
......
import React, { Component, PropTypes } from "react";
import { Link } from "react-router";
import * as Urls from "metabase/lib/urls";
export default class NotFound extends Component {
render() {
return (
......@@ -11,7 +13,7 @@ export default class NotFound extends Component {
<p className="h4">You might've been tricked by a ninja, but in all likelihood, you were just given a bad link.</p>
<p className="h4 my4">You can always:</p>
<div className="flex align-center">
<Link to="/q" className="Button Button--primary">
<Link to={Urls.question()} className="Button Button--primary">
<div className="p1">Ask a new question.</div>
</Link>
<span className="mx2">or</span>
......
......@@ -5,7 +5,7 @@ import { connect } from "react-redux";
import EmbedWidget from "metabase/public/components/widgets/EmbedWidget";
import Urls from "metabase/lib/urls";
import * as Urls from "metabase/lib/urls";
import { createPublicLink, deletePublicLink, updateEnableEmbedding, updateEmbeddingParams } from "../dashboard";
......
......@@ -6,7 +6,7 @@ import LoadingAndErrorWrapper from 'metabase/components/LoadingAndErrorWrapper.j
import ActivityItem from './ActivityItem.jsx';
import ActivityStory from './ActivityStory.jsx';
import Urls from 'metabase/lib/urls';
import * as Urls from "metabase/lib/urls";
export default class Activity extends Component {
......@@ -213,7 +213,7 @@ export default class Activity extends Component {
case "dashboard-remove-cards":
description.body = item.details.dashcards[0].name;
if (item.details.dashcards[0].exists) {
description.bodyLink = Urls.card(item.details.dashcards[0].card_id);
description.bodyLink = Urls.question(item.details.dashcards[0].card_id);
}
break;
case "metric-create":
......
......@@ -2,6 +2,7 @@ import React, { Component, PropTypes } from "react";
import { Link } from "react-router";
import MetabaseSettings from "metabase/lib/settings";
import * as Urls from "metabase/lib/urls";
export default class NewUserOnboardingModal extends Component {
constructor(props, context) {
......@@ -84,7 +85,7 @@ export default class NewUserOnboardingModal extends Component {
{this.renderStep()}
<span className="flex-align-right">
<a className="text-underline-hover cursor-pointer mr3" onClick={() => (this.closeModal())}>skip for now</a>
<Link to="/q#?tutorial" className="Button Button--primary">Let's do it!</Link>
<Link to={Urls.question(null, "?tutorial")} className="Button Button--primary">Let's do it!</Link>
</span>
</div>
</div>
......
......@@ -3,7 +3,7 @@ import { Link } from "react-router";
import Icon from "metabase/components/Icon.jsx";
import SidebarSection from "./SidebarSection.jsx";
import Urls from "metabase/lib/urls";
import * as Urls from "metabase/lib/urls";
import { normal } from "metabase/lib/colors";
......
import _ from "underscore";
import Query, { createQuery } from "metabase/lib/query";
import Utils from "metabase/lib/utils";
import * as Urls from "metabase/lib/urls";
import { CardApi } from "metabase/services";
......@@ -24,10 +25,10 @@ export function startNewCard(type, databaseId, tableId) {
}
// load a card either by ID or from a base64 serialization. if both are present then they are merged, which the serialized version taking precedence
// TODO: move to redux
export async function loadCard(cardId) {
try {
let card = await CardApi.get({ "cardId": cardId });
return card && cleanCopyCard(card);
return await CardApi.get({ "cardId": cardId });
} catch (error) {
console.log("error loading card", error);
throw error;
......@@ -111,16 +112,10 @@ export function b64url_to_utf8(b64url) {
}
export function urlForCardState(state, dirty) {
var url;
if (state.cardId) {
url = "/card/" + state.cardId;
} else {
url = "/q";
}
if (state.serializedCard && dirty) {
url += "#" + state.serializedCard;
}
return url;
return Urls.question(
state.cardId,
(state.serializedCard && dirty) ? state.serializedCard : ""
);
}
export function cleanCopyCard(card) {
......
import { serializeCardForUrl } from "metabase/lib/card";
// provides functions for building urls to things we care about
var Urls = {
q: function(card) {
return "/q#" + serializeCardForUrl(card);
},
card: function(card_id) {
// NOTE that this is for an ephemeral card link, not an editable card
return "/card/"+card_id;
},
dashboard: function(dashboard_id) {
return "/dash/"+dashboard_id;
},
export function question(cardId, cardOrHash = "") {
if (cardOrHash && typeof cardOrHash === "object") {
cardOrHash = serializeCardForUrl(cardOrHash);
}
if (cardOrHash && cardOrHash.charAt(0) !== "#") {
cardOrHash = "#" + cardOrHash;
}
// NOTE that this is for an ephemeral card link, not an editable card
return cardId != null
? `/question/${cardId}${cardOrHash}`
: `/question${cardOrHash}`;
}
modelToUrl: function(model, model_id) {
switch (model) {
case "card": return Urls.card(model_id);
case "dashboard": return Urls.dashboard(model_id);
case "pulse": return Urls.pulse(model_id);
default: return null;
}
},
export function dashboard(dashboardId) {
return `/dashboard/${dashboardId}`;
}
pulse: function(pulse_id) {
return "/pulse/#"+pulse_id;
},
export function modelToUrl(model, modelId) {
switch (model) {
case "card":
return question(modelId);
case "dashboard":
return dashboard(modelId);
case "pulse":
return pulse(modelId);
default:
return null;
}
}
tableRowsQuery: function(database_id, table_id, metric_id, segment_id) {
let url = "/q#?db="+database_id+"&table="+table_id;
export function pulse(pulseId) {
return `/pulse/#${pulseId}`;
}
if (metric_id) {
url = url + "&metric="+metric_id;
}
export function tableRowsQuery(databaseId, tableId, metricId, segmentId) {
let query = `?db=${databaseId}&table=${tableId}`;
if (segment_id) {
url = url + "&segment="+segment_id;
}
if (metricId) {
query += `&metric=${metricId}`;
}
return url;
},
if (segmentId) {
query += `&segment=${segmentId}`;
}
collection(collection) {
return `/questions/collections/${encodeURIComponent(collection.slug)}`;
},
return question(null, query);
}
label(label) {
return `/questions/search?label=${encodeURIComponent(label.slug)}`;
},
export function collection(collection) {
return `/questions/collections/${encodeURIComponent(collection.slug)}`;
}
publicCard(uuid, type = null) {
return `/public/question/${uuid}` + (type ? `.${type}` : ``);
},
export function label(label) {
return `/questions/search?label=${encodeURIComponent(label.slug)}`;
}
publicDashboard(uuid) {
return `/public/dashboard/${uuid}`;
},
export function publicCard(uuid, type = null) {
return `/public/question/${uuid}` + (type ? `.${type}` : ``);
}
embedCard(token, type = null) {
return `/embed/question/${token}` + (type ? `.${type}` : ``);
},
export function publicDashboard(uuid) {
return `/public/dashboard/${uuid}`;
}
export default Urls;
export function embedCard(token, type = null) {
return `/embed/question/${token}` + (type ? `.${type}` : ``);
}
......@@ -3,12 +3,13 @@ import { connect } from "react-redux";
import { Link } from "react-router";
import OnClickOutsideWrapper from 'metabase/components/OnClickOutsideWrapper.jsx';
import MetabaseAnalytics from "metabase/lib/analytics";
import CreateDashboardModal from "metabase/components/CreateDashboardModal.jsx";
import Modal from "metabase/components/Modal.jsx";
import ConstrainToScreen from "metabase/components/ConstrainToScreen";
import MetabaseAnalytics from "metabase/lib/analytics";
import * as Urls from "metabase/lib/urls";
import _ from "underscore";
import cx from "classnames";
......@@ -62,7 +63,7 @@ export default class DashboardsDropdown extends Component {
try {
let action = await createDashboard(newDashboard, true);
// FIXME: this doesn't feel right...
this.props.onChangeLocation(`/dash/${action.payload.id}`);
this.props.onChangeLocation(Urls.dashboard(action.payload.id));
} catch (e) {
console.log("createDashboard failed", e);
}
......@@ -137,7 +138,7 @@ export default class DashboardsDropdown extends Component {
<ul className="NavDropdown-content-layer">
{ dashboards.map(dash =>
<li key={dash.id} className="block">
<Link to={"/dash/"+dash.id} data-metabase-event={"Navbar;Dashboard Dropdown;Open Dashboard;"+dash.id} className="Dropdown-item block text-white no-decoration" onClick={this.closeDropdown}>
<Link to={Urls.dashboard(dash.id)} data-metabase-event={"Navbar;Dashboard Dropdown;Open Dashboard;"+dash.id} className="Dropdown-item block text-white no-decoration" onClick={this.closeDropdown}>
<div className="flex text-bold">
{dash.name}
</div>
......
......@@ -11,6 +11,8 @@ import LogoIcon from "metabase/components/LogoIcon.jsx";
import DashboardsDropdown from "metabase/nav/containers/DashboardsDropdown.jsx";
import ProfileLink from "metabase/nav/components/ProfileLink.jsx";
import * as Urls from "metabase/lib/urls";
import { getPath, getContext, getUser } from "../selectors";
const mapStateToProps = (state, props) => ({
......@@ -115,7 +117,13 @@ export default class Navbar extends Component {
</li>
<li className="pl3">
<DashboardsDropdown {...this.props}>
<a data-metabase-event={"Navbar;Dashboard Dropdown;Toggle"} style={this.styles.navButton} className={cx("NavDropdown-button NavItem text-white text-bold cursor-pointer px2 flex align-center transition-background", {"NavItem--selected": this.isActive("/dash/")})}>
<a
data-metabase-event={"Navbar;Dashboard Dropdown;Toggle"}
style={this.styles.navButton}
className={cx("NavDropdown-button NavItem text-white text-bold cursor-pointer px2 flex align-center transition-background", {
"NavItem--selected": this.isActive("/dashboard/")
})}
>
<span className="NavDropdown-button-layer">
Dashboards
<Icon className="ml1" name={'chevrondown'} size={8}></Icon>
......@@ -133,7 +141,7 @@ export default class Navbar extends Component {
<Link to="/reference/guide" data-metabase-event={"Navbar;DataReference"} style={this.styles.navButton} className={cx("NavItem cursor-pointer text-white text-bold no-decoration flex align-center px2 transition-background")} activeClassName="NavItem--selected">Data Reference</Link>
</li>
<li className="pl3">
<Link to="/q" data-metabase-event={"Navbar;New Question"} style={this.styles.newQuestion} className="NavNewQuestion rounded inline-block bg-white text-brand text-bold cursor-pointer px2 no-decoration transition-all">New <span className="hide sm-show">Question</span></Link>
<Link to={Urls.question()} data-metabase-event={"Navbar;New Question"} style={this.styles.newQuestion} className="NavNewQuestion rounded inline-block bg-white text-brand text-bold cursor-pointer px2 no-decoration transition-all">New <span className="hide sm-show">Question</span></Link>
</li>
<li className="flex-align-right transition-background">
<div className="inline-block text-white"><ProfileLink {...this.props}></ProfileLink></div>
......
......@@ -5,7 +5,7 @@ import { Link } from "react-router";
import cx from "classnames";
import Urls from "metabase/lib/urls";
import * as Urls from "metabase/lib/urls";
import PulseListChannel from "./PulseListChannel.jsx";
export default class PulseListItem extends Component {
......@@ -43,7 +43,7 @@ export default class PulseListItem extends Component {
<ol className="mb2 px4 flex flex-wrap">
{ pulse.cards.map((card, index) =>
<li key={index} className="mr1 mb1">
<Link to={Urls.card(card.id)} className="Button">
<Link to={Urls.question(card.id)} className="Button">
{card.name}
</Link>
</li>
......
......@@ -7,7 +7,7 @@ import Tooltip from "metabase/components/Tooltip.jsx";
import FieldSet from "metabase/components/FieldSet.jsx";
import Urls from "metabase/lib/urls";
import * as Urls from "metabase/lib/urls";
import _ from "underscore";
import cx from "classnames";
......
......@@ -23,7 +23,7 @@ import { CardApi, RevisionApi } from "metabase/services";
import MetabaseAnalytics from "metabase/lib/analytics";
import Query from "metabase/lib/query";
import { cancelable } from "metabase/lib/promise";
import Urls from "metabase/lib/urls";
import * as Urls from "metabase/lib/urls";
import cx from "classnames";
import _ from "underscore";
......
......@@ -15,6 +15,7 @@ import QuestionEmbedWidget from "../containers/QuestionEmbedWidget";
import { formatNumber, inflect } from "metabase/lib/formatting";
import Utils from "metabase/lib/utils";
import MetabaseSettings from "metabase/lib/settings";
import * as Urls from "metabase/lib/urls";
import cx from "classnames";
import _ from "underscore";
......@@ -188,5 +189,5 @@ export default class QueryVisualization extends Component {
const VisualizationEmptyState = ({showTutorialLink}) =>
<div className="flex full layout-centered text-grey-1 flex-column">
<h1>If you give me some data I can show you something cool. Run a Query!</h1>
{ showTutorialLink && <Link to="/q#?tutorial" className="link cursor-pointer my2">How do I use this thing?</Link> }
{ showTutorialLink && <Link to={Urls.question(null, "?tutorial")} className="link cursor-pointer my2">How do I use this thing?</Link> }
</div>
......@@ -154,9 +154,9 @@ export default class QueryBuilder extends Component {
if (nextProps.location.action === "POP" && getURL(nextProps.location) !== getURL(this.props.location)) {
this.props.popState(nextProps.location);
} else if (this.props.location.query.tutorial === undefined && nextProps.location.query.tutorial !== undefined) {
} else if (this.props.location.hash !== "#?tutorial" && nextProps.location.hash === "#?tutorial") {
this.props.initializeQB(nextProps.location, nextProps.params);
} else if (getURL(nextProps.location) === "/q" && getURL(this.props.location) !== "/q") {
} else if (getURL(nextProps.location) === "/question" && getURL(this.props.location) !== "/question") {
this.props.initializeQB(nextProps.location, nextProps.params);
}
}
......
......@@ -5,7 +5,7 @@ import { connect } from "react-redux";
import EmbedWidget from "metabase/public/components/widgets/EmbedWidget";
import Urls from "metabase/lib/urls";
import * as Urls from "metabase/lib/urls";
import { getParameters } from "metabase/meta/Card";
import { createPublicLink, deletePublicLink, updateEnableEmbedding, updateEmbeddingParams, } from "../actions";
......
......@@ -2,7 +2,7 @@
import { createAction, createThunkAction, handleActions, combineReducers } from "metabase/lib/redux";
import { reset } from 'redux-form';
import { replace } from "react-router-redux";
import Urls from "metabase/lib/urls";
import * as Urls from "metabase/lib/urls";
import _ from "underscore";
......
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