diff --git a/.circleci/config.yml b/.circleci/config.yml index de2794e4b4029b5a9457256af3a5d2439c7c699f..97533d0389e37ac9722b6fea85a7f58e922249a4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -321,15 +321,9 @@ jobs: yaml-linter: executor: node steps: - - attach-workspace - - run: - name: Install yamllint - command: npm install yaml-lint - no_output_timeout: 2m - - run: - name: Lint YAML files - command: ./node_modules/.bin/yamllint `find resources -name '*.yaml'` - no_output_timeout: 2m + - run-yarn-command: + command-name: Lint YAML files + command: lint-yaml `find resources -name '*.yaml'` verify-i18n-files: executor: node @@ -712,6 +706,7 @@ workflows: - yaml-linter: requires: - checkout + - fe-deps - verify-i18n-files: requires: diff --git a/.gitignore b/.gitignore index 772e1c944bd200188bfb36392b6525cee9087b4a..e7d7974a24266ea5a43d18fbbf77c6b01e46a1b9 100644 --- a/.gitignore +++ b/.gitignore @@ -72,3 +72,4 @@ profiles.clj target/checksum.txt xcshareddata xcuserdata +dev/src/dev/nocommit/ diff --git a/frontend/src/metabase/icon_paths.js b/frontend/src/metabase/icon_paths.js index 81f9ddcaf90dbba1fbe7a0563f139e7d88d5dcb2..f8690869212878c60db329d328750df43a550e3d 100644 --- a/frontend/src/metabase/icon_paths.js +++ b/frontend/src/metabase/icon_paths.js @@ -169,8 +169,6 @@ export const ICON_PATHS = { "M3.556 4h24.888a3.556 3.556 0 1 1 0 7.111H3.556a3.556 3.556 0 1 1 0-7.111zm4 11.556h16.888a2.222 2.222 0 1 1 0 4.444H7.556a2.222 2.222 0 0 1 0-4.444zM12 24.444h8a2.222 2.222 0 0 1 0 4.445h-8a2.222 2.222 0 0 1 0-4.445z", funnel: "M3.18586974,3.64621479 C2.93075885,3.28932022 3.08031197,3 3.5066208,3 L28.3780937,3 C28.9190521,3 29.0903676,3.34981042 28.7617813,3.77995708 L18.969764,16.5985181 L18.969764,24.3460671 C18.969764,24.8899179 18.5885804,25.5564176 18.133063,25.8254534 C18.133063,25.8254534 12.5698889,29.1260709 12.5673818,28.9963552 C12.4993555,25.4767507 12.5749031,16.7812673 12.5749031,16.7812673 L3.18586974,3.64621479 Z", - funnel_add: - "M22.5185184,5.27947653 L17.2510286,5.27947653 L17.2510286,9.50305775 L22.5185184,9.50305775 L22.5185184,14.7825343 L26.7325102,14.7825343 L26.7325102,9.50305775 L32,9.50305775 L32,5.27947653 L26.7325102,5.27947653 L26.7325102,0 L22.5185184,0 L22.5185184,5.27947653 Z M14.9369872,0.791920724 C14.9369872,0.791920724 2.77552871,0.83493892 1.86648164,0.83493892 C0.957434558,0.83493892 0.45215388,1.50534608 0.284450368,1.77831828 C0.116746855,2.05129048 -0.317642562,2.91298361 0.398382661,3.9688628 C1.11440788,5.024742 9.74577378,17.8573356 9.74577378,17.8573356 C9.74577378,17.8573356 9.74577394,28.8183645 9.74577378,29.6867194 C9.74577362,30.5550744 9.83306175,31.1834301 10.7557323,31.6997692 C11.6784029,32.2161084 12.4343349,31.9564284 12.7764933,31.7333621 C13.1186517,31.5102958 19.6904355,27.7639669 20.095528,27.4682772 C20.5006204,27.1725875 20.7969652,26.5522071 20.7969651,25.7441659 C20.7969649,24.9361247 20.7969651,18.2224765 20.7969651,18.2224765 L21.6163131,16.9859755 L18.152048,15.0670739 C18.152048,15.0670739 17.3822517,16.199685 17.2562629,16.4000338 C17.1302741,16.6003826 16.8393552,16.9992676 16.8393551,17.7062886 C16.8393549,18.4133095 16.8393551,24.9049733 16.8393551,24.9049733 L13.7519708,26.8089871 C13.7519708,26.8089871 13.7318369,18.3502323 13.7318367,17.820601 C13.7318366,17.2909696 13.8484216,16.6759061 13.2410236,15.87149 C12.6336257,15.0670739 5.59381579,4.76288686 5.59381579,4.76288686 L14.9359238,4.76288686 L14.9369872,0.791920724 Z", funnel_outline: { path: "M3.186 3.646C2.93 3.29 3.08 3 3.506 3h24.872c.541 0 .712.35.384.78L18.97 16.599v7.747c0 .544-.381 1.21-.837 1.48 0 0-5.563 3.3-5.566 3.17-.068-3.52.008-12.215.008-12.215L3.185 3.646z", diff --git a/frontend/test/metabase/scenarios/admin/datamodel/metrics.cy.spec.js b/frontend/test/metabase/scenarios/admin/datamodel/metrics.cy.spec.js index 42e6be80735c3df85d7c4daf5ddfdf511fd3b660..6f4e1326f4328600de4db7204b5b9ca20243e3b2 100644 --- a/frontend/test/metabase/scenarios/admin/datamodel/metrics.cy.spec.js +++ b/frontend/test/metabase/scenarios/admin/datamodel/metrics.cy.spec.js @@ -155,12 +155,10 @@ describe("scenarios > admin > datamodel > metrics", () => { cy.contains("Result: 18703"); // update name and description, set a revision note, and save the update - cy.get('[name="name"]') - .clear() - .type("orders >10"); - cy.get('[name="description"]') - .clear() - .type("Count of orders with a total over $10."); + cy.get('[name="name"]').type("{selectall}orders >10"); + cy.get('[name="description"]').type( + "{selectall}Count of orders with a total over $10.", + ); cy.get('[name="revision_message"]').type("time for a change"); cy.contains("Save changes").click(); @@ -204,8 +202,7 @@ describe("scenarios > admin > datamodel > metrics", () => { cy.findByText("Custom Expression").click(); cy.get("[contenteditable='true']") .click() - .clear() - .type("Sum([Discount] * [Quantity])", { delay: 100 }); + .type("{selectall}Sum([Discount] * [Quantity])", { delay: 100 }); cy.findByPlaceholderText("Name (required)") .click() .type("CE", { delay: 100 }); diff --git a/frontend/test/metabase/scenarios/admin/settings/settings.cy.spec.js b/frontend/test/metabase/scenarios/admin/settings/settings.cy.spec.js index 0e7a44c6c83b95378aacd6aaeba1ca30db13df10..c749b1cf38a441e72ebde5cb7e5a705b568ace12 100644 --- a/frontend/test/metabase/scenarios/admin/settings/settings.cy.spec.js +++ b/frontend/test/metabase/scenarios/admin/settings/settings.cy.spec.js @@ -81,8 +81,9 @@ describe("scenarios > admin > settings", () => { emailInput() .click() - .clear() - .type("other.email@metabase.com") + // "hack" substitute for `cy.clear()` as per: + // https://github.com/cypress-io/cypress/issues/2056#issuecomment-702607741 + .type("{selectall}other.email@metabase.com") .blur(); cy.wait("@saveSettings"); @@ -91,12 +92,9 @@ describe("scenarios > admin > settings", () => { emailInput().should("have.value", "other.email@metabase.com"); // reset the email - emailInput() - .click() - .clear() - .type("bob@metabase.com") - .blur(); - cy.wait("@saveSettings"); + cy.request("PUT", "/api/setting/admin-email", { + value: "bob@metabase.com", + }); }); it("should check for working https before enabling a redirect", () => { diff --git a/frontend/test/metabase/scenarios/dashboard/dashboard-drill.cy.spec.js b/frontend/test/metabase/scenarios/dashboard/dashboard-drill.cy.spec.js index 6fc7ec142204ec6c576f2c567f46ba131a10ebd0..01cd4e930670597840db81b5b211c3a7c6ec2a3f 100644 --- a/frontend/test/metabase/scenarios/dashboard/dashboard-drill.cy.spec.js +++ b/frontend/test/metabase/scenarios/dashboard/dashboard-drill.cy.spec.js @@ -3,7 +3,7 @@ import { restore, modal, popover, - selectDashboardFilter, + withSampleDataset, } from "__support__/cypress"; describe("scenarios > dashboard > dashboard drill", () => { @@ -172,73 +172,93 @@ describe("scenarios > dashboard > dashboard drill", () => { }); it("should pass multiple filters for numeric column on drill-through (metabase#13062)", () => { - // go to admin > data model > sample dataset > reviews - cy.visit("/admin/datamodel/database/1/table/4"); - - // Set "Rating" Field type to: "Category" ("Score" is selected by default) - cy.findByText("Score").click(); - // "Category" is not visible and any other method couldn't find it, including `Popover().contains("Category")` - cy.get(".ReactVirtualized__Grid") - .scrollTo("top") - .contains("Category") - .click(); - // make sure the field updated before navigating away - cy.findByText("Category"); - - // go straight to simple question > reviews - cy.visit("/question/new?database=1&table=4"); - - // save the question - cy.findByText("Save").click(); - cy.get(".Modal").within(() => { - cy.findByText("Save").click(); + // Preparation for the test: "Arrange and Act phase" - see repro steps in #13062 + // 1. set "Rating" Field type to: "Category" + withSampleDataset(({ REVIEWS }) => { + cy.request("PUT", `/api/field/${REVIEWS.RATING}`, { + special_type: "type/Category", + }); }); - // and add it to a new dashboard - cy.findByText("Yes please!").click(); - cy.findByText("Create a new dashboard").click(); - cy.findByLabelText("Name") - .click() - .type("13062"); - cy.findByText("Create").click(); - - // make sure we switched to the dashboard in edit mode - cy.findByText("You're editing this dashboard."); - - // add filter - cy.get(".Icon-filter").click(); - cy.findByText("Other Categories").click(); - // and link it to the card - selectDashboardFilter(cy.get(".DashCard"), "Rating"); - - // save the dashboard and exit editing mode - cy.findByText("Save").click(); - cy.findByText("You're editing this dashboard.").should("not.exist"); + // 2. create a question based on Reviews + cy.request("POST", `/api/card`, { + name: "13062Q", + dataset_query: { + database: 1, + query: { + "source-table": 4, + }, + type: "query", + }, + display: "table", + visualization_settings: {}, + }).then(({ body: { id: questionId } }) => { + // 3. create a dashboard + cy.request("POST", "/api/dashboard", { + name: "13062D", + }).then(({ body: { id: dashboardId } }) => { + // add filter to the dashboard + cy.request("PUT", `/api/dashboard/${dashboardId}`, { + parameters: [ + { + id: "18024e69", + name: "Category", + slug: "category", + type: "category", + }, + ], + }); - // add values to the filter - cy.findByText("Category").click(); - popover().within(() => { - cy.findByText("5").click(); - cy.findByText("4").click(); - }); - cy.findByText("Add filter").click(); + // add previously created question to the dashboard + cy.request("POST", `/api/dashboard/${dashboardId}/cards`, { + cardId: questionId, + }).then(({ body: { id: dashCardId } }) => { + // connect filter to that question + cy.request("PUT", `/api/dashboard/${dashboardId}/cards`, { + cards: [ + { + id: dashCardId, + card_id: questionId, + row: 0, + col: 0, + sizeX: 8, + sizeY: 6, + parameter_mappings: [ + { + parameter_id: "18024e69", + card_id: questionId, + target: ["dimension", ["field-id", 31]], // 31 = REVIEWS.RATING + }, + ], + }, + ], + }); + }); - // drill-through - cy.findByText("xavier").click(); - cy.findByText("=").click(); + // NOTE: The actual "Assertion" phase begins here + cy.log("**Reported failing on Metabase 1.34.3 and 0.36.2**"); - cy.log("**Reported failing on Metabase 1.34.3 and 0.36.2**"); - cy.findByText("Reviewer is xavier"); - cy.findByText("Rating is equal to 2 selections"); - // wait for data to finish loading - cy.get(".LoadingSpinner").should("not.exist"); + cy.log("**The first case**"); + // set filter values (ratings 5 and 4) directly through the URL + cy.visit(`/dashboard/${dashboardId}?category=5&category=4`); - cy.log("**Test the second case reported in this issue**"); - // go back to the dashboard - cy.visit("/dashboard/6?category=5&category=4"); - cy.findByText("2 selections"); + // drill-through + cy.findByText("xavier").click(); + cy.findByText("=").click(); - cy.findByText("Reviews").click(); // the card title - cy.findByText("Rating is equal to 2 selections"); + cy.findByText("Reviewer is xavier"); + cy.findByText("Rating is equal to 2 selections"); + cy.contains("Reprehenderit non error"); // xavier's review + + cy.log("**The second case**"); + // go back to the dashboard + cy.visit(`/dashboard/${dashboardId}?category=5&category=4`); + cy.findByText("2 selections"); + + cy.findByText("13062Q").click(); // the card title + cy.findByText("Rating is equal to 2 selections"); + cy.contains("Ad perspiciatis quis et consectetur."); // 5 star review + }); + }); }); }); diff --git a/frontend/test/metabase/scenarios/dashboard/dashboard.cy.spec.js b/frontend/test/metabase/scenarios/dashboard/dashboard.cy.spec.js index 0c590c967cf8825c35d7ad6ec146c5413b09abbe..dece99dfc9c7f30d9b84490ad81e917843c13068 100644 --- a/frontend/test/metabase/scenarios/dashboard/dashboard.cy.spec.js +++ b/frontend/test/metabase/scenarios/dashboard/dashboard.cy.spec.js @@ -1,4 +1,10 @@ -import { popover, restore, signInAsAdmin } from "__support__/cypress"; +import { + popover, + restore, + signInAsAdmin, + withSampleDataset, + selectDashboardFilter, +} from "__support__/cypress"; // Mostly ported from `dashboard.e2e.spec.js` // *** Haven't ported: should add the parameter values to state tree for public dashboards @@ -100,6 +106,76 @@ describe("scenarios > dashboard", () => { cy.findByText("Doppleganger"); }); + it("should link filters to custom question with filtered aggregate data (metabase#11007)", () => { + // programatically create and save a question as per repro instructions in #11007 + withSampleDataset(({ ORDERS, PRODUCTS }) => { + cy.request("POST", "/api/card", { + name: "11007", + dataset_query: { + database: 1, + filter: [">", ["field-literal", "sum", "type/Float"], 100], + query: { + "source-table": 2, + aggregation: [["sum", ORDERS.TOTAL]], + breakout: [ + ["datetime-field", ORDERS.CREATED_AT, "day"], + ["fk->", ORDERS.PRODUCT_ID, PRODUCTS.ID], + ["fk->", ORDERS.PRODUCT_ID, PRODUCTS.CATEGORY], + ], + filter: ["=", ORDERS.USER_ID, 1], + }, + type: "query", + }, + display: "table", + visualization_settings: {}, + }); + }); + + // create a dashboard + cy.request("POST", "/api/dashboard", { + name: "dash:11007", + }); + + cy.visit("/collection/root"); + // enter newly created dashboard + cy.findByText("dash:11007").click(); + cy.findByText("This dashboard is looking empty."); + // add previously created question to it + cy.get(".Icon-pencil").click(); + cy.get(".Icon-add") + .last() + .click(); + cy.findByText("11007").click(); + + // add first filter + cy.get(".Icon-filter").click(); + popover().within(() => { + cy.findByText("Time").click(); + cy.findByText("All Options").click(); + }); + // and connect it to the card + selectDashboardFilter(cy.get(".DashCard"), "Created At"); + + // add second filter + cy.get(".Icon-filter").click(); + popover().within(() => { + cy.findByText("ID").click(); + }); + // and connect it to the card + selectDashboardFilter(cy.get(".DashCard"), "Product ID"); + + // add third filter + cy.get(".Icon-filter").click(); + popover().within(() => { + cy.findByText("Other Categories").click(); + }); + // and connect it to the card + selectDashboardFilter(cy.get(".DashCard"), "Category"); + + cy.findByText("Save").click(); + cy.findByText("You're editing this dashboard.").should("not.exist"); + }); + describe("revisions screen", () => { it("should open and close", () => { cy.visit("/dashboard/1"); diff --git a/frontend/test/metabase/scenarios/question/filter.cy.spec.js b/frontend/test/metabase/scenarios/question/filter.cy.spec.js index 2ba6a54ada370b62810bffc74cb333a44c367ee7..e22feae32736d7e72236ae9228fb2b6e9082ec8d 100644 --- a/frontend/test/metabase/scenarios/question/filter.cy.spec.js +++ b/frontend/test/metabase/scenarios/question/filter.cy.spec.js @@ -36,10 +36,10 @@ describe("scenarios > question > filter", () => { // Add Q2 to a dashboard cy.findByText("Yes please!").click(); - cy.get(".Icon-dashboard").click(); + cy.findByText("Orders in a dashboard").click(); // Add two dashboard filters - cy.get(".Icon-funnel_add").click(); + cy.get(".Icon-filter").click(); cy.findByText("Time").click(); cy.findByText("All Options").click(); cy.findAllByText("Select…") @@ -47,7 +47,7 @@ describe("scenarios > question > filter", () => { .click(); cy.findByText("Created At").click(); - cy.get(".Icon-funnel_add").click(); + cy.get(".Icon-filter").click(); cy.findByText("Other Categories").click(); cy.findAllByText("Select…") .last() @@ -57,14 +57,20 @@ describe("scenarios > question > filter", () => { }); // Save dashboard and refresh page - cy.findByText("Done").click(); - cy.findByText("You're editing this dashboard."); + cy.findAllByText("Done") + .first() + .click(); + cy.findByText("Save").click(); - cy.findByText("Save").should("not.exist"); + cy.findByText("You're editing this dashboard.").should("not.exist"); // Check category search - cy.get(".Icon-empty").should("not.exist"); - cy.findByText("Category").click(); + cy.get("fieldset") + .last() + .within(() => { + cy.findByText("Category").click(); + }); + cy.log("**Failing to show dropdown in v0.36.0 through v.0.37.0**"); cy.findByText("Gadget").click(); cy.findByText("Add filter").click(); }); diff --git a/package.json b/package.json index 7a8fb4bb88ebb8c3adc37bc7a85857e94c591f30..e0e84ff178ccc1dd13d2f50eb2fc413ddfdb1089 100644 --- a/package.json +++ b/package.json @@ -170,7 +170,8 @@ "webpack-dev-server": "^2.9.1", "webpack-notifier": "^1.8.0", "webpack-postcss-tools": "^1.1.2", - "xhr-mock": "^2.4.1" + "xhr-mock": "^2.4.1", + "yaml-lint": "^1.2.4" }, "scripts": { "dev": "concurrently --kill-others -p name -n 'backend,frontend,docs' -c 'blue,green,yellow' 'lein run' 'yarn build-hot' 'yarn docs'", @@ -179,6 +180,7 @@ "lint-prettier": "yarn && prettier -l 'frontend/**/*.{js,jsx,css}' || (echo '\nThese files are not formatted correctly. Did you forget to \"yarn prettier\"?' && false)", "lint-flow": "yarn && flow check", "lint-docs-links": "yarn && ./bin/verify-doc-links", + "lint-yaml": "yamllint", "test": "yarn test-unit && yarn test-timezones && yarn test-integration && yarn test-cypress", "test-unit": "yarn && jest --maxWorkers=2 --config jest.unit.conf.json", "test-unit-watch": "yarn test-unit --watch", diff --git a/src/metabase/email.clj b/src/metabase/email.clj index 0dd2b11c1d61b8822210cb3298690e500cc5a9a8..22905389dded898a3b8cd6101fb5b9871e38f77f 100644 --- a/src/metabase/email.clj +++ b/src/metabase/email.clj @@ -11,6 +11,10 @@ [schema.core :as s]) (:import javax.mail.Session)) +;; https://github.com/metabase/metabase/issues/11879#issuecomment-713816386 +(when-not *compile-files* + (System/setProperty "mail.mime.splitlongparameters" "false")) + ;;; CONFIG (defsetting email-from-address diff --git a/test/metabase/email_test.clj b/test/metabase/email_test.clj index c540d53e486c6e51e1315827a342aa189e22120f..4b9c25285333f949bf48aa5c081ca462c4fc32af 100644 --- a/test/metabase/email_test.clj +++ b/test/metabase/email_test.clj @@ -1,12 +1,19 @@ (ns metabase.email-test "Various helper functions for testing email functionality." ;; TODO - Move to something like `metabase.test.util.email`? - (:require [expectations :refer :all] + (:require [clojure.java.io :as io] + [clojure.test :refer :all] [medley.core :as m] - [metabase.email :as email] + [metabase + [email :as email] + [util :refer [prog1]]] [metabase.test.data.users :as user] - [metabase.test.util :as tu]) - (:import javax.activation.MimeType)) + [metabase.test.util :as tu] + [postal + [core :as postal] + [message :as message]]) + (:import java.io.File + javax.activation.MimeType)) ;; TODO - this should be made dynamic so it's (at least theoretically) possible to use this in parallel (def inbox @@ -138,17 +145,84 @@ :to #{email}} email-map)]})) -;; simple test of email sending capabilities -(expect - [{:from (email/email-from-address) - :to ["test@test.com"] - :subject "101 Reasons to use Metabase" - :body [{:type "text/html; charset=utf-8" - :content "101. Metabase will make you a better person"}]}] - (with-fake-inbox - (email/send-message! - :subject "101 Reasons to use Metabase" - :recipients ["test@test.com"] - :message-type :html - :message "101. Metabase will make you a better person") - (@inbox "test@test.com"))) +(defn temp-csv + [file-basename content] + (prog1 (File/createTempFile file-basename ".csv") + (with-open [file (io/writer <>)] + (.write ^java.io.Writer file ^String content)))) + +(defn mock-send-email! + "To stub out email sending, instead returning the would-be email contents as a string" + [smtp-credentials email-details] + (-> email-details + message/make-jmessage + message/message->str)) + +(deftest send-message!-test + (tu/with-temporary-setting-values [email-from-address "lucky@metabase.com" + email-smtp-host "smtp.metabase.com" + email-smtp-username "lucky" + email-smtp-password "d1nner3scapee!" + email-smtp-port "1025" + email-smtp-security "none"] + (testing "basic sending" + (is (= + [{:from (email/email-from-address) + :to ["test@test.com"] + :subject "101 Reasons to use Metabase" + :body [{:type "text/html; charset=utf-8" + :content "101. Metabase will make you a better person"}]}] + (with-fake-inbox + (email/send-message! + :subject "101 Reasons to use Metabase" + :recipients ["test@test.com"] + :message-type :html + :message "101. Metabase will make you a better person") + (@inbox "test@test.com"))))) + (testing "with an attachment" + (let [recipient "csv_user@example.com" + csv-contents "hugs_with_metabase,hugs_without_metabase\n1,0" + csv-file (temp-csv "metabase-reasons" csv-contents) + params {:subject "101 Reasons to use Metabase" + :recipients [recipient] + :message-type :attachments + :message [{:type "text/html; charset=utf-8" + :content "100. Metabase will hug you when you're sad"} + {:type :attachment + :content-type "text/csv" + :file-name "metabase-reasons.csv" + :content csv-file + :description "very scientific data"}]}] + (testing "it sends successfully" + (is (= + [{:from (email/email-from-address) + :to [recipient] + :subject "101 Reasons to use Metabase" + :body [{:type "text/html; charset=utf-8" + :content "100. Metabase will hug you when you're sad"} + {:type :attachment + :content-type "text/csv" + :file-name "metabase-reasons.csv" + :content csv-file + :description "very scientific data"}]}] + (with-fake-inbox + (m/mapply email/send-message! params) + (@inbox recipient))))) + (testing "it does not wrap long, non-ASCII filenames" + (with-redefs [email/send-email! mock-send-email!] + (let [basename "this-is-quite-long-and-has-non-Âſçïı-characters" + csv-file (temp-csv basename csv-contents) + params-with-problematic-file (-> params + (assoc-in [:message 1 :file-name] (str basename ".csv")) + (assoc-in [:message 1 :content] csv-file))] + ;; Bad string (ignore the linebreak): + ;; Content-Disposition: attachment; filename="=?UTF-8?Q?this-is-quite-long-and-ha?= =?UTF-8?Q?s-non- + ;; =C3=82\"; filename*1=\"=C5=BF=C3=A7=C3=AF=C4=B1-characters.csv?=" + ;; ^-- this is the problem + ;; Acceptable string (again, ignore the linebreak): + ;; Content-Disposition: attachment; filename= "=?UTF-8?Q?this-is-quite-long-and-ha?= + ;; =?UTF-8?Q?s-non-=C3=82=C5=BF=C3=A7=C3=AF=C4=B1-characters.csv?=" + + (is (re-find + #"(?s)Content-Disposition: attachment.+filename=.+this-is-quite-[\-\s?=0-9a-zA-Z]+-characters.csv" + (m/mapply email/send-message! params-with-problematic-file)))))))))) diff --git a/webpack.config.js b/webpack.config.js index 7a7e1dc65691020839a08c80b1b996ca615de74c..1a3a5e846576a7288381ec9e353b67ba68c5e2f0 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -234,6 +234,21 @@ if (NODE_ENV === "hot") { headers: { "Access-Control-Allow-Origin": "*", }, + // tweak stats to make the output in the console more legible + // TODO - once we update webpack to v4+ we can just use `errors-warnings` preset + stats: { + assets: false, + cached: false, + cachedAssets: false, + chunks: false, + chunkModules: false, + chunkOrigins: false, + modules: false, + color: true, + hash: false, + warnings: true, + errorDetals: false, + }, // if webpack doesn't reload UI after code change in development // watchOptions: { // aggregateTimeout: 300, diff --git a/yarn.lock b/yarn.lock index e71b7d811af63404b37f7467119b137342eab991..b4a31dec347afe398058892a86c736f8cb7ecb4a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1595,7 +1595,7 @@ async@2.6.1: dependencies: lodash "^4.17.10" -async@^1.5.0: +async@^1.4.0, async@^1.5.0: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= @@ -3094,7 +3094,7 @@ camelcase@^1.0.2: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" integrity sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk= -camelcase@^2.0.0: +camelcase@^2.0.0, camelcase@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= @@ -3433,7 +3433,7 @@ cliui@^2.1.0: right-align "^0.1.1" wordwrap "0.0.2" -cliui@^3.2.0: +cliui@^3.0.3, cliui@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0= @@ -6852,7 +6852,7 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -ini@^1.3.3, ini@^1.3.4: +ini@^1.3.0, ini@^1.3.3, ini@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== @@ -7843,7 +7843,7 @@ js-tokens@^3.0.2: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= -js-yaml@^3.13.1, js-yaml@^3.4.3, js-yaml@^3.5.1, js-yaml@^3.7.0, js-yaml@^3.8.4: +js-yaml@^3.10.0, js-yaml@^3.13.1, js-yaml@^3.4.3, js-yaml@^3.5.1, js-yaml@^3.7.0, js-yaml@^3.8.4: version "3.14.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== @@ -8151,6 +8151,13 @@ leaflet@^1.2.0: resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.7.1.tgz#10d684916edfe1bf41d688a3b97127c0322a2a19" integrity sha512-/xwPEBidtg69Q3HlqPdU3DnrXQOvQU/CCHA1tcDQVzOwm91YMYaILjNp7L4Eaw5Z4sOYdbBz6koWyibppd8Zqw== +leprechaun@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/leprechaun/-/leprechaun-0.0.2.tgz#8b96514a9e634c53fbe59a8094f3378c8fb2084d" + integrity sha1-i5ZRSp5jTFP75ZqAlPM3jI+yCE0= + dependencies: + log-symbols "^1.0.2" + leven@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580" @@ -8458,7 +8465,7 @@ lodash.memoize@^4.1.2: resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= -lodash.merge@^4.4.0: +lodash.merge@^4.4.0, lodash.merge@^4.6.1: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== @@ -8483,6 +8490,11 @@ lodash.reject@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.reject/-/lodash.reject-4.6.0.tgz#80d6492dc1470864bbf583533b651f42a9f52415" integrity sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU= +lodash.snakecase@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d" + integrity sha1-OdcUo1NXFHg3rv1ktdy7Fr7Nj40= + lodash.some@^4.4.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d" @@ -9101,6 +9113,16 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= +nconf@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/nconf/-/nconf-0.10.0.tgz#da1285ee95d0a922ca6cee75adcf861f48205ad2" + integrity sha512-fKiXMQrpP7CYWJQzKkPPx9hPgmq+YLDyxcG9N8RpiE9FoCkCbzD0NyW0YhE3xn3Aupe7nnDeIx4PFzYehpHT9Q== + dependencies: + async "^1.4.0" + ini "^1.3.0" + secure-keys "^1.0.0" + yargs "^3.19.0" + nearley@^2.7.10: version "2.19.7" resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.19.7.tgz#eafbe3e2d8ccfe70adaa5c026ab1f9709c116218" @@ -12150,6 +12172,11 @@ screenfull@^4.2.1: resolved "https://registry.yarnpkg.com/screenfull/-/screenfull-4.2.1.tgz#3245b7bc73d2b7c9a15bd8caaf6965db7cbc7f04" integrity sha512-PLSp6f5XdhvjCCCO8OjavRfzkSGL3Qmdm7P82bxyU8HDDDBhDV3UckRaYcRa/NDNTYt8YBpzjoLWHUAejmOjLg== +secure-keys@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/secure-keys/-/secure-keys-1.0.0.tgz#f0c82d98a3b139a8776a8808050b824431087fca" + integrity sha1-8MgtmKOxOah3aogIBQuCRDEIf8o= + select-hose@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" @@ -14030,6 +14057,11 @@ window-size@0.1.0: resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" integrity sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0= +window-size@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.4.tgz#f8e1aa1ee5a53ec5bf151ffa09742a6ad7697876" + integrity sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY= + winston@^2.1.1: version "2.4.5" resolved "https://registry.yarnpkg.com/winston/-/winston-2.4.5.tgz#f2e431d56154c4ea765545fc1003bd340c95b59a" @@ -14145,7 +14177,7 @@ xxhashjs@^0.2.1: dependencies: cuint "^0.2.2" -y18n@^3.2.1: +y18n@^3.2.0, y18n@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" integrity sha1-bRX7qITAhnnA136I53WegR4H+kE= @@ -14160,6 +14192,18 @@ yallist@^2.1.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= +yaml-lint@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/yaml-lint/-/yaml-lint-1.2.4.tgz#0dec2d1ef4e5ec999bba1e34d618fc60498d1bc5" + integrity sha512-qpKE0szyKsE9TrlVPi+bxKxVAjl30QjNAOyOxy7noQdf/WCCYUlT4xiCRxMG48eyeBzMBtBN6PgGfaB0MJePNw== + dependencies: + glob "^7.1.2" + js-yaml "^3.10.0" + leprechaun "0.0.2" + lodash.merge "^4.6.1" + lodash.snakecase "^4.1.1" + nconf "^0.10.0" + yaml@^1.7.2: version "1.10.0" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" @@ -14223,6 +14267,19 @@ yargs@^15.3.1: y18n "^4.0.0" yargs-parser "^18.1.2" +yargs@^3.19.0: + version "3.32.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.32.0.tgz#03088e9ebf9e756b69751611d2a5ef591482c995" + integrity sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU= + dependencies: + camelcase "^2.0.1" + cliui "^3.0.3" + decamelize "^1.1.1" + os-locale "^1.4.0" + string-width "^1.0.1" + window-size "^0.1.4" + y18n "^3.2.0" + yargs@^8.0.2: version "8.0.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360"