开发环境

Metabase 应用程序有两个基本组件

  1. 后端使用 Clojure 编写,其中包含 REST API 以及用于与数据库对话和处理查询的所有相关代码。
  2. 前端编写为 Javascript 单页应用程序,提供 Web UI。

这两个组件构建并组装成一个 JAR 文件。在运行 JAR 的目录中,您可以创建一个 JAR 文件(如果 Metabase 尚未创建),并在其中添加驱动程序(驱动程序也是 JAR)。

快速入门

要启动开发环境,请运行

yarn dev

这将同时运行前端后端。或者,您可以分别在下面的两个终端会话中运行它们。

前端

Metabase 依赖于第三方库来运行,因此您需要保持这些库为最新。Clojure CLI 将在需要时自动获取依赖项。但是,对于 JavaScript 依赖项,您需要手动启动安装过程。

# javascript dependencies
$ yarn

使用以下命令启动前端构建过程

yarn build-hot

请参阅前端开发

后端

使用以下命令运行后端开发服务器

clojure -M:run

请参阅后端开发

前端开发

我们使用这些技术进行 FE 构建过程,以便我们能够使用模块、es6 语法和 css 变量。

  • webpack
  • babel
  • cssnext

前端任务使用 yarn 执行。所有可用的任务都可以在 package.json 的 *scripts* 下找到。

要构建前端客户端而不监视更改,您可以使用

$ yarn build

如果您直接在前端工作,您很可能希望在保存时重新加载更改,并且在 React 组件的情况下,在保持状态的同时执行此操作。要启动热重载构建,请使用

$ yarn build-hot

请注意,此时如果您更改 CSS 变量,则只有在重新启动构建时才会拾取这些更改。

如果您喜欢,也可以选择在保存时重新加载更改,而无需热重载。

$ yarn build-watch

某些系统可能无法检测到前端文件的更改。您可以通过取消注释 webpack.config.js 中的 watchOptions 子句来启用文件系统轮询。如果您这样做,则可能值得使用 git update-index --assume-unchanged webpack.config.js 使 git 忽略对 webpack 配置的更改

默认情况下,我们在开发模式下排除 ESLint 加载器,以实现七倍更快的初始构建。您可以通过导出环境变量来启用它

$ USE_ESLINT=true yarn build-hot

默认情况下,这些构建过程依赖于内存缓存。启用 ESLint 加载器的构建过程使用大量内存,并且可能需要相当长的时间才能启动(1-2 分钟或更长时间)。鼓励 FE 开发人员(或任何其他经常重新启动 FE 构建的人员)使用 webpack 的文件系统缓存选项,以获得更好的启动性能

$ FS_CACHE=true yarn build-hot

当使用 FS_CACHE=true 时,您可能需要删除 node_modules/.cache 目录,以修复构建可能被不正确缓存的情况,并且当在代码库的开源版本和企业版本之间交替时,您必须运行 rm -rf node_modules/.cache 才能使构建正常工作。

前端测试

使用以下命令运行所有单元测试和 Cypress 端到端测试

yarn test

Cypress 测试和一些单元测试位于 frontend/test 目录中。新的单元测试文件添加到它们测试的文件旁边。

如果您正在使用 FS_CACHE=true,您也可以将 FS_CACHE=trueyarn test 一起使用。

前端调试

默认情况下,我们使用针对速度优化的简单源映射选项。

如果您在断点处遇到问题,尤其是在 jsx 内部,请在运行服务器之前将环境变量 BETTER_SOURCE_MAPS 设置为 true。

示例

BETTER_SOURCE_MAPS=true yarn dev

Cypress 端到端测试

端到端测试模拟用户交互的真实序列。阅读更多关于我们如何使用 Cypress 进行端到端测试

Cypress 端到端测试使用强制文件命名约定 <test-suite-name>.cy.spec.js 将它们与单元测试分开。

Jest 单元测试

单元测试侧重于业务逻辑的隔离部分。

单元测试使用强制文件命名约定 <test-suite-name>.unit.spec.js 将它们与端到端测试分开。

yarn test-unit # Run all tests at once
yarn test-unit-watch # Watch for file changes

后端开发

Clojure REPL 是后端的​​主要开发工具。下面介绍如何设置 REPL 以简化开发。

当然,您的 Jetty 开发服务器也可以通过以下方式获得

clojure -M:run

您也可以通过另一种方式(例如,通过您的编辑器)启动 REPL,然后调用

