Skip to content
Snippets Groups Projects
Unverified Commit 288494de authored by Cam Saul's avatar Cam Saul Committed by GitHub
Browse files

Merge x.37.5back into master (#14282)


* #11907 Repro: Tooltip should display correct value for unaggregated data [ci skip] (#14102)


Co-authored-by: default avatarflamber <1447303+flamber@users.noreply.github.com>

* #11957 Repro: Changing filters shouldn't drop aggregated filter [ci skip] (#14103)

* #13187 Repro: FE should respect (BE) settings for `auto_run_queries` [ci skip] (#14120)

* Mac App build script tweaks

* Defer the parser and its related construction until it's needed (#14133)

Expression parser construction is expensive (mainly due to grammar validation
via Chevrotain's performSelfAnalysis). Thus, instantiating the parsers at the
initialization time will lead to a signifant delay in DOMContentLoaded,
thereby affecting (among others) the embedding performance.

To avoid this, simply defer that step until parsing is really needed
(which is actually far far later, i.e. when the user is using the
notebook).

* Add a clarifying comment regarding parser lazy loading

* Fix #13187: FE should respect settings for `auto_run_queries` on page load (#14121)

Closes #13187

* Remove initial state from `auto_run_queries`

* Assert that `auto_run_queries` is ON by default for sample dataset

This test confirms that toggle works without explicitly setting the initial state to `true`.

* Improve toggle's accessibility

Reference: https://a11y-style-guide.com/style-guide/section-forms.html#kssref-forms-toggles



* Adjust Cypress tests

* #13415 Repro: Visualization type should be respected when entering a question from a dashboard [ci skip] (#14142)

* Fix #13299: Misconfigured "Port" label and input fields (#14134)

Closes #13299

* Add `aria-labelledby` attribute to numeric form fields

* Update affected Cypress tests

* #13175 Repro: Can not do arithmetic in filter expressions (#14155)

* #14193 Repro: Custom column is dropped after removing filter (that comes after aggregation) [ci skip] (#14197)

* Fix 13868 (second try) (#14248)

* Fix 13868

* Unskip Cypress test

* #14255 Repro: CC and table column same name [REGRESSION] [ci skip] (#14267)

* Backport migrations fixups from master (#14276)

* Misc code improvements (#14247)

* Add test-cypress-open-no-backend

* Enable test endpoints for dev

* More are+ test util function to metabase.test

* Improve the way with-temp-defaults are reloaded for models

* Simplify defendpoint macro a bit; convert tests to new style

* Rework a few more tests

* Code cleanup

Co-authored-by: default avatarNemanja Glumac <31325167+nemanjaglumac@users.noreply.github.com>
Co-authored-by: default avatarflamber <1447303+flamber@users.noreply.github.com>
Co-authored-by: default avatarAriya Hidayat <ariya@metabase.com>
parent 52c5ad3e
No related branches found
No related tags found
No related merge requests found
Showing
with 286 additions and 43 deletions
......@@ -17,11 +17,11 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.34.3</string>
<string>0.37.2</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>0.34.3</string>
<string>0.37.2</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>
......
File moved
......@@ -8,5 +8,4 @@ cd "$script_directory"
source "../bin/check-clojure-cli.sh"
check_clojure_cli
cd OSX/macos_release
clojure -M -m macos-release $@
(ns macos-release.create-dmg
(:require [macos-release
[codesign :as codesign]
[common :as c]]))
[common :as c]]
[metabuild-common.core :as u]))
(def ^:private dmg (c/artifact "Metabase.dmg"))
(def ^:private temp-dmg "/tmp/Metabase.dmg")
......@@ -10,7 +11,7 @@
(defn- copy-app-to-source-dir! []
(c/step "Copy app to source dir"
(c/delete-file! source-dir)
(u/delete-file-if-exists! source-dir)
(c/create-directory-unless-exists! source-dir)
(let [source-app (c/assert-file-exists (c/artifact "Metabase.app"))
dest-app (str source-dir "/Metabase.app")]
......@@ -19,7 +20,7 @@
(codesign/verify-codesign dest-app))))
(defn- create-dmg-from-source-dir! []
(c/delete-file! temp-dmg)
(u/delete-file-if-exists! temp-dmg)
(c/step (format "Create DMG %s from source dir %s" temp-dmg source-dir)
(c/sh "hdiutil" "create"
"-srcfolder" (str (c/assert-file-exists source-dir) "/")
......@@ -71,14 +72,14 @@
(defn- add-applications-shortcut! []
(c/assert-file-exists mounted-dmg)
(c/sh "osascript" (c/assert-file-exists (str c/macos-source-dir "/macos_release/addShortcut.scpt"))))
(c/sh "osascript" (c/assert-file-exists (u/filename c/macos-source-dir "src" "macos_release" "addShortcut.scpt"))))
(defn- delete-temporary-files-in-dmg!
"Delete any temporary files that might have creeped in."
[]
(c/assert-file-exists mounted-dmg)
(c/delete-file! (str mounted-dmg "/.Trashes")
(str mounted-dmg "/.fseventsd")))
(u/delete-file-if-exists! (str mounted-dmg "/.Trashes")
(str mounted-dmg "/.fseventsd")))
(defn- set-dmg-permissions! []
(c/sh "chmod" "-Rf" "go-w" (c/assert-file-exists mounted-dmg)))
......@@ -88,7 +89,7 @@
(defn- compress-and-copy-dmg!
[]
(c/delete-file! dmg)
(u/delete-file-if-exists! dmg)
(c/step (format "Compress DMG %s -> %s" (c/assert-file-exists temp-dmg) dmg)
(c/sh "hdiutil" "convert" temp-dmg
"-format" "UDZO"
......@@ -98,11 +99,11 @@
(defn- delete-temp-files! []
(c/step "Delete temp files"
(c/delete-file! temp-dmg source-dir)))
(u/delete-file-if-exists! temp-dmg source-dir)))
(defn create-dmg! []
(c/step (format "Create %s" dmg)
(c/delete-file! dmg temp-dmg source-dir)
(u/delete-file-if-exists! dmg temp-dmg source-dir)
(copy-app-to-source-dir!)
(create-dmg-from-source-dir!)
(with-mounted-dmg [_ temp-dmg]
......
......@@ -6,6 +6,7 @@
[macos-release
[codesign :as codesign]
[common :as c]]
[metabuild-common.core :as u]
[pl.danieljanus.tagsoup :as tagsoup])
(:import [java.io File FileOutputStream OutputStreamWriter]
java.nio.charset.StandardCharsets))
......@@ -17,7 +18,7 @@
(defn- verify-zip-codesign []
(c/step (format "Verify code signature of Metabase.app archived in %s" zip-file)
(let [temp-file "/tmp/Metabase.zip"]
(c/delete-file! temp-file)
(u/delete-file-if-exists! temp-file)
(c/sh {:quiet? true}
"unzip" (c/assert-file-exists zip-file)
"-d" temp-file)
......@@ -26,7 +27,7 @@
(codesign/verify-codesign unzipped-app-file)))))
(defn- create-zip-archive! []
(c/delete-file! zip-file)
(u/delete-file-if-exists! zip-file)
(c/step (format "Create ZIP file %s" zip-file)
(c/assert-file-exists (c/artifact "Metabase.app"))
;; Use ditto instead of zip to preserve the codesigning -- see https://forums.developer.apple.com/thread/116831
......@@ -38,8 +39,8 @@
(defn- generate-file-signature [filename]
(c/step (format "Generate signature for %s" filename)
(let [private-key (c/assert-file-exists (str c/macos-source-dir "/dsa_priv.pem"))
script (c/assert-file-exists (str c/root-directory "/bin/lib/sign_update.rb"))
(let [private-key (c/assert-file-exists (u/filename c/macos-source-dir "dsa_priv.pem"))
script (c/assert-file-exists (u/filename c/macos-source-dir "bin" "sign_update.rb"))
[out] (c/sh script (c/assert-file-exists filename) private-key)
signature (str/trim out)]
(assert (seq signature))
......@@ -89,7 +90,7 @@
:sparkle/dsaSignature signature}]]]])))
(defn- generate-appcast! []
(c/delete-file! appcast-file)
(u/delete-file-if-exists! appcast-file)
(c/step (format "Generate appcast %s" appcast-file)
(with-open [os (FileOutputStream. (File. appcast-file))
w (OutputStreamWriter. os StandardCharsets/UTF_8)]
......@@ -117,7 +118,7 @@
[:body (release-notes-body)]])
(defn- generate-release-notes! []
(c/delete-file! release-notes-file)
(u/delete-file-if-exists! release-notes-file)
(c/step (format "Generate release notes %s" release-notes-file)
(let [notes (release-notes)]
(with-open [w (io/writer release-notes-file)]
......
......@@ -8,6 +8,7 @@ const FormInputWidget = ({ placeholder, field }) => (
<NumericInput
className="Form-input full"
placeholder={placeholder}
aria-labelledby={`${field.name}-label`}
{...formDomOnlyProps(field)}
/>
);
......
......@@ -3,7 +3,12 @@ import React from "react";
import Toggle from "metabase/components/Toggle";
const FormToggleWidget = ({ field }) => (
<Toggle aria-labelledby={`${field.name}-label`} {...field} />
<Toggle
aria-labelledby={`${field.name}-label`}
aria-checked={field.value}
role="switch"
{...field}
/>
);
export default FormToggleWidget;
......@@ -282,7 +282,6 @@ const forms = {
type: "boolean",
title: t`Automatically run queries when doing simple filtering and summarizing`,
description: t`When this is on, Metabase will automatically run queries when users do simple explorations with the Summarize and Filter buttons when viewing a table or chart. You can turn this off if querying this database is slow. This setting doesn’t affect drill-throughs or SQL queries.`,
initial: true,
hidden: !engine,
},
{
......
import { parse } from "metabase/lib/expressions/parser";
import { compile } from "metabase/lib/expressions/compile";
import { suggest } from "metabase/lib/expressions/suggest";
import { syntax } from "metabase/lib/expressions/syntax";
// combine compile/suggest/syntax so we only need to parse once
export function processSource(options) {
// Lazily load all these parser-related stuff, because parser construction is expensive
// https://github.com/metabase/metabase/issues/13472
const parse = require("./parser").parse;
const compile = require("./compile").compile;
const suggest = require("./suggest").suggest;
const syntax = require("./syntax").syntax;
const { source, targetOffset } = options;
let expression;
......
......@@ -22,10 +22,11 @@ export const BackendResource = createSharedResource("BackendResource", {
create({ dbKey = DEFAULT_DB_KEY }) {
const dbFile = getDbFile();
const absoluteDbKey = dbKey ? __dirname + dbKey : dbFile;
if (process.env["E2E_HOST"] && dbKey === DEFAULT_DB_KEY) {
const e2eHost = process.env["E2E_HOST"];
if (e2eHost) {
return {
dbKey: absoluteDbKey,
host: process.env["E2E_HOST"],
host: e2eHost,
process: { kill: () => {} },
};
} else {
......
......@@ -25,9 +25,7 @@ describe("mongodb > admin > add", () => {
typeAndBlurUsingLabel("Name", "QA Mongo4");
typeAndBlurUsingLabel("Host", "localhost");
cy.findByPlaceholderText("27017")
.click()
.type("27017");
typeAndBlurUsingLabel("Port", "27017");
typeAndBlurUsingLabel("Database name", "sample");
typeAndBlurUsingLabel("Username", "metabase");
typeAndBlurUsingLabel("Password", "metasample123");
......
......@@ -25,11 +25,7 @@ describe("mysql > admin > add", () => {
typeAndBlurUsingLabel("Name", "QA MySQL8");
typeAndBlurUsingLabel("Host", "localhost");
// TODO: "Port" label and input field are misconfigured (input field is missing `aria-labeledby` attribute)
// typeAndBlurUsingLabel("Port", "3306") => this will not work (switching to placeholder temporarily)
cy.findByPlaceholderText("3306")
.click()
.type("3306");
typeAndBlurUsingLabel("Port", "3306");
typeAndBlurUsingLabel("Database name", "sample");
typeAndBlurUsingLabel("Username", "metabase");
typeAndBlurUsingLabel("Password", "metasample123");
......
......@@ -25,11 +25,7 @@ describe("postgres > admin > add", () => {
typeAndBlurUsingLabel("Name", "QA Postgres12");
typeAndBlurUsingLabel("Host", "localhost");
// TODO: "Port" label and input field are misconfigured (input field is missing `aria-labeledby` attribute)
// typeAndBlurUsingLabel("Port", "5432") => this will not work (switching to placeholder temporarily)
cy.findByPlaceholderText("5432")
.click()
.type("5432");
typeAndBlurUsingLabel("Port", "5432");
typeAndBlurUsingLabel("Database name", "sample");
typeAndBlurUsingLabel("Username", "metabase");
typeAndBlurUsingLabel("Password", "metasample123");
......
import { parse } from "metabase/lib/expressions/parser";
describe("metabase/lib/expressions/parser", () => {
describe("in aggregation mode", () => {
function parseAggregation(source) {
const tokenVector = null;
const startRule = "aggregation";
return parse({ source, tokenVector, startRule });
}
it("should handle a simple aggregation", () => {
expect(() => parseAggregation("Sum([Price])")).not.toThrow();
});
it("should handle a conditional aggregation", () => {
expect(() => parseAggregation("CountIf( [Discount] > 0 )")).not.toThrow();
});
it.skip("should handle a complex conditional aggregation", () => {
expect(() =>
parseAggregation("CountIf( ( [Subtotal] + [Tax] ) > 100 )"),
).not.toThrow();
});
});
describe("in expression mode", () => {
function parseExpression(source) {
const tokenVector = null;
const startRule = "expression";
return parse({ source, tokenVector, startRule });
}
it("should handle a conditional using CASE", () => {
expect(() =>
parseExpression("Case( [Discount] > 0, 'Sale', 'Normal' )"),
).not.toThrow();
});
it.skip("should handle a complex conditional using CASE", () => {
expect(() =>
parseExpression("Case( [Price]-[Discount] > 50, 'Deal', 'Regular' )"),
).not.toThrow();
});
});
});
import { processSource } from "metabase/lib/expressions/process";
describe("metabase/lib/expressions/process", () => {
describe("processSource", () => {
it("should non throw", () => {
expect(() =>
processSource({ source: "1", targetOffset: null }),
).not.toThrow();
});
it("should handle valid input", () => {
const { compileError, syntaxTree } = processSource({
source: "1",
targetOffset: null,
});
expect(compileError).toBeUndefined();
expect(syntaxTree).toBeDefined();
expect(syntaxTree.children).toBeDefined();
expect(syntaxTree.children.length).toEqual(1);
});
it("should handle invalid input", () => {
const { compileError } = processSource({
source: "1+",
targetOffset: null,
});
expect(compileError.toString()).toEqual(
"NoViableAltException: Expected expression",
);
});
});
});
......@@ -40,6 +40,28 @@ describe("scenarios > admin > databases > edit", () => {
cy.findByText("Connection");
cy.findByText("Scheduling");
});
it("`auto_run_queries` toggle should be ON by default for `SAMPLE_DATASET`", () => {
cy.visit("/admin/databases/1");
cy.findByLabelText(
"Automatically run queries when doing simple filtering and summarizing",
).should("have.attr", "aria-checked", "true");
});
it("should respect the settings for automatic query running (metabase#13187)", () => {
cy.log("**--Turn off `auto run queries`--**");
cy.request("PUT", "/api/database/1", {
auto_run_queries: false,
});
cy.visit("/admin/databases/1");
cy.log("**Reported failing on v0.36.4**");
cy.findByLabelText(
"Automatically run queries when doing simple filtering and summarizing",
).should("have.attr", "aria-checked", "false");
});
});
describe("Scheduling tab", () => {
......
......@@ -202,7 +202,7 @@ describe("scenarios > dashboard > chained filter", () => {
});
}
it.skip("can use a chained filter with embedded SQL questions (metabase#13868)", () => {
it("can use a chained filter with embedded SQL questions (metabase#13868)", () => {
createDashboardWithQuestion({}, dashboardId => {
// Enable embedding for this dashboard with both the city and state filters enabled
cy.request("PUT", `/api/dashboard/${dashboardId}`, {
......
......@@ -10,7 +10,7 @@ import {
import { SAMPLE_DATASET } from "__support__/cypress_sample_dataset";
const { ORDERS, ORDERS_ID, PRODUCTS } = SAMPLE_DATASET;
const { ORDERS, ORDERS_ID, PRODUCTS, PRODUCTS_ID } = SAMPLE_DATASET;
const customFormulas = [
{
......@@ -363,4 +363,74 @@ describe("scenarios > question > custom columns", () => {
cy.findAllByText("57911");
});
});
it.skip("should not be dropped if filter is changed after aggregation (metaabase#14193)", () => {
const CC_NAME = "Double the fun";
cy.request("POST", "/api/card", {
name: "14193",
dataset_query: {
type: "query",
query: {
"source-query": {
"source-table": ORDERS_ID,
filter: [">", ["field-id", ORDERS.SUBTOTAL], 0],
aggregation: [["sum", ["field-id", ORDERS.TOTAL]]],
breakout: [
["datetime-field", ["field-id", ORDERS.CREATED_AT], "year"],
],
},
expressions: {
[CC_NAME]: ["*", ["field-literal", "sum", "type/Float"], 2],
},
},
database: 1,
},
display: "table",
visualization_settings: {},
}).then(({ body: { id: QUESTION_ID } }) => {
cy.visit(`/question/${QUESTION_ID}`);
// Test displays collapsed filter - click on number 1 to expand and show the filter name
cy.get(".Icon-filter")
.parent()
.contains("1")
.click();
cy.findByText(/Subtotal is greater than 0/i)
.parent()
.find(".Icon-close")
.click();
cy.findByText(CC_NAME);
});
});
it.skip("should handle identical custom column and table column names (metabase#14255)", () => {
// Uppercase is important for this reproduction on H2
const CC_NAME = "CATEGORY";
cy.request("POST", "/api/card", {
name: "14255",
dataset_query: {
type: "query",
query: {
"source-table": PRODUCTS_ID,
expressions: {
[CC_NAME]: ["concat", ["field-id", PRODUCTS.CATEGORY], "2"],
},
aggregation: [["count"]],
breakout: [["expression", CC_NAME]],
},
database: 1,
},
display: "table",
visualization_settings: {},
}).then(({ body: { id: QUESTION_ID } }) => {
cy.visit(`/question/${QUESTION_ID}`);
cy.findByText(CC_NAME);
cy.findByText("Gizmo2");
});
});
});
......@@ -8,7 +8,7 @@ import {
import { SAMPLE_DATASET } from "__support__/cypress_sample_dataset";
const { ORDERS, PRODUCTS, PRODUCTS_ID } = SAMPLE_DATASET;
const { ORDERS, ORDERS_ID, PRODUCTS, PRODUCTS_ID } = SAMPLE_DATASET;
describe("scenarios > question > filter", () => {
beforeEach(() => {
......@@ -402,4 +402,49 @@ describe("scenarios > question > filter", () => {
cy.findByText("Rustic Paper Wallet"); // Product ID 1, Gizmo
});
});
it.skip("should not drop aggregated filters (metabase#11957)", () => {
const AGGREGATED_FILTER = "Count is less than or equal to 20";
cy.request("POST", "/api/card", {
name: "11957",
dataset_query: {
database: 1,
query: {
"source-query": {
"source-table": ORDERS_ID,
filter: [">", ["field-id", ORDERS.CREATED_AT], "2020-01-01"],
aggregation: [["count"]],
breakout: [
["datetime-field", ["field-id", ORDERS.CREATED_AT], "day"],
],
},
filter: ["<=", ["field-literal", "count", "type/Integer"], 20],
},
type: "query",
},
display: "table",
visualization_settings: {},
}).then(({ body: { id: QUESTION_ID } }) => {
cy.visit(`/question/${QUESTION_ID}`);
});
// Test shows two filter collapsed - click on number 2 to expand and show filter names
cy.get(".Icon-filter")
.parent()
.contains("2")
.click();
cy.findByText(AGGREGATED_FILTER);
cy.findByText(/^Created At is after/i)
.parent()
.find(".Icon-close")
.click();
cy.log(
"**Removing or changing filters shouldn't remove aggregated filter**",
);
cy.findByText(AGGREGATED_FILTER);
});
});
......@@ -299,6 +299,37 @@ describe("scenarios > visualizations > drillthroughs > chart drill", () => {
cy.findByText("There was a problem with your question").should("not.exist");
});
it.skip("should display correct value in a tooltip for unaggregated data (metabase#11907)", () => {
cy.request("POST", "/api/card", {
name: "11907",
dataset_query: {
type: "native",
native: {
query:
"SELECT parsedatetime('2020-01-01', 'yyyy-MM-dd') AS \"d\", 5 AS \"c\" UNION ALL\nSELECT parsedatetime('2020-01-01', 'yyyy-MM-dd') AS \"d\", 2 AS \"c\" UNION ALL\nSELECT parsedatetime('2020-01-01', 'yyyy-MM-dd') AS \"d\", 3 AS \"c\" UNION ALL\nSELECT parsedatetime('2020-01-02', 'yyyy-MM-dd') AS \"d\", 1 AS \"c\" UNION ALL\nSELECT parsedatetime('2020-01-02', 'yyyy-MM-dd') AS \"d\", 4 AS \"c\"",
"template-tags": {},
},
database: 1,
},
display: "line",
visualization_settings: {},
}).then(({ body: { id: QUESTION_ID } }) => {
cy.visit(`/question/${QUESTION_ID}`);
clickLineDot({ index: 0 });
popover().within(() => {
cy.findByText("January 1, 2020");
cy.findByText("10");
});
clickLineDot({ index: 1 });
popover().within(() => {
cy.findByText("January 2, 2020");
cy.findByText("5");
});
});
});
describe("for an unsaved question", () => {
before(() => {
restore();
......@@ -329,3 +360,9 @@ describe("scenarios > visualizations > drillthroughs > chart drill", () => {
});
});
});
function clickLineDot({ index } = {}) {
cy.get(".Visualization .dot")
.eq(index)
.click({ force: true });
}
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