Skip to content

Commit

Permalink
Merge branch 'master' of github.com:metabase/metabase into linkify-links
Browse files Browse the repository at this point in the history
  • Loading branch information
tlrobinson committed Nov 7, 2016
2 parents 05a2fe5 + cda0f5b commit ec85680
Show file tree
Hide file tree
Showing 59 changed files with 873 additions and 585 deletions.
1 change: 1 addition & 0 deletions .dir-locals.el
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
(let-500 1)
(match 1)
(match-$ 1)
(merge-with 1)
(post-select 1)
(pre-cascade-delete 1)
(pre-insert 1)
Expand Down
14 changes: 10 additions & 4 deletions docs/operations-guide/running-metabase-on-heroku.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,21 @@ Here's each step:

* Clone the latest version to your local machine:

git clone https://github.com/metabase/metabase-deploy.git
cd metabase-deploy
```bash
git clone https://github.com/metabase/metabase-deploy.git
cd metabase-deploy
```

* Add a git remote with your metabase setup:

git remote add heroku https://git.heroku.com/your-metabase-app.git
```bash
git remote add heroku https://git.heroku.com/your-metabase-app.git
```

* Force push the new version to Heroku:

git push -f heroku master
```bash
git push -f heroku master
```

* Wait for the deploy to finish
3 changes: 3 additions & 0 deletions docs/operations-guide/running-the-metabase-mac-app.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,6 @@ The application database lives on your filesystem, at
`~/Library/Application Support/Metabase/metabase.db.h2.db`

If you want to delete it, back it up, or replace it with an old backup, shut down the application and then delete, copy or replace the file.

Note: depending on when you first started using Metabase the file may be called
`~/Library/Application Support/Metabase/metabase.db.mv.db`
7 changes: 4 additions & 3 deletions docs/operations-guide/start.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ You can see these database files from the terminal:

You should see the following files:

metabase.db.h2.db
metabase.db.h2.db # Or metabase.db.mv.db depending on when you first started using Metabase.
metabase.db.trace.db

If for any reason you want to use an H2 database file in a separate location from where you launch Metabase you can do so using an environment variable. For example:
Expand All @@ -107,7 +107,8 @@ If for any reason you want to use an H2 database file in a separate location fro


#### [Postgres](http://www.postgresql.org/)
For production installations of Metabase we recommend that users replace the H2 database with a more robust option such as Postgres. This offers a greater degree of performance and reliability when Metabase is running with many users.

**For production installations of Metabase we recommend that users replace the H2 database with a more robust option such as Postgres.** This offers a greater degree of performance and reliability when Metabase is running with many users.

You can change the application database to use Postgres using a few simple environment variables. For example:

Expand Down Expand Up @@ -206,7 +207,7 @@ If you are using Metabase in a production environment or simply want to make sur
Metabase uses a single SQL database for all of its runtime application data, so all you need to do is backup that database and you're good to go. From a database back-up you can restore any Metabase installation.

### H2 Embedded Database (default)
If you launched Metabase on a laptop or PC the application will create an embedded H2 database in the directory it is being run in. Navigate to the directory where you started Metabase from and find the file named `metabase.db.h2.db`. Simply copy that file somewhere safe and you are all backed up!
If you launched Metabase on a laptop or PC the application will create an embedded H2 database in the directory it is being run in. Navigate to the directory where you started Metabase from and find the file named `metabase.db.h2.db` or `metabase.db.mv.db` (you will see one of the two depending on when you first started using Metabase). Simply copy that file somewhere safe and you are all backed up!

NOTE: If your Metabase is currently running it's best to shut down the Metabase process before making a backup copy of the file. Then, restart the application.

Expand Down
2 changes: 1 addition & 1 deletion docs/users-guide/04-visualizing-results.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ When you select the Map visualization setting, Metabase will automatically try a