(do (dev) (start!))

启动服务器(位于 localhost:3000)。这也将设置或迁移您的应用程序数据库。要实际使用 Metabase,请不要忘记也启动前端(例如使用 yarn build-hot)。

应用程序数据库

默认情况下,Metabase 使用 H2 作为其应用程序数据库,但我们建议使用 Postgres。这通过几个属性配置,这些属性可以设置为环境变量或在 deps.edn 中设置。一种方法是

;; ~/.clojure/deps.edn

{:aliases
 {:user
  {:jvm-opts
   ["-Dmb.db.host=localhost"
    "-Dmb.db.type=postgres"
    "-Dmb.db.user=<username>"
    "-Dmb.db.dbname=<dbname>"
    "-Dmb.db.pass="]}}}

您也可以将完整的连接字符串作为 mb.db.connection.uri 传入

"-Dmb.db.connection.uri=postgres://<user>:<password>@localhost:5432/<dbname>"

除了使用环境变量外,还可以选择直接与配置库 environ 接口。

这种方法需要您的项目目录中创建一个 .lein-env 文件

{:mb-db-type   "postgres"
 :mb-db-host   "localhost"
 :mb-db-user   "<username>"
 :mb-db-dbname "<dbname>"
 :mb-db-pass   ""}

尽管名称如此,此文件仍可与 deps.edn 项目正常工作。与全局 deps.edn 方法相比,此方法的优势在于它仅限于此项目。

仅将其用于开发,生产用途不支持。 .gitignore 中已经有条目,以防止您意外提交此文件。

构建驱动程序

Metabase 用于连接到外部数据仓库数据库的大多数驱动程序都是 modules/ 子目录下的单独项目。通过 clojure 运行 Metabase 时,您需要构建这些驱动程序才能访问它们。您可以按如下方式构建驱动程序

# Build the 'mongo' driver
./bin/build-driver.sh mongo

(或)

# Build all drivers
./bin/build-drivers.sh

包含驱动程序源路径以进行开发或其他任务

对于开发,当运行各种 Clojure 任务时,您可以添加 driversdrivers-dev 别名,以将驱动程序的依赖项和源路径合并到 Metabase 项目中

# Install dependencies, including for drivers
clojure -P -X:dev:ci:drivers:drivers-dev

运行单元测试

使用以下命令运行单元测试

# OSS tests only
clojure -X:dev:test

# OSS + EE tests
clojure -X:dev:ee:ee-dev:test

或使用以下命令运行特定测试(或测试命名空间)

# run tests in only one namespace (pass in a symbol)
clojure -X:dev:test :only metabase.api.session-test

# run one specific test (pass in a qualified symbol)
clojure -X:dev:test :only metabase.api.session-test/my-test

# run tests in one specific folder (test/metabase/util in this example)
# pass arg in double-quotes so Clojure CLI interprets it as a string;
# our test runner treats strings as directories
clojure -X:dev:test :only '"test/metabase/util"'

与任何 clojure.test 项目一样,您也可以从 REPL 运行单元测试。以下是一些运行测试的有用方法示例

;; run a single test with clojure.test
some-ns=> (clojure.test/run-test metabase.util-test/add-period-test)

Testing metabase.util-test

Ran 1 tests containing 4 assertions.
0 failures, 0 errors.
{:test 1, :pass 4, :fail 0, :error 0, :type :summary}

