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

Merge branch 'master' of github.com:metabase/metabase into merge-release-0.22.0

parents 9bbbca65 5bb4ef23
No related branches found
No related tags found
No related merge requests found
Showing
with 124 additions and 122 deletions
{
"plugins": ["transform-flow-strip-types", "transform-decorators-legacy"],
"plugins": ["transform-flow-strip-types", "add-react-displayname", "transform-decorators-legacy"],
"presets": ["es2015", "stage-0", "react"],
"env": {
"development": {
......
......@@ -17,7 +17,7 @@ esproposal.decorators=ignore
esproposal.class_static_fields=enable
esproposal.class_instance_fields=enable
suppress_comment= \\(.\\|\n\\)*\\$FlowFixMe
module.name_mapper='.*\(.css\)' -> 'CSSModule'
module.name_mapper='.*\(\.css\)' -> 'CSSModule'
module.system=haste
strip_root=true
module.file_ext=.js
......
declare module "selenium-webdriver" {
declare class WebDriver {
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>;
sendKeys(keys: string): Promise<void>;
clear(): Promise<void>;
getText(): Promise<string>;
getAttribute(attribute: string): Promise<string>;
}
declare class WebElementPromise extends WebElement {
}
declare class Condition {
}
declare class By {
static css(selector: string): By;
static xpath(selector: string): By;
}
declare class until {
static elementLocated(selector: By): Condition;
}
}
......@@ -40,7 +40,7 @@ export default class VisualizationSettings extends React.Component {
name={CardVisualization.iconName}
size={12}
/>
{CardVisualization.displayName}
{CardVisualization.uiName}
<Icon className="ml1" name="chevrondown" size={8} />
</span>
);
......@@ -73,7 +73,7 @@ export default class VisualizationSettings extends React.Component {
onClick={this.setDisplay.bind(null, vizType)}
>
<Icon name={viz.iconName} size={12} />
<span className="ml1">{viz.displayName}</span>
<span className="ml1">{viz.uiName}</span>
</li>
)}
</ul>
......
......@@ -3,7 +3,7 @@ import React, { Component, PropTypes } from "react";
import LineAreaBarChart from "./components/LineAreaBarChart.jsx";
export default class AreaChart extends LineAreaBarChart {
static displayName = "Area";
static uiName = "Area";
static identifier = "area";
static iconName = "area";
static noun = "area chart";
......
......@@ -3,7 +3,7 @@ import React, { Component, PropTypes } from "react";
import LineAreaBarChart from "./components/LineAreaBarChart.jsx";
export default class BarChart extends LineAreaBarChart {
static displayName = "Bar";
static uiName = "Bar";
static identifier = "bar";
static iconName = "bar";
static noun = "bar chart";
......
......@@ -8,7 +8,7 @@ import { getSettings } from "metabase/lib/visualization_settings";
import i from "icepick";
export default class Funnel extends Component {
static displayName = "Funnel";
static uiName = "Funnel";
static identifier = "funnel";
static iconName = "funnel";
......
......@@ -3,7 +3,7 @@ import React, { Component, PropTypes } from "react";
import LineAreaBarChart from "./components/LineAreaBarChart.jsx";
export default class LineChart extends LineAreaBarChart {
static displayName = "Line";
static uiName = "Line";
static identifier = "line";
static iconName = "line";
static noun = "line chart";
......
......@@ -6,7 +6,7 @@ import PinMap from "./PinMap.jsx";
import { ChartSettingsError } from "metabase/visualizations/lib/errors";
export default class Map extends Component {
static displayName = "Map";
static uiName = "Map";
static identifier = "map";
static iconName = "pinmap";
......
......@@ -27,7 +27,7 @@ const OTHER_SLICE_MIN_PERCENTAGE = 0.003;
const PERCENT_REGEX = /percent/i;
export default class PieChart extends Component {
static displayName = "Pie";
static uiName = "Pie";
static identifier = "pie";
static iconName = "pie";
......
......@@ -17,7 +17,7 @@ const MAP_COMPONENTS_BY_TYPE = {
}
export default class PinMap extends Component {
static displayName = "Pin Map";
static uiName = "Pin Map";
static identifier = "pin_map";
static iconName = "pinmap";
......
......@@ -11,7 +11,7 @@ import Color from "color";
const BORDER_RADIUS = 5;
export default class Progress extends Component {
static displayName = "Progress";
static uiName = "Progress";
static identifier = "progress";
static iconName = "progress";
......
......@@ -14,7 +14,7 @@ import i from "icepick";
import d3 from "d3";
export default class Scalar extends Component {
static displayName = "Number";
static uiName = "Number";
static identifier = "scalar";
static iconName = "number";
......
......@@ -3,7 +3,7 @@ import React, { Component, PropTypes } from "react";
import LineAreaBarChart from "./components/LineAreaBarChart.jsx";
export default class ScatterPlot extends LineAreaBarChart {
static displayName = "Scatter";
static uiName = "Scatter";
static identifier = "scatter";
static iconName = "bubble";
static noun = "scatter plot";
......
......@@ -7,7 +7,7 @@ import * as DataGrid from "metabase/lib/data_grid";
import _ from "underscore";
export default class Bar extends Component {
static displayName = "Table";
static uiName = "Table";
static identifier = "table";
static iconName = "table";
......
......@@ -19,7 +19,7 @@ import "xkcdplot/humor-sans";
import cx from "classnames";
export default class XKCDChart extends Component {
static displayName = "XKCD"
static uiName = "XKCD"
static identifier = "xkcd";
static iconName = "pinmap";
......
......@@ -13,17 +13,24 @@ describeE2E("admin/settings", () => {
describe("admin settings", () => {
it("should persist a setting", async () => {
// pick a random site name to try updating it to
const siteName = "Metabase" + Math.random();
// load the "general" pane of the admin settings
await d.get(`${server.host}/admin/settings/general`);
// first just make sure the site name isn't already set (it shouldn't since we're using a random name)
expect(await d.select(".SettingsInput").wait().attribute("value")).not.toBe(siteName);
// clear the site name input, send the keys corresponding to the site name, then blur to trigger the update
await d.select(".SettingsInput").wait().clear().sendKeys(siteName).blur();
// wait for the loading indicator to show success
await d.select(".SaveStatus.text-success").wait();
// reload the page
await d.get(`${server.host}/admin/settings/general`);
// verify the site name value was persisted
expect(await d.select(".SettingsInput").wait().attribute("value")).toBe(siteName);
});
});
......
import {
waitForElementRemoved,
waitForElementAndClick,
waitForElementAndSendKeys,
screenshot,
describeE2E,
ensureLoggedIn
......@@ -17,132 +14,134 @@ describeE2E("query_builder", () => {
describe("tables", () => {
it("should allow users to create pivot tables", async () => {
await driver.get(`${server.host}/q`);
// load the query builder and screenshot blank
await d.get("/q");
await d.screenshot("screenshots/qb-initial.png");
await screenshot(driver, "screenshots/qb-initial.png");
// pick the orders table (assumes database is already selected, i.e. there's only 1 database)
await d.select("#TablePicker .List-item a:contains(Orders)").wait().click();
await waitForElementAndClick(driver, "#TablePicker .List-item:first-child>a");
await d.select(":react(AggregationWidget)").wait().click();
await waitForElementAndClick(driver, "#Query-section-aggregation");
await waitForElementAndClick(driver, "#AggregationPopover .List-item:nth-child(2)>a");
await d.select("#AggregationPopover .List-item:nth-child(2)>a").wait().click();
await waitForElementAndClick(driver, ".Query-section.Query-section-breakout #BreakoutWidget");
await waitForElementAndClick(driver, "#BreakoutPopover .List-section:nth-child(3) .List-section-header");
await waitForElementAndClick(driver, "#BreakoutPopover .List-item:nth-child(12)>a");
await d.select(".Query-section.Query-section-breakout #BreakoutWidget").wait().click();
await d.select("#BreakoutPopover .List-section:nth-child(3) .List-section-header").wait().click();
await d.select("#BreakoutPopover .List-item:nth-child(12)>a").wait().click();
await waitForElementAndClick(driver, ".Query-section.Query-section-breakout #BreakoutWidget .AddButton");
await waitForElementAndClick(driver, "#BreakoutPopover .List-item:first-child .Field-extra>a");
await waitForElementAndClick(driver, "#TimeGroupingPopover .List-item:nth-child(4)>a");
await d.select(".Query-section.Query-section-breakout #BreakoutWidget .AddButton").wait().click();
await d.select("#BreakoutPopover .List-item:first-child .Field-extra>a").wait().click();
await d.select("#TimeGroupingPopover .List-item:nth-child(4)>a").wait().click();
await waitForElementAndClick(driver, ".Button.RunButton");
await d.select(".Button.RunButton").wait().click();
await waitForElementRemoved(driver, ".Loading", 20000);
await screenshot(driver, "screenshots/qb-pivot-table.png");
await d.select(".Loading").waitRemoved(20000);
await d.screenshot("screenshots/qb-pivot-table.png");
// save question
await waitForElementAndClick(driver, ".Header-buttonSection:first-child");
await waitForElementAndSendKeys(driver, "#SaveQuestionModal input[name='name']", 'Pivot Table');
await waitForElementAndClick(driver, "#SaveQuestionModal .Button.Button--primary");
await d.select(".Header-buttonSection:first-child").wait().click();
await d.select("#SaveQuestionModal input[name='name']").wait().sendKeys("Pivot Table");
await d.select("#SaveQuestionModal .Button.Button--primary").wait().click().waitRemoved(); // wait for the modal to be removed
// add to new dashboard
await waitForElementAndClick(driver, "#QuestionSavedModal .Button.Button--primary");
await waitForElementAndSendKeys(driver, "#CreateDashboardModal input[name='name']", 'Main Dashboard');
await waitForElementAndClick(driver, "#CreateDashboardModal .Button.Button--primary");
await d.select("#QuestionSavedModal .Button.Button--primary").wait().click();
await d.select("#CreateDashboardModal input[name='name']").wait().sendKeys("Main Dashboard");
await d.select("#CreateDashboardModal .Button.Button--primary").wait().click().waitRemoved(); // wait for the modal to be removed
// save dashboard
await waitForElementAndClick(driver, ".EditHeader .Button.Button--primary");
await waitForElementRemoved(driver, ".EditHeader");
await d.select(".EditHeader .Button.Button--primary").wait().click();
await d.select(".EditHeader").waitRemoved();
});
});
describe("charts", () => {
xit("should allow users to create line charts", async () => {
await driver.get(`${server.host}/q`);
await d.get("/q");
// select orders table
await waitForElementAndClick(driver, "#TablePicker .List-item:first-child>a");
await d.select("#TablePicker .List-item:first-child>a").wait().click();
// select filters
await waitForElementAndClick(driver, ".GuiBuilder-filtered-by .Query-section:not(.disabled) a");
await d.select(".GuiBuilder-filtered-by .Query-section:not(.disabled) a").wait().click();
await waitForElementAndClick(driver, "#FilterPopover .List-item:first-child>a");
await d.select("#FilterPopover .List-item:first-child>a").wait().click();
await waitForElementAndClick(driver, ".Button[data-ui-tag='relative-date-shortcut-this-year']");
await waitForElementAndClick(driver, ".Button[data-ui-tag='add-filter']:not(.disabled)");
await d.select(".Button[data-ui-tag='relative-date-shortcut-this-year']").wait().click();
await d.select(".Button[data-ui-tag='add-filter']:not(.disabled)").wait().click();
// select aggregations
await waitForElementAndClick(driver, "#Query-section-aggregation");
await waitForElementAndClick(driver, "#AggregationPopover .List-item:nth-child(2)>a");
await d.select("#Query-section-aggregation").wait().click();
await d.select("#AggregationPopover .List-item:nth-child(2)>a").wait().click();
// select breakouts
await waitForElementAndClick(driver, ".Query-section.Query-section-breakout>div");
await d.select(".Query-section.Query-section-breakout>div").wait().click();
await waitForElementAndClick(driver, "#BreakoutPopover .List-item:first-child .Field-extra>a");
await waitForElementAndClick(driver, "#TimeGroupingPopover .List-item:nth-child(3)>a");
await d.select("#BreakoutPopover .List-item:first-child .Field-extra>a").wait().click();
await d.select("#TimeGroupingPopover .List-item:nth-child(3)>a").wait().click();
// run query
await waitForElementAndClick(driver, ".Button.RunButton");
await d.select(".Button.RunButton").wait().click();
await waitForElementAndClick(driver, "#VisualizationTrigger");
await d.select("#VisualizationTrigger").wait().click();
// this step occassionally fails without the timeout
await driver.sleep(500);
await waitForElementAndClick(driver, "#VisualizationPopover li:nth-child(3)");
await d.sleep(500);
await d.select("#VisualizationPopover li:nth-child(3)").wait().click();
await screenshot(driver, "screenshots/qb-line-chart.png");
// save question
await waitForElementAndClick(driver, ".Header-buttonSection:first-child");
await waitForElementAndSendKeys(driver, "#SaveQuestionModal input[name='name']", 'Line Chart');
await waitForElementAndClick(driver, "#SaveQuestionModal .Button.Button--primary");
await d.select(".Header-buttonSection:first-child").wait().click();
await d.select("#SaveQuestionModal input[name='name']").wait().sendKeys("Line Chart");
await d.select("#SaveQuestionModal .Button.Button--primary").wait().click();
// add to existing dashboard
await driver.sleep(500);
await waitForElementAndClick(driver, "#QuestionSavedModal .Button.Button--primary");
await waitForElementAndClick(driver, "#AddToDashSelectDashModal .SortableItemList-list li:first-child>a");
await d.sleep(500);
await d.select("#QuestionSavedModal .Button.Button--primary").wait().click();
await d.select("#AddToDashSelectDashModal .SortableItemList-list li:first-child>a").wait().click();
// save dashboard
await waitForElementAndClick(driver, ".EditHeader .Button.Button--primary");
await waitForElementRemoved(driver, ".EditHeader");
await d.select(".EditHeader .Button.Button--primary").wait().click();
await d.select(".EditHeader").waitRemoved();
});
xit("should allow users to create bar charts", async () => {
// load line chart
await driver.get(`${server.host}/card/2`);
await d.get("/card/2");
// dismiss saved questions modal
await waitForElementAndClick(driver, ".Modal .Button.Button--primary");
await d.select(".Modal .Button.Button--primary").wait().click();
// change breakouts
await waitForElementAndClick(driver, ".View-section-breakout.SelectionModule");
await d.select(".View-section-breakout.SelectionModule").wait().click();
await waitForElementAndClick(driver, "#BreakoutPopover .List-item:first-child .Field-extra>a");
await waitForElementAndClick(driver, "#TimeGroupingPopover .List-item:nth-child(4)>a");
await d.select("#BreakoutPopover .List-item:first-child .Field-extra>a").wait().click();
await d.select("#TimeGroupingPopover .List-item:nth-child(4)>a").wait().click();
// change visualization
await waitForElementAndClick(driver, "#VisualizationTrigger");
await d.select("#VisualizationTrigger").wait().click();
// this step occassionally fails without the timeout
await driver.sleep(500);
await waitForElementAndClick(driver, "#VisualizationPopover li:nth-child(4)");
await d.sleep(500);
await d.select("#VisualizationPopover li:nth-child(4)").wait().click();
// run query
await waitForElementAndClick(driver, ".Button.RunButton");
await waitForElementRemoved(driver, ".Loading", 20000);
await d.select(".Button.RunButton").wait().click();
await d.select(".Loading").waitRemoved(20000);
await screenshot(driver, "screenshots/qb-bar-chart.png");
// save question
await waitForElementAndClick(driver, ".Header-buttonSection:first-child");
await waitForElementAndSendKeys(driver, "#SaveQuestionModal input[name='name']", 'Bar Chart');
await waitForElementAndClick(driver, "#SaveQuestionModal .Button.Button--primary");
await d.select(".Header-buttonSection:first-child").wait().click();
await d.select("#SaveQuestionModal input[name='name']").wait().sendKeys("Bar Chart");
await d.select("#SaveQuestionModal .Button.Button--primary").wait().click();
// add to existing dashboard
await driver.sleep(500);
await waitForElementAndClick(driver, "#QuestionSavedModal .Button.Button--primary");
await waitForElementAndClick(driver, "#AddToDashSelectDashModal .SortableItemList-list li:first-child>a");
await d.sleep(500);
await d.select("#QuestionSavedModal .Button.Button--primary").wait().click();
await d.select("#AddToDashSelectDashModal .SortableItemList-list li:first-child>a").wait().click();
// save dashboard
await waitForElementAndClick(driver, ".EditHeader .Button.Button--primary");
await waitForElementRemoved(driver, ".EditHeader");
await d.select(".EditHeader .Button.Button--primary").wait().click();
await d.select(".EditHeader").waitRemoved();
});
});
});
......@@ -131,7 +131,7 @@ export const screenshotFailures = async (driver) => {
export const getJson = async (driver, url) => {
await driver.get(url);
try {
let source = await driver.getPageSource();
let source = await driver.findElement(By.tagName("body")).getText();
return JSON.parse(source);
} catch (e) {
return null;
......@@ -143,17 +143,45 @@ export const checkLoggedIn = async (server, driver, email) => {
return currentUser && currentUser.email === email;
}
const getSessionId = (server, email) => {
server.sessions = server.sessions || {};
return server.sessions[email];
}
const setSessionId = (server, email, sessionId) => {
server.sessions = server.sessions || {};
server.sessions[email] = sessionId;
}
export const ensureLoggedIn = async (server, driver, email, password) => {
if (await checkLoggedIn(server, driver, email)) {
console.log("already logged in");
console.log("LOGIN: already logged in");
return;
}
console.log("logging in");
const sessionId = getSessionId(server, email);
if (sessionId != null) {
console.log("LOGIN: trying previous session");
await driver.get(`${server.host}/`);
await driver.manage().deleteAllCookies();
await driver.manage().addCookie("metabase.SESSION_ID", sessionId);
await driver.get(`${server.host}/`);
if (await checkLoggedIn(server, driver, email)) {
console.log("LOGIN: cached session succeeded");
return;
} else {
console.log("LOGIN: cached session failed");
setSessionId(server, email, null);
}
}
console.log("LOGIN: logging in manually");
await driver.get(`${server.host}/`);
await driver.manage().deleteAllCookies();
await driver.get(`${server.host}/`);
await loginMetabase(driver, email, password);
await waitForUrl(driver, `${server.host}/`);
const sessionCookie = await driver.manage().getCookie("metabase.SESSION_ID");
setSessionId(server, email, sessionCookie.value);
}
export const loginMetabase = async (driver, email, password) => {
......@@ -189,7 +217,9 @@ export const describeE2E = (name, options, describeCallback) => {
]);
global.driver = webdriver.driver;
global.d = new Driver(webdriver.driver);
global.d = new Driver(webdriver.driver, {
base: server.host
});
global.server = server;
await driver.get(`${server.host}/`);
......
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