* **United States Map** — Creating a map of the United States from your data requires your results to contain a column field with states. This lets you do things like visualize the count of your users broken out by state.
* **Country Map** — To visualize your results in the format of a map of the world broken out by country, your result must contain a field with countries.
* **Pin Map** — If your table contains a latitude and longitude field, Metabase will try to display it as a pin map of the world. This will put one pin on the map for each row in your table, based on the latitude and longitude fields. *Note: this map option requires a [Google Maps API key](https://developers.google.com/maps/documentation/javascript/get-api-key).*
* **Pin Map** — If your table contains a latitude and longitude field, Metabase will try to display it as a pin map of the world. This will put one pin on the map for each row in your table, based on the latitude and longitude fields.

When you open up the Map options, you can manually switch between a region map (i.e., United States or world) and a pin map. (And don't worry — a flexible way to add custom maps of other countries and regions will be coming soon.) If you're using a region map, you can also choose which field to use as the measurement, and which to use as the region (i.e. State or Country).

Expand Down
15 changes: 12 additions & 3 deletions frontend/interfaces/webdriver.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,23 @@
declare module "selenium-webdriver" {

declare class WebDriver {
wait(condition: Condition|Function, timeout: ?number): Promise<WebElement>;
findElement(selector: By): WebElement;
get(url: string): Promise<void>;
wait(condition: Condition|Function, timeout: ?number): WebElementPromise;
findElement(selector: By): WebElementPromise;
deleteAllCookies(): Promise<void>;
getCurrentUrl(): Promise<string>;
}

declare class WebElement {
findElement(selector: By): WebElementPromise;
click(): Promise<void>;
findElement(selector: By): WebElement;
sendKeys(keys: string): Promise<void>;
clear(): Promise<void>;
getText(): Promise<string>;
getAttribute(attribute: string): Promise<string>;
}

declare class WebElementPromise extends WebElement {
}

declare class Condition {
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/metabase/admin/settings/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ const SECTIONS = [
type: "boolean"
},
{
key: "google-maps-api-key",
display_name: "Google Maps API Key",
key: "map-tile-server-url",
display_name: "Map tile server URL",
type: "string"
}
]
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/metabase/admin/settings/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const initializeSettings = createThunkAction("INITIALIZE_SETTINGS", funct
export const updateSetting = createThunkAction("UPDATE_SETTING", function(setting) {
return async function(dispatch, getState) {
try {
await SettingsApi.put({ key: setting.key }, setting);
await SettingsApi.put(setting);
await dispatch(refreshSiteSettings());
return await loadSettings();
} catch(error) {
Expand Down
17 changes: 17 additions & 0 deletions frontend/src/metabase/components/ExternalLink.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from "react";

const ExternalLink = ({ href, children }) =>
<a
href={href}
className="link"
// open in a new tab
target="_blank"
// prevent malicious pages from navigating us away
rel="noopener"
// disables quickfilter in tables
onClickCapture={(e) => e.stopPropagation()}
>
{children}
</a>

export default ExternalLink;
1 change: 0 additions & 1 deletion frontend/src/metabase/css/components/mb_data_table.css
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
}

.MB-DataTable .public_fixedDataTableCell_main:hover {
cursor: pointer;
border-color: var(--brand-color);
color: var(--brand-color);
}
Expand Down
14 changes: 7 additions & 7 deletions frontend/src/metabase/dashboard/components/DashCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,37 +127,37 @@ export default class DashCard extends Component {
series={series}
onRemove={onRemove}
onAddSeries={onAddSeries}
onUpdateVisualizationSettings={this.props.onUpdateVisualizationSettings}
onReplaceAllVisualizationSettings={this.props.onReplaceAllVisualizationSettings}
/> : undefined
}
onUpdateVisualizationSetting={this.props.onUpdateVisualizationSetting}
onUpdateVisualizationSettings={this.props.onUpdateVisualizationSettings}
replacementContent={isEditingParameter && <DashCardParameterMapper dashcard={dashcard} />}
/>
</div>
);
}
}

const DashCardActionButtons = ({ series, onRemove, onAddSeries, onUpdateVisualizationSettings }) =>
const DashCardActionButtons = ({ series, onRemove, onAddSeries, onReplaceAllVisualizationSettings }) =>
<span className="DashCard-actions flex align-center">
{ getVisualizationRaw(series).CardVisualization.supportsSeries &&
<AddSeriesButton series={series} onAddSeries={onAddSeries} />
}
{ onUpdateVisualizationSettings &&
<ChartSettingsButton series={series} onUpdateVisualizationSettings={onUpdateVisualizationSettings} />
{ onReplaceAllVisualizationSettings &&
<ChartSettingsButton series={series} onReplaceAllVisualizationSettings={onReplaceAllVisualizationSettings} />
}
<RemoveButton onRemove={onRemove} />
</span>

const ChartSettingsButton = ({ series, onUpdateVisualizationSettings }) =>
const ChartSettingsButton = ({ series, onReplaceAllVisualizationSettings }) =>
<ModalWithTrigger
className="Modal Modal--wide Modal--tall"
triggerElement={<Icon name="gear" />}
triggerClasses="text-grey-2 text-grey-4-hover cursor-pointer mr1 flex align-center flex-no-shrink"
>
<ChartSettings
series={series}
onChange={onUpdateVisualizationSettings}
onChange={onReplaceAllVisualizationSettings}
isDashboard
/>
</ModalWithTrigger>
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/metabase/dashboard/components/Dashboard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ export default class Dashboard extends Component {
saveDashboard: PropTypes.func.isRequired,
setDashboardAttributes: PropTypes.func.isRequired,
setEditingDashboard: PropTypes.func.isRequired,
setDashCardVisualizationSetting: PropTypes.func.isRequired,

onUpdateDashCardVisualizationSettings: PropTypes.func.isRequired,
onReplaceAllDashCardVisualizationSettings: PropTypes.func.isRequired,

onChangeLocation: PropTypes.func.isRequired,
};
Expand Down
22 changes: 5 additions & 17 deletions frontend/src/metabase/dashboard/components/DashboardGrid.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ export default class DashboardGrid extends Component {
markNewCardSeen: PropTypes.func.isRequired,
fetchCardData: PropTypes.func.isRequired,

onUpdateDashCardVisualizationSettings: PropTypes.func.isRequired,
onReplaceAllDashCardVisualizationSettings: PropTypes.func.isRequired,

onChangeLocation: PropTypes.func.isRequired
};

Expand Down Expand Up @@ -191,21 +194,6 @@ export default class DashboardGrid extends Component {
this.setState({ addSeriesModalDashCard: dc });
}

onUpdateVisualizationSetting(dc, key, value) {
this.props.setDashCardVisualizationSetting({
id: dc.id,
key: key,
value: value
});
}

onUpdateVisualizationSettings(dc, settings) {
this.props.setDashCardVisualizationSettings({
id: dc.id,
settings: settings
});
}

renderDashCard(dc, isMobile) {
return (
<DashCard
Expand All @@ -221,8 +209,8 @@ export default class DashboardGrid extends Component {
isMobile={isMobile}
onRemove={this.onDashCardRemove.bind(this, dc)}
onAddSeries={this.onDashCardAddSeries.bind(this, dc)}
onUpdateVisualizationSetting={this.onUpdateVisualizationSetting.bind(this, dc)}
onUpdateVisualizationSettings={this.onUpdateVisualizationSettings.bind(this, dc)}
onUpdateVisualizationSettings={this.props.onUpdateDashCardVisualizationSettings.bind(this, dc.id)}
onReplaceAllVisualizationSettings={this.props.onReplaceAllDashCardVisualizationSettings.bind(this, dc.id)}
/>
)
}
Expand Down
16 changes: 8 additions & 8 deletions frontend/src/metabase/dashboard/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ export const SET_DASHBOARD_ATTRIBUTES = "metabase/dashboard/SET_DASHBOARD_ATTRIB
export const ADD_CARD_TO_DASH = "metabase/dashboard/ADD_CARD_TO_DASH";
export const REMOVE_CARD_FROM_DASH = "metabase/dashboard/REMOVE_CARD_FROM_DASH";
export const SET_DASHCARD_ATTRIBUTES = "metabase/dashboard/SET_DASHCARD_ATTRIBUTES";
export const SET_DASHCARD_VISUALIZATION_SETTING = "metabase/dashboard/SET_DASHCARD_VISUALIZATION_SETTING";
export const SET_DASHCARD_VISUALIZATION_SETTINGS = "metabase/dashboard/SET_DASHCARD_VISUALIZATION_SETTINGS";
export const UPDATE_DASHCARD_VISUALIZATION_SETTINGS = "metabase/dashboard/UPDATE_DASHCARD_VISUALIZATION_SETTINGS";
export const REPLACE_ALL_DASHCARD_VISUALIZATION_SETTINGS = "metabase/dashboard/REPLACE_ALL_DASHCARD_VISUALIZATION_SETTINGS";
export const UPDATE_DASHCARD_ID = "metabase/dashboard/UPDATE_DASHCARD_ID"
export const SAVE_DASHCARD = "metabase/dashboard/SAVE_DASHCARD";

Expand Down Expand Up @@ -373,8 +373,8 @@ export const revertToRevision = createThunkAction(REVERT_TO_REVISION, function({
};
});

export const setDashCardVisualizationSetting = createAction(SET_DASHCARD_VISUALIZATION_SETTING);
export const setDashCardVisualizationSettings = createAction(SET_DASHCARD_VISUALIZATION_SETTINGS);
export const onUpdateDashCardVisualizationSettings = createAction(UPDATE_DASHCARD_VISUALIZATION_SETTINGS, (id, settings) => ({ id, settings }));
export const onReplaceAllDashCardVisualizationSettings = createAction(REPLACE_ALL_DASHCARD_VISUALIZATION_SETTINGS, (id, settings) => ({ id, settings }));

export const setEditingParameterId = createAction(SET_EDITING_PARAMETER_ID);
export const setParameterMapping = createThunkAction(SET_PARAMETER_MAPPING, (parameter_id, dashcard_id, card_id, target) =>
Expand Down Expand Up @@ -441,14 +441,14 @@ const dashcards = handleActions({
[id]: { ...state[id], ...attributes, isDirty: true }
})
},
[SET_DASHCARD_VISUALIZATION_SETTING]: {
next: (state, { payload: { id, key, value } }) =>
[UPDATE_DASHCARD_VISUALIZATION_SETTINGS]: {
next: (state, { payload: { id, settings } }) =>
i.chain(state)
.assocIn([id, "visualization_settings", key], value)
.updateIn([id, "visualization_settings"], (value = {}) => ({ ...value, ...settings }))
.assocIn([id, "isDirty"], true)
.value()
},
[SET_DASHCARD_VISUALIZATION_SETTINGS]: {
[REPLACE_ALL_DASHCARD_VISUALIZATION_SETTINGS]: {
next: (state, { payload: { id, settings } }) =>
i.chain(state)
.assocIn([id, "visualization_settings"], settings)
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/metabase/lib/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ export const field_special_types = [{
'id': TYPE.Description,
'name': 'Description',
'section': 'Common'
}, {
'id': TYPE.Email,
'name': 'Email',
'section': 'Common'
}, {
'id': TYPE.ImageURL,
'name': 'Image URL',
Expand Down
25 changes: 0 additions & 25 deletions frontend/src/metabase/lib/data_grid.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,6 @@ function compareNumbers(a, b) {
return a - b;
}

export function filterOnPreviewDisplay(data) {
// find any columns where visibility_type = details-only
var hiddenColumnIdxs = _.map(data.cols, function(col, idx) { if(col.visibility_type === "details-only") return idx; });
hiddenColumnIdxs = _.filter(hiddenColumnIdxs, function(val) { return val !== undefined; });

// filter out our data grid using the indexes of the hidden columns
var filteredRows = data.rows.map(function(row, rowIdx) {
return row.filter(function(cell, cellIdx) {
if (_.contains(hiddenColumnIdxs, cellIdx)) {
return false;
} else {
return true;
}
});
});

return {
cols: _.filter(data.cols, function(col) { return col.visibility_type !== "details-only"; }),
columns: _.map(data.cols, function(col) { return col.display_name; }),
rows: filteredRows,
rows_truncated: data.rows_truncated,
native_form: data.native_form
};
}

export function pivot(data) {
// find the lowest cardinality dimension and make it our "pivoted" column
// TODO: we assume dimensions are in the first 2 columns, which is less than ideal
Expand Down
29 changes: 15 additions & 14 deletions frontend/src/metabase/lib/formatting.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import moment from "moment";
import Humanize from "humanize";
import React from "react";

import ExternalLink from "metabase/components/ExternalLink.jsx";

import { isDate, isNumber, isCoordinate } from "metabase/lib/schema_metadata";
import { isa, TYPE } from "metabase/lib/types";
import { parseTimestamp } from "metabase/lib/time";
Expand Down Expand Up @@ -99,25 +101,22 @@ function formatTimeWithUnit(value, unit, options = {}) {
}
}

const EMAIL_WHITELIST_REGEX = /.+@.+/;

export function formatEmail(value, { jsx } = {}) {
if (jsx && EMAIL_WHITELIST_REGEX.test(value)) {
return <ExternalLink href={"mailto:" + value}>{value}</ExternalLink>;
} else {
value;
}
}

// prevent `javascript:` etc URLs
const URL_WHITELIST_REGEX = /^(https?|mailto):/;

export function formatUrl(value, { jsx } = {}) {
if (jsx && URL_WHITELIST_REGEX.test(value)) {
return (
<a
href={value}
className="link"
// open in a new tab
target="_blank"
// prevent malicious pages from navigating us away
rel="noopener"
// disables quickfilter in tables
onClickCapture={(e) => e.stopPropagation()}
>
{value}
</a>
);
return <ExternalLink href={value}>{value}</ExternalLink>;
} else {
return value;
}
Expand All @@ -134,6 +133,8 @@ export function formatValue(value, options = {}) {
return null;
} else if (column && isa(column.special_type, TYPE.URL)) {
return formatUrl(value, options);
} else if (column && isa(column.special_type, TYPE.Email)) {
return formatEmail(value, options);
} else if (column && column.unit != null) {
return formatTimeWithUnit(value, column.unit, options);
} else if (isDate(column) || moment.isDate(value) || moment.isMoment(value) || moment(value, ["YYYY-MM-DD'T'HH:mm:ss.SSSZ"], true).isValid()) {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/metabase/lib/schema_metadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ const OPERATORS = {
},
"INSIDE": {
validArgumentsFilters: [longitudeFieldSelectArgument, numberArgument, numberArgument, numberArgument, numberArgument],
placeholders: ["Select longitude field", "Enter upper latitude", "Enter left longitude", "Enter lower latitude", "Enter right latitude"]
placeholders: ["Select longitude field", "Enter upper latitude", "Enter left longitude", "Enter lower latitude", "Enter right longitude"]
},
"BETWEEN": {
validArgumentsFilters: [comparableArgument, comparableArgument]
Expand Down
Loading

0 comments on commit ec85680

Please sign in to comment.