Skip to content
Snippets Groups Projects
Commit 4bdcdca5 authored by Cam Saül's avatar Cam Saül
Browse files

Merge branch 'master' into swisscom-bigdata-sort-druid-select [ci drivers]

parents a250debc 80f4b6d1
No related branches found
No related tags found
No related merge requests found
Showing
with 194 additions and 49 deletions
......@@ -2,7 +2,7 @@
"rules": {
"strict": [2, "never"],
"no-undef": 2,
"no-unused-vars": [1, {"vars": "all", "args": "none", "varsIgnorePattern": "React|PropTypes|Component"}],
"no-unused-vars": [1, {"vars": "all", "args": "none", "varsIgnorePattern": "^(React|PropTypes|Component)$"}],
"import/no-commonjs": 1,
"quotes": 0,
"camelcase": 0,
......
......@@ -37,6 +37,7 @@ For more information check out [metabase.com](http://www.metabase.com)
- CrateDB
- Oracle
- Vertica
- Presto
Don't see your favorite database? File an issue to let us know.
......
......@@ -19,11 +19,15 @@ node-1() {
run_step lein-test
}
node-2() {
is_enabled "drivers" && export ENGINES="h2,postgres,sqlite" || export ENGINES="h2"
is_enabled "drivers" && export ENGINES="h2,postgres,sqlite,presto" || export ENGINES="h2"
if is_engine_enabled "crate"; then
run_step install-crate
fi
if is_engine_enabled "presto"; then
run_step install-presto
fi
MB_ENCRYPTION_SECRET_KEY='Orw0AAyzkO/kPTLJRxiyKoBHXa/d6ZcO+p+gpZO/wSQ=' MB_DB_TYPE=mysql MB_DB_DBNAME=circle_test MB_DB_PORT=3306 MB_DB_USER=ubuntu MB_DB_HOST=localhost \
MB_PRESTO_HOST=localhost MB_PRESTO_PORT=8080 \
run_step lein-test
}
node-3() {
......@@ -91,6 +95,11 @@ install-vertica() {
sleep 60
}
install-presto() {
docker run --detach --publish 8080:8080 wiill/presto-mb-ci
sleep 10
}
lein-test() {
lein test
}
......
......@@ -70,15 +70,33 @@ Step-by-step instructions on how to upgrade Metabase running on Heroku.
# Troubleshooting Common Problems
### Metabase fails to startup
### Metabase fails to start due to database locks
Sometimes Metabase will fail to complete its startup due to a database lock that was not cleared properly.
Sometimes Metabase will fail to complete its startup due to a database lock that was not cleared properly. The error message will look something like:
liquibase.exception.DatabaseException: liquibase.exception.LockException: Could not acquire change log lock.
When this happens, go to a terminal where Metabase is installed and run:
java -jar metabase.jar migrate release-locks
in the command line to manually clear the locks. Then restart your Metabase instance.
in the command line to manually clear the locks. Then restart your Metabase instance.
### Metabase fails to start due to OutOfMemoryErrors
On Java 7, Metabase may fail to launch with a message like
java.lang.OutOfMemoryError: PermGen space
or one like
Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler
If this happens, setting a few JVM options should fix your issue:
java -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC -XX:MaxPermSize=256m -jar target/uberjar/metabase.jar
Alternatively, you can upgrade to Java 8 instead, which will fix the issue as well.
# Configuring the Metabase Application Database
......
......@@ -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,19 +2,13 @@ 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 _ from "underscore";
import cx from "classnames";
export default class PartialQueryBuilder extends Component {
constructor(props, context) {
super(props, context);
this.state = {};
_.bindAll(this, "setQuery");
}
import * as Query from "metabase/lib/query/query";
export default class PartialQueryBuilder extends Component {
static propTypes = {
onChange: PropTypes.func.isRequired,
tableMetadata: PropTypes.object.isRequired,
......@@ -34,15 +28,15 @@ export default class PartialQueryBuilder extends Component {
});
}
setQuery(query) {
this.props.onChange(query.query);
this.props.updatePreviewSummary(query);
setDatasetQuery = (datasetQuery) => {
this.props.onChange(datasetQuery.query);
this.props.updatePreviewSummary(datasetQuery);
}
render() {
let { features, value, tableMetadata, previewSummary } = this.props;
let dataset_query = {
let datasetQuery = {
type: "query",
database: tableMetadata.db_id,
query: {
......@@ -53,28 +47,39 @@ export default class PartialQueryBuilder extends Component {
let previewCard = {
dataset_query: {
...dataset_query,
...datasetQuery,
query: {
aggregation: ["rows"],
breakout: [],
filter: [],
...dataset_query.query
...datasetQuery.query
}
}
};
let previewUrl = "/q#" + serializeCardForUrl(previewCard);
let previewUrl = Urls.question(null, previewCard);
const onChange = (query) => {
this.props.onChange(query);
this.props.updatePreviewSummary({ ...datasetQuery, query });
}
return (
<div className="py1">
<GuiQueryEditor
query={dataset_query}
features={features}
datasetQuery={datasetQuery}
tableMetadata={tableMetadata}
databases={tableMetadata && [tableMetadata.db]}
setQueryFn={this.setQuery}
setDatasetQuery={this.setDatasetQuery}
isShowingDataReference={false}
setDatabaseFn={null}
setSourceTableFn={null}
addQueryFilter={(filter) => onChange(Query.addFilter(datasetQuery.query, filter))}
updateQueryFilter={(index, filter) => onChange(Query.updateFilter(datasetQuery.query, index, filter))}
removeQueryFilter={(index) => onChange(Query.removeFilter(datasetQuery.query, index))}
addQueryAggregation={(aggregation) => onChange(Query.addAggregation(datasetQuery.query, aggregation))}
updateQueryAggregation={(index, aggregation) => onChange(Query.updateAggregation(datasetQuery.query, index, aggregation))}
removeQueryAggregation={(index) => onChange(Query.removeAggregation(datasetQuery.query, index))}
>
<div className="flex align-center mx2 my2">
<span className="text-bold px3">{previewSummary}</span>
......
......@@ -42,12 +42,12 @@ import cx from "classnames";
},
metricFormSelectors)
export default class MetricForm extends Component {
updatePreviewSummary(query) {
updatePreviewSummary(datasetQuery) {
this.props.updatePreviewSummary({
...query,
...datasetQuery,
query: {
aggregation: ["count"],
...query.query,
...datasetQuery.query,
}
})
}
......
......@@ -40,11 +40,11 @@ import cx from "classnames";
},
segmentFormSelectors)
export default class SegmentForm extends Component {
updatePreviewSummary(query) {
updatePreviewSummary(datasetQuery) {
this.props.updatePreviewSummary({
...query,
...datasetQuery,
query: {
...query.query,
...datasetQuery.query,
aggregation: ["count"]
}
})
......
......@@ -3,6 +3,7 @@ import React, { Component, PropTypes } from "react";
import SettingHeader from "./SettingHeader.jsx";
import SettingInput from "./widgets/SettingInput.jsx";
import SettingNumber from "./widgets/SettingNumber.jsx";
import SettingPassword from "./widgets/SettingPassword.jsx";
import SettingRadio from "./widgets/SettingRadio.jsx";
import SettingToggle from "./widgets/SettingToggle.jsx";
......@@ -10,6 +11,7 @@ import SettingSelect from "./widgets/SettingSelect.jsx";
const SETTING_WIDGET_MAP = {
"string": SettingInput,
"number": SettingNumber,
"password": SettingPassword,
"select": SettingSelect,
"radio": SettingRadio,
......
......@@ -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."
/>;
import React from "react";
import SettingInput from "./SettingInput";
const SettingNumber = ({ type = "number", ...props }) =>
<SettingInput {...props} type="number" />
export default SettingNumber;
......@@ -86,7 +86,7 @@ const SECTIONS = [
key: "email-smtp-port",
display_name: "SMTP Port",
placeholder: "587",
type: "string",
type: "number",
required: true,
validations: [["integer", "That's not a valid port number"]]
},
......@@ -236,6 +236,34 @@ const SECTIONS = [
getHidden: (settings) => !settings["enable-embedding"]
}
]
},
{
name: "Caching",
settings: [
{
key: "enable-query-caching",
display_name: "Enable Caching",
type: "boolean"
},
{
key: "query-caching-min-ttl",
display_name: "Minimum Query Duration",
type: "number",
getHidden: (settings) => !settings["enable-query-caching"]
},
{
key: "query-caching-ttl-ratio",
display_name: "Cache Time-To-Live (TTL)",
type: "number",
getHidden: (settings) => !settings["enable-query-caching"]
},
{
key: "query-caching-max-kb",
display_name: "Max Cache Entry Size",
type: "number",
getHidden: (settings) => !settings["enable-query-caching"]
}
]
}
];
for (const section of SECTIONS) {
......
......@@ -17,6 +17,7 @@ type Props = {
activeText?: string,
failedText?: string,
successText?: string,
forceActiveStyle?: boolean
}
type State = {
......@@ -48,7 +49,8 @@ export default class ActionButton extends Component<*, Props, State> {
normalText: "Save",
activeText: "Saving...",
failedText: "Save failed",
successText: "Saved"
successText: "Saved",
forceActiveStyle: false
};
componentWillUnmount() {
......@@ -96,13 +98,13 @@ export default class ActionButton extends Component<*, Props, State> {
render() {
// eslint-disable-next-line no-unused-vars
const { normalText, activeText, failedText, successText, actionFn, className, children, ...props } = this.props;
const { normalText, activeText, failedText, successText, actionFn, className, forceActiveStyle, children, ...props } = this.props;
const { active, result } = this.state;
return (
<Button
{...props}
className={cx(className, {
className={forceActiveStyle ? cx('Button', 'Button--waiting') : cx(className, {
'Button--waiting pointer-events-none': active,
'Button--success': result === 'success',
'Button--danger': result === 'failed'
......@@ -114,7 +116,7 @@ export default class ActionButton extends Component<*, Props, State> {
activeText
: result === "success" ?
<span>
<Icon name='check' size={12} />
{forceActiveStyle ? null : <Icon name='check' size={12} /> }
<span className="ml1">{successText}</span>
</span>
: result === "failed" ?
......
......@@ -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 ReactDOM from "react-dom";
import Button from "metabase/components/Button.jsx";
......
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>
......
......@@ -79,13 +79,8 @@ class BrowserSelect extends Component {
<PopoverWithTrigger
ref="popover"
className={className}
triggerElement={
<div className={"flex align-center " + (!value ? " text-grey-3" : "")}>
<span className="AdminSelect-content mr1">{selectedName}</span>
<Icon className="AdminSelect-chevron flex-align-right" name="chevrondown" size={12} />
</div>
}
triggerClasses={cx("AdminSelect", className)}
triggerElement={<SelectButton hasValue={!!value}>{selectedName}</SelectButton>}
triggerClasses={className}
verticalAttachments={["top"]}
isInitiallyOpen={isInitiallyOpen}
>
......@@ -117,6 +112,17 @@ class BrowserSelect extends Component {
}
}
export const SelectButton = ({ hasValue, children }) =>
<div className={"AdminSelect flex align-center " + (!hasValue ? " text-grey-3" : "")}>
<span className="AdminSelect-content mr1">{children}</span>
<Icon className="AdminSelect-chevron flex-align-right" name="chevrondown" size={12} />
</div>
SelectButton.propTypes = {
hasValue: PropTypes.bool,
children: PropTypes.any,
};
export class Option extends Component {
static propTypes = {
children: PropTypes.any,
......
/* @flow */
import React, { Component, PropTypes } from "react";
import ReactDOM from "react-dom";
import ExplicitSize from "metabase/components/ExplicitSize";
type Props = {
className?: string,
items: any[],
renderItem: (item: any) => any,
renderItemSmall: (item: any) => any
};
type State = {
isShrunk: ?boolean
};
@ExplicitSize
export default class ShrinkableList extends Component<*, Props, State> {
state: State = {
isShrunk: null
}
componentWillReceiveProps() {
this.setState({
isShrunk: null
})
}
componentDidMount() {
this.componentDidUpdate();
}
componentDidUpdate() {
const container = ReactDOM.findDOMNode(this)
const { isShrunk } = this.state;
if (container && isShrunk === null) {
this.setState({
isShrunk: container.scrollWidth !== container.offsetWidth
})
}
}
render() {
const { items, className, renderItemSmall, renderItem } = this.props;
const { isShrunk } = this.state;
return (
<div className={className}>
{ items.map(item =>
isShrunk ?
renderItemSmall(item)
:
renderItem(item)
)}
</div>
);
}
}
......@@ -14,7 +14,7 @@ export default class Tooltip extends Component {
}
static propTypes = {
tooltip: PropTypes.node.isRequired,
tooltip: PropTypes.node,
children: PropTypes.element.isRequired,
isEnabled: PropTypes.bool,
verticalAttachments: PropTypes.array,
......
......@@ -32,6 +32,10 @@
justify-content: space-between;
}
.justify-end {
justify-content: flex-end;
}
.align-start {
align-items: flex-start;
}
......
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