;; run all tests in the namespace
some-ns=> (clojure.test/run-tests 'metabase.util-test)

Testing metabase.util-test
{:result true, :num-tests 100, :seed 1696600311261, :time-elapsed-ms 45, :test-var "pick-first-test"}

Ran 33 tests containing 195 assertions.
0 failures, 0 errors.
{:test 33, :pass 195, :fail 0, :error 0, :type :summary}

;; run tests for a set of namespaces related to a feature you are working on (eg pulses)
some-ns=> (let [namespaces '[metabase.pulse.markdown-test metabase.pulse.parameters-test]]
            (apply require namespaces) ;; make sure the test namespaces are loaded
            (apply clojure.test/run-tests namespaces))

Testing metabase.pulse.markdown-test

Testing metabase.pulse.parameters-test

Ran 5 tests containing 147 assertions.
0 failures, 0 errors.
{:test 5, :pass 147, :fail 0, :error 0, :type :summary}

;; but we also have a lovely test runner with lots of cool options
some-ns=> (metabase.test-runner/find-and-run-tests-repl {:namespace-pattern ".*pulse.*"})
Running tests with options {:mode :repl, :namespace-pattern ".*pulse.*", :exclude-directories ["classes" "dev" "enterprise/backend/src" "local" "resources" "resources-ee" "src" "target" "test_config" "test_resources"], :test-warn-time 3000}
Excluding directory "dev/src"
Excluding directory "local/src"
Looking for test namespaces in directory test
Finding tests took 1.6 s.
Excluding directory "test_resources"
Excluding directory "enterprise/backend/src"
Looking for test namespaces in directory enterprise/backend/test
Excluding directory "src"
Excluding directory "resources"
Running 159 tests
...

;; you can even specify a directory if you're working on a subfeature like that
some-ns=> (metabase.test-runner/find-and-run-tests-repl {:only "test/metabase/pulse/"})
Running tests with options {:mode :repl, :namespace-pattern #"^metabase.*", :exclude-directories ["classes" "dev" "enterprise/backend/src" "local" "resources" "resources-ee" "src" "target" "test_config" "test_resources"], :test-warn-time 3000, :only "test/metabase/pulse/"}
Running tests in "test/metabase/pulse/"
Looking for test namespaces in directory test/metabase/pulse
Finding tests took 37.0 ms.
Running 65 tests
...

测试驱动程序

默认情况下,测试仅针对 h2 驱动程序运行。您可以使用环境变量 DRIVERS 指定要针对哪些驱动程序运行测试

DRIVERS=h2,postgres,mysql,mongo clojure -X:dev:drivers:drivers-dev:test

某些驱动程序在测试时需要额外的环境变量,因为它们无法在本地运行(例如 Redshift 和 Bigquery)。测试将在启动时失败,并告知您如果需要提供哪些参数。

如果从 REPL 运行测试,您可以调用类似

(mt/set-test-drivers! #{:postgres :mysql :h2})

大多数驱动程序需要能够加载一些数据(一些使用静态数据集),并且所有驱动程序都需要能够连接到该数据库的实例。您可以在每个驱动程序的测试数据命名空间中找到所需的内容,该命名空间遵循模式 metabase.test.data.<driver>

应该有一个多方法 tx/dbdef->connection-details 的实现,它必须生成一种连接到数据库的方法。您可以看到需要什么。

这是 metabase.test.data.postgres 中 postgres 的一个

(defmethod tx/dbdef->connection-details :postgres
  [_ context {:keys [database-name]}]
  (merge
   {:host     (tx/db-test-env-var-or-throw :postgresql :host "localhost")
    :port     (tx/db-test-env-var-or-throw :postgresql :port 5432)
    :timezone :America/Los_Angeles}
   (when-let [user (tx/db-test-env-var :postgresql :user)]
     {:user user})
   (when-let [password (tx/db-test-env-var :postgresql :password)]
     {:password password})
   (when (= context :db)
     {:db database-name})))

您可以看到这在环境中查找

  • 主机(默认为“localhost”)
  • 端口(默认为 5432)
  • 用户
  • 密码

函数名称指示它们是否抛出异常(尽管在此实例中,也会为那些会抛出异常的函数提供默认值)。

(tx/db-test-env-var :postgresql :password) 将在 env/env 映射中查找 :mb-postgresql-test-password,它将由环境变量 MB_POSTGRESQL_TEST_PASSWORD 设置。

some-ns=> (take 10 (keys environ.core/env))
(:mb-redshift-test-password
 :java-class-path
 :path
 :mb-athena-test-s3-staging-dir
 :iterm-profile
 :mb-snowflake-test-warehouse
 :mb-bigquery-cloud-sdk-test-service-account-json
 :tmpdir
 :mb-oracle-test-service-name
 :sun-management-compiler)

运行 linters

clj-kondo 必须单独安装

# Run Eastwood
clojure -X:dev:ee:ee-dev:drivers:drivers-dev:eastwood

# Run the namespace checker
clojure -X:dev:ee:ee-dev:drivers:drivers-dev:test:namespace-checker

# Run clj-kondo
./bin/kondo.sh

# Lint the migrations file (if you've written a database migration):
./bin/lint-migrations-file.sh

持续集成

可以使用以下命令执行所有前端和后端 linters 和测试

$ yarn ci

也可以分别执行前端和后端检查

$ yarn ci-frontend
$ yarn ci-backend

阅读关于 其他版本的 Metabase 的文档。