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

Shared CLJ/CLJS lib (#14980)

* Shared CLJ/CLJS lib (PoC/WIP)

* PoC 2.0

* Fixes :wrench:

* More test fixes :wrench:

* Bump shadow-cljs version

* Fix more stuff

* Need to ^:export the exports

* CI fixes :wrench:

* Add eslintignore

* Ignore cljs files for FE code coverage

* Try prefixing CLJS -> JS import with goog:

* Revert indentation change

* No goog:

* Add .prettierignore

* Use advanced build for now for JS tests unit we can figure out how to make it work
parent f4d90f68
No related branches found
No related tags found
No related merge requests found
......@@ -492,11 +492,11 @@ jobs:
# .BACKEND-CHECKSUMS is every Clojure source file as well as dependency files like deps.edn and plugin manifests
- create-checksum-file:
filename: .BACKEND-CHECKSUMS
find-args: ". -type f -name '*.clj' -or -name '*.java' -or -name '*.edn' -or -name '*.yaml' -or -name sample-dataset.db.mv.db"
find-args: ". -type f -name '*.clj' -or -name '*.cljc' -or -name '*.java' -or -name '*.edn' -or -name '*.yaml' -or -name sample-dataset.db.mv.db"
# .FRONTEND-CHECKSUMS is every JavaScript source file as well as dependency files like yarn.lock
- create-checksum-file:
filename: .FRONTEND-CHECKSUMS
find-args: ". -type f -name '*.js' -or -name '*.jsx' -or -name '*.json' -or -name yarn.lock -or -name sample-dataset.db.mv.db"
find-args: ". -type f -name '*.js' -or -name '*.jsx' -or -name '*.cljc' -or -name '*.json' -or -name yarn.lock -or -name sample-dataset.db.mv.db"
# .MODULES-CHECKSUMS is every Clojure source file in the modules/ directory as well as plugin manifests
- create-checksum-file:
filename: .MODULES-CHECKSUMS
......
frontend/src/cljs
......@@ -13,6 +13,7 @@ on:
- '**'
paths:
- 'frontend/**'
- 'shared/**'
- 'enterprise/frontend/**'
- 'docs/**'
- '**/package.lock'
......
......@@ -74,5 +74,6 @@ xcshareddata
xcuserdata
dev/src/dev/nocommit/
*~
**/cypress_sample_dataset.json
/frontend/src/cljs
.shadow-cljs
frontend/src/cljs
......@@ -32,6 +32,15 @@
(u/announce "CI run: enforce the lockfile")
(u/sh {:dir u/project-root-directory} "yarn" "--frozen-lockfile"))
(u/sh {:dir u/project-root-directory} "yarn")))
;; TODO -- I don't know why it doesn't work if we try to combine the two steps below by calling `yarn build`,
;; which does the same thing.
(u/step "Build frontend (ClojureScript)"
(u/sh {:dir u/project-root-directory
:env {"PATH" (env/env :path)
"HOME" (env/env :user-home)
"NODE_ENV" "production"
"MB_EDITION" mb-edition}}
"./node_modules/.bin/shadow-cljs" "release" "app"))
(u/step "Run 'webpack' with NODE_ENV=production to assemble and minify frontend assets"
(u/sh {:dir u/project-root-directory
:env {"PATH" (env/env :path)
......
import _ from "underscore";
import { isa, TYPE } from "cljs/metabase.types";
import MetabaseSettings from "metabase/lib/settings";
const PARENTS = MetabaseSettings.get("types");
/// Basically exactly the same as Clojure's isa?
/// Recurses through the type hierarchy until it can give you an answer.
/// isa(TYPE.BigInteger, TYPE.Number) -> true
/// isa(TYPE.Text, TYPE.Boolean) -> false
export function isa(child, ancestor) {
if (!child || !ancestor) {
return false;
}
if (child === ancestor) {
return true;
}
const parents = PARENTS[child];
if (!parents) {
if (child !== "type/*") {
console.error("Invalid type:", child);
} // the base type is the only type with no parents, so anything else that gets here is invalid
return false;
}
for (const parent of parents) {
if (isa(parent, ancestor)) {
return true;
}
}
return false;
}
// build a pretty sweet dictionary of top-level types, so people can do TYPE.Latitude instead of "type/Latitude" and get error messages / etc.
// this should also make it easier to keep track of things when we tweak the type hierarchy
export const TYPE = {};
for (const type of _.keys(PARENTS)) {
const key = type.substring(5); // strip off "type/"
TYPE[key] = type;
}
export { isa, TYPE };
// convenience functions since these operations are super-common
// this will also make it easier to tweak how these checks work in the future,
......
......@@ -30,6 +30,7 @@
"collectCoverageFrom": ["frontend/src/**/*.js", "frontend/src/**/*.jsx"],
"coveragePathIgnorePatterns": [
"/node_modules/",
"/frontend/src/metabase/visualizations/lib/errors.js"
"/frontend/src/metabase/visualizations/lib/errors.js",
"/frontend/src/cljs/"
]
}
......@@ -141,6 +141,7 @@
"promise-loader": "^1.0.0",
"raf": "^3.4.0",
"react-test-renderer": "~16.14.0",
"shadow-cljs": "2.11.20",
"style-loader": "^0.19.0",
"uglifyjs-webpack-plugin": "^1.0.0",
"unused-files-webpack-plugin": "^3.0.0",
......@@ -153,22 +154,30 @@
"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'",
"dev-ee": "concurrently --kill-others -p name -n 'backend,frontend,docs' -c 'blue,green,yellow' 'lein with-profiles +ee run' 'MB_EDITION=ee yarn build-hot' 'yarn docs'",
"concurrently": "yarn && concurrently --kill-others -p name",
"dev": "yarn concurrently -n 'backend,frontend,cljs,docs' -c 'blue,green,yellow,magenta' 'lein run' 'yarn build-hot:js' 'yarn build-hot:cljs' 'yarn docs'",
"dev-ee": "yarn concurrently -n 'backend,frontend,cljs,docs' -c 'blue,green,yellow,magenta' 'lein with-profiles +ee run' 'MB_EDITION=ee yarn build-hot:js' 'MB_EDITION=ee yarn build-hot:cljs' 'yarn docs'",
"lint": "yarn lint-eslint && yarn lint-prettier && yarn lint-docs-links && yarn lint-yaml",
"lint-eslint": "yarn && eslint --ext .js --ext .jsx --rulesdir frontend/lint/eslint-rules --max-warnings 0 enterprise/frontend/src frontend/src enterprise/frontend/test frontend/test",
"lint-eslint": "yarn build-quick:cljs && eslint --ext .js --ext .jsx --rulesdir frontend/lint/eslint-rules --max-warnings 0 enterprise/frontend/src frontend/src enterprise/frontend/test frontend/test",
"lint-prettier": "yarn && prettier -l '{enterprise/,}frontend/**/*.{js,jsx,css}' || (echo '\nThese files are not formatted correctly. Did you forget to \"yarn prettier\"?' && false)",
"lint-docs-links": "yarn && ./bin/verify-doc-links",
"lint-yaml": "yamllint **/*.{yaml,yml} --ignore=node_modules/**/*.{yaml,yml}",
"test": "yarn test-unit && yarn test-timezones && yarn test-cypress",
"test-unit": "yarn && jest --maxWorkers=2 --config jest.unit.conf.json",
"test-unit": "yarn build-quick:cljs && jest --maxWorkers=2 --config jest.unit.conf.json",
"test-unit-watch": "yarn test-unit --watch",
"test-unit-update-snapshot": "yarn test-unit --updateSnapshot",
"test-timezones-unit": "yarn && jest --maxWorkers=2 --config jest.tz.unit.conf.json",
"test-timezones-unit": "yarn build-quick:cljs && jest --maxWorkers=2 --config jest.tz.unit.conf.json",
"test-timezones": "yarn && ./frontend/test/__runner__/run_timezone_tests",
"build": "yarn && webpack --bail",
"build-watch": "yarn && webpack --watch",
"build-hot": "yarn && NODE_ENV=hot webpack-dev-server --progress",
"build:js": "yarn && webpack --bail",
"build-watch:js": "yarn && webpack --watch",
"build-hot:js": "yarn && NODE_ENV=hot webpack-dev-server --progress",
"build:cljs": "yarn && shadow-cljs release app",
"build-quick:cljs": "yarn build:cljs",
"build-watch:cljs": "yarn && shadow-cljs watch app",
"build-hot:cljs": "yarn && shadow-cljs watch app",
"build": "yarn build:cljs && yarn build:js",
"build-watch": "yarn concurrently -n 'cljs,js' 'yarn build-watch:cljs' 'yarn build-watch:js'",
"build-hot": "yarn concurrently -n 'cljs,js' 'yarn build-hot:cljs' 'yarn build-hot:js'",
"build-stats": "yarn && webpack --json > stats.json",
"build-shared": "yarn && webpack --config webpack.shared.config.js",
"start": "yarn build && lein ring server",
......
......@@ -175,7 +175,7 @@
["-target" "1.8", "-source" "1.8"]
:source-paths
["src" "backend/mbql/src"]
["src" "backend/mbql/src" "shared/src"]
:java-source-paths
["java"]
......@@ -193,7 +193,7 @@
:dev
{:source-paths ["dev/src" "local/src"]
:test-paths ["test" "backend/mbql/test"]
:test-paths ["test" "backend/mbql/test" "shared/test"]
:dependencies
[[clj-http-fake "1.0.3" :exclusions [slingshot]] ; Library to mock clj-http responses
......@@ -398,8 +398,8 @@
[:test-common
{:dependencies [[camsaul/cloverage "1.2.1.1" :exclusions [riddley]]]
:plugins [[camsaul/lein-cloverage "1.2.1.1"]]
:source-paths ^:replace ["src" "backend/mbql/src" "enterprise/backend/src"]
:test-paths ^:replace ["test" "backend/mbql/test" "enterprise/backend/test"]
:source-paths ^:replace ["src" "backend/mbql/src" "enterprise/backend/src" "shared/src"]
:test-paths ^:replace ["test" "backend/mbql/test" "enterprise/backend/test" "shared/test"]
:cloverage {:fail-threshold 69
:exclude-call
[;; don't instrument logging forms, since they won't get executed as part of tests anyway
......
;; shadow-cljs configuration
{:source-paths
["shared/src"]
:dependencies
[]
:builds
{:app {:target :npm-module
:output-dir "frontend/src/cljs/"
;; empty entries = export all namespaces (?)
:entries []}}}
(ns metabase.shared.util)
(defn qualified-name
"Return `k` as a string, qualified by its namespace, if any (unlike `name`). Handles `nil` values gracefully as well
(also unlike `name`).
(u/qualified-name :type/FK) -> \"type/FK\""
[k]
(when (some? k)
(if-let [namespac (when #?(:clj (instance? clojure.lang.Named k)
:cljs (keyword? k))
(namespace k))]
(str namespac "/" (name k))
(name k))))
......@@ -3,7 +3,8 @@
which in turn derive from their own parents. This makes it possible to add new types without needing to add
corresponding mappings in the frontend or other places. For example, a Database may want a type called something
like `:type/CaseInsensitiveText`; we can add this type as a derivative of `:type/Text` and everywhere else can
continue to treat it as such until further notice.")
continue to treat it as such until further notice."
(:require [metabase.shared.util :as shared.u]))
;; NOTE: be sure to update frontend/test/metabase-bootstrap.js when updating this
......@@ -210,7 +211,7 @@
;;; ---------------------------------------------------- Util Fns ----------------------------------------------------
(defn types->parents
(defn- types->parents
"Return a map of various types to their parent types.
This is intended for export to the frontend as part of `MetabaseBootstrap` so it can build its own implementation of
......@@ -226,3 +227,17 @@
{:arglists '([field])}
[{base-type :base_type, semantic-type :semantic_type}]
(some #(isa? % :type/Temporal) [base-type semantic-type]))
#?(:cljs
(defn ^:export isa
"Is `x` the same as, or a descendant type of, `y`?"
[x y]
(isa? (keyword x) (keyword y))))
#?(:cljs
(def ^:export TYPE
"A map of Type name (as string, without `:type/` namespace) -> qualified type name as string
{\"Temporal\" \"type/Temporal\", ...}"
(clj->js (into {} (for [tyype (descendants :type/*)]
[(name tyype) (shared.u/qualified-name tyype)])))))
......@@ -7,7 +7,6 @@
[metabase.models.setting :as setting :refer [defsetting]]
[metabase.plugins.classloader :as classloader]
[metabase.public-settings.metastore :as metastore]
[metabase.types :as types]
[metabase.util :as u]
[metabase.util.i18n :as i18n :refer [available-locales-with-names deferred-tru trs tru]]
[metabase.util.password :as password]
......@@ -323,18 +322,6 @@
:setter :none
:getter driver.u/available-drivers-info)
(defsetting types
"Field types"
:visibility :public
:setter :none
:getter (fn [] (types/types->parents :type/*)))
(defsetting entities
"Entity types"
:visibility :public
:setter :none
:getter (fn [] (types/types->parents :entity/*)))
(defsetting has-sample-dataset?
"Whether this instance has a Sample Dataset database"
:visibility :authenticated
......
......@@ -13,7 +13,9 @@
[flatland.ordered.map :refer [ordered-map]]
[medley.core :as m]
[metabase.config :as config]
[metabase.shared.util :as shared.u]
[metabase.util.i18n :refer [trs tru]]
[potemkin :as p]
[ring.util.codec :as codec]
[weavejester.dependency :as dep])
(:import [java.net InetAddress InetSocketAddress Socket]
......@@ -23,6 +25,12 @@
javax.xml.bind.DatatypeConverter
[org.apache.commons.validator.routines RegexValidator UrlValidator]))
(comment shared.u/keep-me)
(p/import-vars
[shared.u
qualified-name])
(defn format-bytes
"Nicely format `num-bytes` as kilobytes/megabytes/etc.
......@@ -463,18 +471,6 @@
[f coll]
(into {} (map (juxt f identity)) coll))
(defn qualified-name
"Return `k` as a string, qualified by its namespace, if any (unlike `name`). Handles `nil` values gracefully as well
(also unlike `name`).
(u/qualified-name :type/FK) -> \"type/FK\""
[k]
(when (some? k)
(if-let [namespac (when (instance? clojure.lang.Named k)
(namespace k))]
(str namespac "/" (name k))
(name k))))
(defn id
"If passed an integer ID, returns it. If passed a map containing an `:id` key, returns the value if it is an integer.
Otherwise returns `nil`.
......
......@@ -24,6 +24,7 @@ const LIB_SRC_PATH = __dirname + "/frontend/src/metabase-lib";
const ENTERPRISE_SRC_PATH =
__dirname + "/enterprise/frontend/src/metabase-enterprise";
const TYPES_SRC_PATH = __dirname + "/frontend/src/metabase-types";
const CLJS_SRC_PATH = __dirname + "/frontend/src/cljs";
const TEST_SUPPORT_PATH = __dirname + "/frontend/test/__support__";
const BUILD_PATH = __dirname + "/resources/frontend_client";
......@@ -67,12 +68,12 @@ const config = (module.exports = {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
exclude: /node_modules|cljs/,
use: [{ loader: "babel-loader", options: BABEL_CONFIG }],
},
{
test: /\.(js|jsx)$/,
exclude: /node_modules|\.spec\.js/,
exclude: /node_modules|cljs|\.spec\.js/,
use: [
{
loader: "eslint-loader",
......@@ -108,6 +109,7 @@ const config = (module.exports = {
"metabase-lib": LIB_SRC_PATH,
"metabase-enterprise": ENTERPRISE_SRC_PATH,
"metabase-types": TYPES_SRC_PATH,
"cljs": CLJS_SRC_PATH,
__support__: TEST_SUPPORT_PATH,
style: SRC_PATH + "/css/core/index",
ace: __dirname + "/node_modules/ace-builds/src-min-noconflict",
......@@ -210,7 +212,7 @@ if (NODE_ENV === "hot") {
config.module.rules.unshift({
test: /\.jsx$/,
// NOTE: our verison of react-hot-loader doesn't play nice with react-dnd's DragLayer, so we exclude files named `*DragLayer.jsx`
exclude: /node_modules|DragLayer\.jsx$/,
exclude: /node_modules|cljs|DragLayer\.jsx$/,
use: [
// NOTE Atte Keinänen 10/19/17: We are currently sticking to an old version of react-hot-loader
// because newer versions would require us to upgrade to react-router v4 and possibly deal with
......
......@@ -1697,6 +1697,11 @@ async-each@^1.0.0, async-each@^1.0.1:
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf"
integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==
 
async-limiter@~1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
async@^1.4.0, async@^1.5.0:
version "1.5.2"
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
......@@ -9105,7 +9110,7 @@ node-int64@^0.4.0:
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=
 
"node-libs-browser@^1.0.0 || ^2.0.0", node-libs-browser@^2.0.0:
"node-libs-browser@^1.0.0 || ^2.0.0", node-libs-browser@^2.0.0, node-libs-browser@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425"
integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==
......@@ -11256,6 +11261,11 @@ readdirp@~3.5.0:
dependencies:
picomatch "^2.2.1"
 
readline-sync@^1.4.7:
version "1.4.10"
resolved "https://registry.yarnpkg.com/readline-sync/-/readline-sync-1.4.10.tgz#41df7fbb4b6312d673011594145705bf56d8873b"
integrity sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==
recast@^0.11.17:
version "0.11.23"
resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.23.tgz#451fd3004ab1e4df9b4e4b66376b2a21912462d3"
......@@ -12153,6 +12163,23 @@ sha.js@^2.4.0, sha.js@^2.4.8:
inherits "^2.0.1"
safe-buffer "^5.0.1"
 
shadow-cljs-jar@1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/shadow-cljs-jar/-/shadow-cljs-jar-1.3.2.tgz#97273afe1747b6a2311917c1c88d9e243c81957b"
integrity sha512-XmeffAZHv8z7451kzeq9oKh8fh278Ak+UIOGGrapyqrFBB773xN8vMQ3O7J7TYLnb9BUwcqadKkmgaq7q6fhZg==
shadow-cljs@2.11.20:
version "2.11.20"
resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.11.20.tgz#fa814b74a7fa7909ff056994a80f8f7435881046"
integrity sha512-TmZp1Hjp49oziqaTdBYwO0qzvoVMYa+O5c7rwdfO334bdYhTPMmJJeG/EbeJpRvLAKErjbxGmY4P28rqfPmZ3w==
dependencies:
node-libs-browser "^2.2.1"
readline-sync "^1.4.7"
shadow-cljs-jar "1.3.2"
source-map-support "^0.4.15"
which "^1.3.1"
ws "^3.0.0"
shallowequal@^1.0.2, shallowequal@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8"
......@@ -13287,6 +13314,11 @@ ultron@1.0.x:
resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa"
integrity sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=
 
ultron@~1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c"
integrity sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==
unc-path-regex@^0.1.0, unc-path-regex@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa"
......@@ -13936,7 +13968,7 @@ which-module@^2.0.0:
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
 
which@^1.1.1, which@^1.2.10, which@^1.2.9, which@^1.3.0:
which@^1.1.1, which@^1.2.10, which@^1.2.9, which@^1.3.0, which@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
......@@ -14049,6 +14081,15 @@ ws@^1.0.1:
options ">=0.0.5"
ultron "1.0.x"
 
ws@^3.0.0:
version "3.3.3"
resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2"
integrity sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==
dependencies:
async-limiter "~1.0.0"
safe-buffer "~5.1.0"
ultron "~1.1.0"
x-is-string@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/x-is-string/-/x-is-string-0.1.0.tgz#474b50865af3a49a9c4657f05acd145458f77d82"
......
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