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

Merge branch 'master' of github.com:metabase/metabase into update-frontend-deps-and-devtool

parents d9483053 044155f7
Branches
Tags
No related merge requests found
{
"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;
}
}
......@@ -207,7 +207,7 @@ export default class QueryHeader extends Component {
}
// persistence buttons on saved cards
if (!isNew) {
if (!isNew && card.can_write) {
if (!isEditing) {
if (this.state.recentlySaved) {
// existing card + not editing + recently saved = save confirmation
......@@ -219,7 +219,6 @@ export default class QueryHeader extends Component {
</span>
</button>
]);
} else {
// edit button
buttonSections.push([
......
......@@ -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}/`);
......
......@@ -203,7 +203,7 @@
"Get `Card` with ID."
[id]
(-> (read-check Card id)
(hydrate :creator :dashboard_count :labels :collection)
(hydrate :creator :dashboard_count :labels :can_write :collection)
(assoc :actor_id *current-user-id*)
(->> (events/publish-event! :card-read))
(dissoc :actor_id)))
......
......@@ -186,6 +186,7 @@
:id $
:display "table"
:visualization_settings {}
:can_write true
:created_at $
:database_id database-id ; these should be inferred from the dataset_query
:table_id table-id
......
......@@ -157,9 +157,9 @@ amdefine@>=0.0.4:
version "1.0.1"
resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
annotate-react-dom@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/annotate-react-dom/-/annotate-react-dom-1.0.0.tgz#446b4667620c2d071344401344747b4fd7680b6f"
annotate-react-dom@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/annotate-react-dom/-/annotate-react-dom-1.1.0.tgz#607c14d2565198d4bf365f6f05c60a61ba939a16"
ansi-escapes@^1.1.0:
version "1.4.0"
......@@ -572,6 +572,10 @@ babel-messages@^6.8.0:
dependencies:
babel-runtime "^6.0.0"
babel-plugin-add-react-displayname@^0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/babel-plugin-add-react-displayname/-/babel-plugin-add-react-displayname-0.0.4.tgz#bc2a74bcbee6e505025b3352fea85ee7bc4c6f7c"
babel-plugin-check-es2015-constants@^6.3.13:
version "6.8.0"
resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.8.0.tgz#dbf024c32ed37bfda8dee1e76da02386a8d26fe7"
......@@ -1188,6 +1192,10 @@ bo-selector@0.0.10:
version "0.0.10"
resolved "https://registry.yarnpkg.com/bo-selector/-/bo-selector-0.0.10.tgz#9816dcb00adf374ea87941a863b2acfc026afa3e"
bo-selector@0.0.10:
version "0.0.10"
resolved "https://registry.yarnpkg.com/bo-selector/-/bo-selector-0.0.10.tgz#9816dcb00adf374ea87941a863b2acfc026afa3e"
body-parser@^1.12.4:
version "1.15.2"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.15.2.tgz#d7578cf4f1d11d5f6ea804cef35dc7a7ff6dae67"
......@@ -4286,6 +4294,14 @@ mz@^2.3.1, mz@^2.6.0:
object-assign "^4.0.1"
thenify-all "^1.0.0"
mz@^2.6.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/mz/-/mz-2.6.0.tgz#c8b8521d958df0a4f2768025db69c719ee4ef1ce"
dependencies:
any-promise "^1.0.0"
object-assign "^4.0.1"
thenify-all "^1.0.0"
nan@^2.3.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.5.0.tgz#aa8f1e34531d807e9e27755b234b4a6ec0c152a8"
......@@ -6872,11 +6888,11 @@ watchpack@^0.2.1:
chokidar "^1.0.0"
graceful-fs "^4.1.2"
webchauffeur@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/webchauffeur/-/webchauffeur-1.1.0.tgz#b0abe64978c655472b181ff4c49752c33fdd12f7"
webchauffeur@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/webchauffeur/-/webchauffeur-1.2.0.tgz#d03d7f38d336c2ae55099d978adabc0e75a50d6f"
dependencies:
annotate-react-dom "^1.0.0"
annotate-react-dom "^1.1.0"
css-to-xpath "^0.1.0"
mz "^2.6.0"
promise-chain-decorator "^1.2.0"
......@@ -7074,6 +7090,10 @@ xpath-builder@0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/xpath-builder/-/xpath-builder-0.0.7.tgz#67d6bbc3f6a320ec317e3e6368c5706b6111deec"
xpath-builder@0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/xpath-builder/-/xpath-builder-0.0.7.tgz#67d6bbc3f6a320ec317e3e6368c5706b6111deec"
xtend@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment