Skip to content

Commit

Permalink
Public Cards & Dashboards 😋
Browse files Browse the repository at this point in the history
  • Loading branch information
camsaul committed Jan 31, 2017
1 parent f298072 commit 190ab3a
Show file tree
Hide file tree
Showing 112 changed files with 3,230 additions and 717 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ profiles.clj
/*.trace.db
/resources/frontend_client/app/dist/
/resources/frontend_client/index.html
/resources/frontend_client/public.html
/node_modules/
/.babel_cache
/coverage
Expand Down
105 changes: 104 additions & 1 deletion docs/api-documentation.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# API Documentation for Metabase v0.22.0-snapshot
# API Documentation for Metabase v0.23.0-snapshot

## `GET /api/activity/`

Expand All @@ -19,6 +19,17 @@ Unfavorite a Card.
* **`card-id`**


## `DELETE /api/card/:card-id/public_link`

Delete the publically-accessible link to this Card.

You must be a superuser to do this.

##### PARAMS:

* **`card-id`**


## `DELETE /api/card/:id`

Delete a `Card`.
Expand Down Expand Up @@ -102,6 +113,19 @@ Update the set of `Labels` that apply to a `Card`.
* **`label_ids`** value must be an array. Each value must be an integer greater than zero.


## `POST /api/card/:card-id/public_link`

Generate publically-accessible links for this Card. Returns UUID to be used in public links.
(If this Card has already been shared, it will return the existing public link rather than creating a new one.)
Public sharing must be enabled.

You must be a superuser to do this.

##### PARAMS:

* **`card-id`**


## `POST /api/card/:card-id/query`

Run the query associated with a Card.
Expand Down Expand Up @@ -236,6 +260,17 @@ Do a batch update of Collections Permissions by passing in a modified graph.
* **`body`** value must be a map.


## `DELETE /api/dashboard/:dashboard-id/public_link`

Delete the publically-accessible link to this Dashboard.

You must be a superuser to do this.

##### PARAMS:

* **`dashboard-id`**


## `DELETE /api/dashboard/:id`

Delete a `Dashboard`.
Expand Down Expand Up @@ -299,6 +334,19 @@ Create a new `Dashboard`.
* **`dashboard`**


## `POST /api/dashboard/:dashboard-id/public_link`

Generate publically-accessible links for this Dashboard. Returns UUID to be used in public links.
(If this Dashboard has already been shared, it will return the existing public link rather than creating a new one.)
Public sharing must be enabled.

You must be a superuser to do this.

##### PARAMS:

* **`dashboard-id`**


## `POST /api/dashboard/:id/cards`

Add a `Card` to a `Dashboard`.
Expand Down Expand Up @@ -911,6 +959,61 @@ You must be a superuser to do this.
* **`name`** value must be a non-blank string.


## `GET /api/public/card/:uuid`

Fetch a publically-accessible Card an return query results as well as `:card` information. Does not require auth credentials. Public sharing must be enabled.

##### PARAMS:

* **`uuid`**

* **`parameters`** value may be nil, or if non-nil, value must be a valid JSON string.


## `GET /api/public/card/:uuid/csv`

Fetch a publically-accessible Card and return query results as CSV. Does not require auth credentials. Public sharing must be enabled.

##### PARAMS:

* **`uuid`**

* **`parameters`** value may be nil, or if non-nil, value must be a valid JSON string.


## `GET /api/public/card/:uuid/json`

Fetch a publically-accessible Card and return query results as JSON. Does not require auth credentials. Public sharing must be enabled.

##### PARAMS:

* **`uuid`**

* **`parameters`** value may be nil, or if non-nil, value must be a valid JSON string.


## `GET /api/public/dashboard/:uuid`

Fetch a publically-accessible Dashboard. Does not require auth credentials. Public sharing must be enabled.

##### PARAMS:

* **`uuid`**


## `GET /api/public/dashboard/:uuid/card/:card-id`

Fetch the results for a Card in a publically-accessible Dashboard. Does not require auth credentials. Public sharing must be enabled.

##### PARAMS:

* **`uuid`**

* **`card-id`**

* **`parameters`** value may be nil, or if non-nil, value must be a valid JSON string.


## `DELETE /api/pulse/:id`

Delete a `Pulse`.
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/metabase/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ export default class App extends Component {
return (
<div className="spread flex flex-column">
<Navbar location={location} className="flex-no-shrink" />
{ errorPage === 403 ?
{ errorPage && errorPage.status === 403 ?
<Unauthorized />
: errorPage === 404 ?
: errorPage && errorPage.status === 404 ?
<NotFound />
:
children
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,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={"/q#?db="+databaseId}>ask a question</Link> about
this database.
</p>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/* @flow */

import React, { Component, PropTypes } from "react";

import Icon from "metabase/components/Icon";
import Link from "metabase/components/Link";
import ExternalLink from "metabase/components/ExternalLink";
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";

