开发环境

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

请参阅后端开发

前端开发

我们的前端构建过程使用以下技术,以支持使用模块、ES6 语法和 CSS 变量。

  • webpack
  • babel
  • cssnext

前端任务使用 yarn 执行。所有可用任务都可以在 package.jsonscripts 部分找到。

要在不监视更改的情况下构建前端客户端,您可以使用

$ yarn build

如果您直接在前端工作,您很可能希望在保存时重新加载更改,对于 React 组件,则在保持状态的同时进行。要使用热重载启动构建,请使用

$ yarn build-hot

请注意,目前如果您更改 CSS 变量,这些更改只有在重新启动构建时才会生效。

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

$ yarn build-watch

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

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

$ USE_ESLINT=true yarn build-hot

前端测试

运行所有单元测试和 Cypress 端到端测试,使用

yarn test

Cypress 测试和一些单元测试位于 frontend/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.session.api-test

# run one specific test (pass in a qualified symbol)
clojure -X:dev:test :only metabase.session.api-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})))

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

  • host(默认为“localhost”)
  • port(默认为 5432)
  • user
  • password

函数名称指示它们是否抛出(尽管在这种情况下,会抛出的函数也提供了默认值)。

(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)

运行代码检查工具

clj-kondo 必须单独安装

# Run clj-kondo
mage kondo

# Lint the migrations file (if you've written a database migration):
mage lint-migrations

# 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

持续集成

所有前端和后端代码检查工具和测试都可以通过以下方式执行

$ yarn ci

也可以单独执行前端和后端检查

$ yarn ci-frontend
$ yarn ci-backend

阅读其他Metabase 版本的文档。

© . All rights reserved.