import { CardApi, DashboardApi } from "metabase/services";
import Urls from "metabase/lib/urls";

type PublicLink = {
id: string,
name: string,
public_uuid: string
};

type Props = {
load: () => Promise<PublicLink[]>,
revoke: (link: PublicLink) => Promise<void>,
getUrl: (link: PublicLink) => string,
getPublicUrl: (link: PublicLink) => string,
};

type State = {
list: ?PublicLink[],
error: ?any
};

export default class PublicLinksListing extends Component<*, Props, State> {
props: Props;
state: State;

constructor(props: Props) {
super(props);
this.state = {
list: null,
error: null
};
}

componentWillMount() {
this.load();
}

async load() {
try {
const list = await this.props.load();
this.setState({ list });
} catch (error) {
this.setState({ error });
}
}

async revoke(link: PublicLink) {
try {
await this.props.revoke(link);
this.load();
} catch (error) {
alert(error)
}
}

render() {
const { getUrl, getPublicUrl } = this.props;
const { list, error } = this.state;
return (
<LoadingAndErrorWrapper loading={!list} error={error}>
{ () =>
<table className="ContentTable">
<thead>
<tr>
<th>Name</th>
<th>Public Link</th>
<th>Revoke Link</th>
</tr>
</thead>
<tbody>
{ list && list.map(link =>
<tr>
<td>
<Link to={getUrl(link)}>
{link.name}
</Link>
</td>
<td>
<ExternalLink href={getPublicUrl(link)}>
{getPublicUrl(link)}
</ExternalLink>
</td>
<td className="flex layout-centered">
<Icon
name="close"
className="text-grey-2 text-grey-4-hover cursor-pointer"
onClick={() => this.revoke(link)}
/>
</td>
</tr>
) }
</tbody>
</table>
}
</LoadingAndErrorWrapper>
);
}
}

export const PublicLinksDashboardListing = () =>
<PublicLinksListing
load={DashboardApi.listPublic}
revoke={DashboardApi.deletePublicLink}
getUrl={({ id }) => Urls.dashboard(id)}
getPublicUrl={({ public_uuid }) => window.location.origin + Urls.publicDashboard(public_uuid)}
/>;

export const PublicLinksQuestionListing = () =>
<PublicLinksListing
load={CardApi.listPublic}
revoke={CardApi.deletePublicLink}
getUrl={({ id }) => Urls.card(id)}
getPublicUrl={({ public_uuid }) => window.location.origin + Urls.publicCard(public_uuid)}
/>;
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import cx from 'classnames';

import {
getSettings,
getSettingValues,
getSections,
getActiveSection,
getNewVersionAvailable
Expand All @@ -27,6 +28,7 @@ import * as settingsActions from "../settings";
const mapStateToProps = (state, props) => {
return {
settings: getSettings(state, props),
settingValues: getSettingValues(state, props),
sections: getSections(state, props),
activeSection: getActiveSection(state, props),
newVersionAvailable: getNewVersionAvailable(state, props)
Expand Down Expand Up @@ -72,52 +74,59 @@ export default class SettingsEditorApp extends Component {
}

renderSettingsPane() {
if (!this.props.activeSection) return null;
const { activeSection, settingValues } = this.props;

let section = this.props.activeSection; // this.props.sections[this.state.currentSection];
if (!activeSection) {
return null;
}

if (section.name === "Email") {
if (activeSection.name === "Email") {
return (
<SettingsEmailForm
ref="emailForm"
elements={section.settings}
elements={activeSection.settings}
updateEmailSettings={this.props.updateEmailSettings}
sendTestEmail={this.props.sendTestEmail}
/>
);
} else if (section.name === "Setup") {
} else if (activeSection.name === "Setup") {
return (
<SettingsSetupList
ref="settingsForm"
/>
);
} else if (section.name === "Slack") {
} else if (activeSection.name === "Slack") {
return (
<SettingsSlackForm
ref="slackForm"
elements={section.settings}
elements={activeSection.settings}
updateSlackSettings={this.props.updateSlackSettings}
/>
);
} else if (section.name === "Updates") {
} else if (activeSection.name === "Updates") {
return (
<SettingsUpdatesForm
settings={this.props.settings}
elements={section.settings}
elements={activeSection.settings}
updateSetting={this.updateSetting}
/>
);
} else if (section.name === "Single Sign-On") {
} else if (activeSection.name === "Single Sign-On") {
return (
<SettingsSingleSignOnForm
elements={section.settings}
elements={activeSection.settings}
updateSetting={this.updateSetting}
/>
);
} else {

return (
<ul>
{section.settings.map((setting, index) =>
{activeSection.settings
.filter((setting) =>
setting.getHidden ? !setting.getHidden(settingValues) : true
)
.map((setting, index) =>
<SettingsSetting
key={setting.key}
setting={setting}
Expand Down
Loading

0 comments on commit 190ab3a

Please sign in to comment.