Compare commits

..

115 Commits

Author SHA1 Message Date
Uladzimir Havenchyk
39a68def7e cleanup sandboxing 2025-10-30 22:30:51 +03:00
Uladzimir Havenchyk
3da641ba00 fix typecheck 2025-10-29 16:15:09 +03:00
Uladzimir Havenchyk
91247c3be2 Merge remote-tracking branch 'origin/nmezzopera/test-pyodie' into test-python-be 2025-10-29 16:03:16 +03:00
Uladzimir Havenchyk
ed4f399a01 add a comment 2025-10-28 18:49:55 +03:00
Uladzimir Havenchyk
a291c967c7 improve sandboxing 2025-10-28 18:49:12 +03:00
Uladzimir Havenchyk
4f1dec3ce8 add a comment 2025-10-28 16:42:41 +03:00
Uladzimir Havenchyk
f6347c776d log python error 2025-10-28 16:42:25 +03:00
Uladzimir Havenchyk
ae3c2c7a2a move mocks to proper place 2025-10-28 16:40:03 +03:00
Uladzimir Havenchyk
3a985e46a2 lint 2025-10-28 16:13:18 +03:00
Uladzimir Havenchyk
ba2ae00dac fix typecheck 2025-10-28 14:28:01 +03:00
Uladzimir Havenchyk
0df6adcc51 remove duplicated button 2025-10-28 14:20:52 +03:00
Uladzimir Havenchyk
87c2392827 Merge remote-tracking branch 'origin/master' into nmezzopera/test-pyodie 2025-10-28 13:32:00 +03:00
Uladzimir Havenchyk
eda355803b Merge remote-tracking branch 'origin/master' into nmezzopera/test-pyodie 2025-10-26 16:39:15 +03:00
Uladzimir Havenchyk
e6df0dc8c5 Merge remote-tracking branch 'origin/master' into nmezzopera/test-pyodie 2025-10-26 16:02:00 +03:00
Uladzimir Havenchyk
7fac1f9d24 Merge remote-tracking branch 'origin/master' into nmezzopera/test-pyodie 2025-10-23 00:06:52 +03:00
Romeo Van Snick
4bc3b6484b Ban certain imports in Python script 2025-10-13 17:02:06 +11:00
Romeo Van Snick
ee29d769a0 Add more tests for backend test runner 2025-10-10 14:25:02 +11:00
Romeo Van Snick
013974b705 Improve python runner mode switch 2025-10-10 13:53:51 +11:00
Romeo Van Snick
202290121b Remove unused helper 2025-10-09 16:08:02 +11:00
Romeo Van Snick
5766d22bce Fix flaky test 2025-10-09 15:59:46 +11:00
Romeo Van Snick
7b500427ec Increase timeout for python runner 2025-10-09 15:47:00 +11:00
Romeo Van Snick
d0a36c3d7c Fix misplaced docstring 2025-10-09 14:56:32 +11:00
Romeo Van Snick
ef7ee34349 Add UI to switch between browser runner and api runnner 2025-10-09 14:55:27 +11:00
Romeo Van Snick
24d6fc3d7d WIP 2025-10-09 14:37:27 +11:00
Romeo Van Snick
e00705930f Update python transform endpoint 2025-10-09 14:34:44 +11:00
Romeo Van Snick
a85b69ce0d Adjust pyodide worker types to align with test endpoint 2025-10-09 14:33:20 +11:00
Romeo Van Snick
dff77917f4 Remove blob: for worker-src 2025-10-09 13:17:38 +11:00
Romeo Van Snick
c25d90382f Fix metababot broken code 2025-10-09 13:16:46 +11:00
Romeo Van Snick
42d8c14a8e Improve comments 2025-10-09 13:14:45 +11:00
Romeo Van Snick
6b37f5f3af Add more documentation on the worker pool 2025-10-09 13:13:54 +11:00
Romeo Van Snick
809503986a Only unsubscribe when message is handled 2025-10-09 13:10:13 +11:00
Romeo Van Snick
b551d305fc Remove any 2025-10-09 13:08:59 +11:00
Romeo Van Snick
b24f87f5d1 Remove todo 2025-10-09 13:08:38 +11:00
Romeo Van Snick
9c95e9b8a7 Add comment to proxy route 2025-10-09 13:06:59 +11:00
Romeo Van Snick
8e8480fc95 Only add querystring when it exists
Co-authored-by: metabase-bot[bot] <109303359+metabase-bot[bot]@users.noreply.github.com>
2025-10-09 13:04:29 +11:00
Romeo Van Snick
abf5f8caa8 Top-level comments should have three semicolons
Co-authored-by: metabase-bot[bot] <109303359+metabase-bot[bot]@users.noreply.github.com>
2025-10-09 13:00:52 +11:00
Romeo Van Snick
d13f7355c5 Decompress archive 2025-10-09 12:49:19 +11:00
Romeo Van Snick
e560d95105 Fix typo 2025-10-09 12:48:36 +11:00
Romeo Van Snick
e7828cc2f8 Remove redundant declare 2025-10-09 12:48:09 +11:00
Romeo Van Snick
0af03be7cc Merge branch 'master' into nmezzopera/test-pyodie
# Conflicts:
#	enterprise/frontend/src/metabase-enterprise/transforms-python/components/PythonTransformEditor/PythonEditorResults/PythonEditorResults.module.css
2025-10-09 12:31:14 +11:00
Romeo Van Snick
26915a1feb Remove log 2025-10-09 12:30:03 +11:00
Romeo Van Snick
8fcd5d3839 Do not store pyodide packages in git 2025-10-09 12:28:29 +11:00
Romeo Van Snick
39cefbab7f Revert "Remove pyodide from github repo"
This reverts commit 95bb56b5e8.
2025-10-08 23:11:00 +11:00
Romeo Van Snick
e002ff8980 Revert "Ignore code issues in vendored library"
This reverts commit 3606567fc8.
2025-10-08 23:10:04 +11:00
Romeo Van Snick
95bb56b5e8 Remove pyodide from github repo 2025-10-08 22:50:35 +11:00
Romeo Van Snick
1e03d56dac Install pyodide on install 2025-10-08 22:49:38 +11:00
Romeo Van Snick
a5d21e9623 Add script to download pyodide's distro 2025-10-08 22:49:27 +11:00
Romeo Van Snick
3606567fc8 Ignore code issues in vendored library 2025-10-08 21:12:10 +11:00
Romeo Van Snick
a3b380ca00 Remove template string quotes 2025-10-08 20:57:55 +11:00
Romeo Van Snick
59ec9091d8 Increase ready timout 2025-10-08 20:57:36 +11:00
Romeo Van Snick
158ce159f7 Add mock for PyodideWorkerManager 2025-10-08 15:52:25 +11:00
Romeo Van Snick
a6a737b33b Handle app/dist when not in dev mode 2025-10-08 13:09:57 +11:00
Romeo Van Snick
af75705b3c Remove unused argument 2025-10-08 13:05:41 +11:00
Romeo Van Snick
f904f950ba Rename variable to be clearer 2025-10-08 11:12:49 +11:00
Romeo Van Snick
64a8ec3593 Add search params to request 2025-10-08 10:47:43 +11:00
Romeo Van Snick
39e2e551dc Simplify worker loops 2025-10-07 17:02:26 +11:00
Romeo Van Snick
7606803604 Add termination better 2025-10-07 16:30:53 +11:00
Romeo Van Snick
7fd1c79dad Ignore results for aborted runs 2025-10-07 16:04:04 +11:00
Romeo Van Snick
1a7017612c Use types from pyodide 2025-10-07 15:41:05 +11:00
Romeo Van Snick
4420d0c963 Add pyodide 2025-10-07 15:28:16 +11:00
Romeo Van Snick
3a00a330bc Handle errored out workers 2025-10-07 15:21:56 +11:00
Romeo Van Snick
22211b18e8 Fix types 2025-10-07 15:10:14 +11:00
Romeo Van Snick
bc5bcff995 Improve waitFor error handling 2025-10-07 15:05:51 +11:00
Romeo Van Snick
27456e9669 Split up code further 2025-10-07 14:42:30 +11:00
Romeo Van Snick
13854b5c49 Work of shared types for Worker and Manager 2025-10-07 14:35:36 +11:00
Romeo Van Snick
143ed4974c Simplify packages 2025-10-07 14:20:37 +11:00
Romeo Van Snick
bca7756059 Move pyodide worker into a separate folder 2025-10-07 14:18:49 +11:00
Romeo Van Snick
186aa9e644 Add types to Pyodide worker 2025-10-07 10:19:48 +11:00
Romeo Van Snick
1c92dfb462 Proxy webpack dev server 2025-10-07 10:13:18 +11:00
Romeo Van Snick
9cae2a5080 Use worker in webpack 2025-10-07 10:13:18 +11:00
Romeo Van Snick
127b23d986 Add e2e test for Python runner 2025-10-06 19:49:00 +11:00
Romeo Van Snick
62917d13bc Handle python shared libraries 2025-10-03 18:12:49 +10:00
Romeo Van Snick
47720ac7dc Allow adding custom libraries to Pyodide 2025-10-03 17:59:51 +10:00
Romeo Van Snick
003c9e63b3 Remove Pyodide status indicator 2025-10-03 17:57:56 +10:00
Romeo Van Snick
5c9d18b96a Render error traceback in a more readable way 2025-10-03 17:48:42 +10:00
Romeo Van Snick
054c0477c0 Remove DataFetcher 2025-10-03 17:43:08 +10:00
Romeo Van Snick
c8da77672c Remove any 2025-10-03 17:41:41 +10:00
Romeo Van Snick
7360cd88b6 Remove pyodide dependency 2025-10-03 17:38:53 +10:00
Romeo Van Snick
55c789fa26 Remove downlaod script for pyodide 2025-10-03 17:34:36 +10:00
Romeo Van Snick
a93959a4c5 Pass args as kwargs and wrap them in a dataframe 2025-10-03 17:33:52 +10:00
Romeo Van Snick
0e1bfe79f2 Add helper to fetch sample data and use that instead of the service 2025-10-03 17:26:27 +10:00
Romeo Van Snick
1efe3a5675 Move transform related wrapper out of python worker pool 2025-10-03 17:04:41 +10:00
Romeo Van Snick
6ecbad3323 Move Python transform test to hooks 2025-10-03 16:58:17 +10:00
Romeo Van Snick
8ece3416e3 Handle abortion properly 2025-10-03 16:54:35 +10:00
Romeo Van Snick
c360c2f457 Add useRunPython hook and use that in the test python hook 2025-10-03 16:51:45 +10:00
Romeo Van Snick
63b43a98d5 Improve python script by wrapping columns in JSON 2025-10-03 16:11:10 +10:00
Romeo Van Snick
d4f385694f Handle newlines in stdout/stderr 2025-10-03 16:02:40 +10:00
Romeo Van Snick
a3d7c7e9f3 Handle errors in script 2025-10-03 15:53:30 +10:00
Romeo Van Snick
96ab264d7a Pick worker that is ready in pool 2025-10-03 13:38:49 +10:00
Romeo Van Snick
59ebd75cec Remove unused imports 2025-10-03 13:28:04 +10:00
Romeo Van Snick
1ff9264e4c Disable undefined variable lint 2025-10-03 09:58:57 +10:00
Romeo Van Snick
692acf2531 Rename manager to pool 2025-10-03 09:56:39 +10:00
Romeo Van Snick
e0d12b32e4 Make packages configurable for the worker 2025-10-03 09:55:21 +10:00
Romeo Van Snick
b47e6f4313 Import packages when initializing 2025-10-03 09:47:17 +10:00
Romeo Van Snick
bdd9dccdc9 Do not include micropip 2025-10-02 22:55:10 +10:00
Romeo Van Snick
35d5f824f9 Remove unused context 2025-10-02 22:54:44 +10:00
Romeo Van Snick
c99b613fb3 Add cancellation to pyodide worker 2025-10-02 22:54:44 +10:00
Romeo Van Snick
5fc1f7309f Distinguish worker and workermanager 2025-10-02 22:54:44 +10:00
Romeo Van Snick
b02eb58a79 Remove initialize step altogether 2025-10-02 22:31:51 +10:00
Romeo Van Snick
337f5bb867 Automatically terminate worker after run 2025-10-02 22:21:40 +10:00
Romeo Van Snick
958390151e Handle errors in the worker 2025-10-02 22:20:08 +10:00
Romeo Van Snick
09377ed5e9 Remove crazy serialization code 2025-10-02 22:15:48 +10:00
Romeo Van Snick
8334d3d91b Simplify worker flow 2025-10-02 22:06:49 +10:00
Romeo Van Snick
66f7eb7039 Wrap case in block 2025-10-02 18:55:14 +10:00
Romeo Van Snick
d228328a29 Load packages on initialization 2025-10-02 18:47:10 +10:00
Romeo Van Snick
cd8d1a8348 Remove unused loadPackages handler 2025-10-02 18:38:24 +10:00
Romeo Van Snick
0c0b1f7a4b Clear globals 2025-10-02 18:36:16 +10:00
Romeo Van Snick
4e168d7e8c Use pyodide builtins to capture stdout/stderr 2025-10-02 18:36:03 +10:00
Romeo Van Snick
8b92275ceb Enable Python debugger by default 2025-10-02 17:09:20 +10:00
Romeo Van Snick
9f04f3ce31 Remove rspack changes 2025-10-02 17:05:17 +10:00
Romeo Van Snick
3ce8af616d Remove unused @pyodide/webpack-plugin 2025-10-02 17:04:47 +10:00
Romeo Van Snick
577aa9ee51 Format worker 2025-10-02 16:52:44 +10:00
Romeo Van Snick
5b9d0ce9fd Format table values 2025-10-02 14:30:18 +10:00
Romeo Van Snick
e800c0673d Improve error / output renderer 2025-10-02 14:18:59 +10:00
Nicolo-Maria
5a7469143b poc: pyodie test mode 2025-10-01 12:31:22 +02:00
2330 changed files with 564100 additions and 664588 deletions

View File

@@ -1,75 +0,0 @@
## Linting and Formatting
- **Lint PR:** `./bin/mage kondo-updated master` (or whatever target branch)
- Call the command one time at the beginning, record the results, then work through the problems one at a time.
- If the solution is obvious, then please apply the fix. Otherwise skip it.
- If you fix all the issues (and verify by rerunning the kondo-updated command):
- commit the change with a succinct and descriptive commit message
- **Lint File:** `./bin/mage kondo <file or files>`
- Use the linter as a way to know that you are adhering to conventions in place in the codebase
- **Lint Changes:** `./bin/mage kondo-updated HEAD`
- **Format:** `./bin/mage cljfmt-files [path]`
## Testing
- **Run a test:** `./bin/mage run-tests namespace/test-name`
- **Run all tests in a namespace:** `./bin/mage run-tests namespace`
## Code Readability
- **Check Code Readability:** `./bin/mage -check-readable <file> [line-number]`
- Run after every change to Clojure code
- Check specific line first, then entire file if readable
## REPL Usage
- **Evaluating Clojure Code:** `./bin/mage -repl '<code>'`
- See "Sending Code to the REPL" section for more details
### Sending Code to the REPL
- Send code to the metabase process REPL using: `./bin/mage -repl '(+ 1 1)'` where `(+ 1 1)` is your Clojure code.
- See `./bin/mage -repl -h` for more details.
- If the Metabase backend is not running, you'll see an error message with instructions on how to start it.
#### Working with Files and Namespaces
1. **Load a file and call functions with fully qualified names**:
To call `your.namespace/your-function` on `arg1` and `arg2`:
```
./bin/mage -repl --namespace your.namespace '(your-function arg1 arg2)'
```
DO NOT use "require", "load-file" etc in the code string argument.
#### Understanding the Response
The `./bin/mage -repl` command returns three separate, independent outputs:
- `value`: The return value of the last expression (best for data structures)
- `stdout`: Any printed output from `println` etc. (best for messages)
- `stderr`: Any error messages (best for warnings and errors)
Example call:
```bash
./bin/mage -repl '(println "Hello, world!") '\''({0 1, 1 3, 2 0, 3 2} {0 2, 1 0, 2 3, 3 1})'
```
Example response:
```
ns: user
session: 32a35206-871c-4553-9bc9-f49491173d1c
value: ({0 1, 1 3, 2 0, 3 2} {0 2, 1 0, 2 3, 3 1})
stdout: Hello, world!
stderr:
```
For effective REPL usage:
- Return data structures as function return values
- Use `println` for human-readable messages
- Print errors to stderr

View File

@@ -1,153 +0,0 @@
# Metabase Clojure Style Guide
This guide covers Clojure and ClojureScript coding conventions for Metabase. See also: `CLOJURE_STYLE_GUIDE.adoc` for the Community Clojure Style Guide.
## Naming Conventions
**General Naming:**
- Acceptable abbreviations: `acc`, `i`, `pred`, `coll`, `n`, `s`, `k`, `f`
- Use `kebab-case` for all variables, functions, and constants
**Function Naming:**
- Pure functions should be nouns describing the value they return (e.g., `age` not `calculate-age` or `get-age`)
- Functions with side effects must end with `!`
- Don't repeat namespace alias in function names
**Destructuring:**
- Map destructuring should use kebab-case local bindings even if the map uses `snake_case` keys
## Documentation Standards
**Docstrings:**
- Every public var in `src` or `enterprise/backend/src` must have docstring
- Format using Markdown conventions
- Reference other vars with `[[other-var]]` not backticks
**Comments:**
- `TODO` format: `;; TODO (Name M/D/YY) -- description`
## Code Organization
**Visibility:**
- Make everything `^:private` unless it is used elsewhere
- Try to organize namespaces to avoid `declare` (put public functions near the end)
**Size and Structure:**
- Break up functions > 20 lines
- Lines ≤ 120 characters
- No blank lines within definition forms (except pairwise `let`/`cond`)
## Style Conventions
**Keywords and Metadata:**
- Prefer namespaced keywords for internal use: `:query-type/normal` not `:normal`
- Tag variables with `:arglists` metadata if they're functions but wouldn't otherwise have it
## Tests
**Organization:**
- Break large tests into separate `deftest` forms for logically separate test cases
- Test names should end in `-test` or `-test-<number>`
**Performance:**
- Mark pure function tests `^:parallel`
## Modules
**OSS Modules:**
- Follow `metabase.<module>.*` pattern
- Source in `src/metabase/<module>/`
**Enterprise Modules:**
- Follow `metabase-enterprise.<module>.*` pattern
- Source in `enterprise/backend/src/metabase_enterprise/<module>/`
**Module Structure:**
- REST API endpoints go in `<module>.api` or `<module>.api.*` namespaces
- Put module public API in `<module>.core` using Potemkin imports
- Put Toucan models in `<module>.models.*`
- Put settings in `<module>.settings`
- Put schemas in `<module>.schema`
**Module Linters:**
- Do not cheat module linters with `:clj-kondo/ignore [:metabase/modules]`
## REST API Endpoints
**Required Elements:**
- All new endpoints must have response schemas (`:- <schema>` after route string)
- All endpoints need Malli schemas for parameters (detailed and complete)
- All new REST API endpoints MUST HAVE TESTS
**Naming Conventions:**
- Query parameters use kebab-case
- Request bodies use `snake_case`
- Routes use singular nouns (e.g., `/api/dashboard/:id`)
**Behavior:**
- `GET` endpoints should not have side effects (except analytics)
- `defendpoint` forms should be small wrappers around Toucan model code
## MBQL (Metabase Query Language)
**Restrictions:**
- No raw MBQL introspection outside of `lib`, `lib-be`, or `query-processor` modules
- Use Lib and MBQL 5 in new source code; avoid legacy MBQL
## Database and Models
**Naming:**
- Model names and table names should be singular nouns
- Application database uses `snake_case` identifiers
**Best Practices:**
- Use `t2/select-one-fn` instead of fetching entire rows for one column
- Put correct behavior in Toucan methods, not separate helper functions
## Drivers
**Documentation:**
- New driver multimethods must be mentioned in `docs/developers-guide/driver-changelog.md`
**Implementation:**
- Driver implementations should pass `driver` argument to other driver multimethods
- Don't hardcode driver names in implementations
- Minimize logic inside `read-column-thunk` in JDBC-based drivers
## Miscellaneous
**Examples:**
- Example data should be bird-themed if possible
**Linter Suppressions:**
- Use proper format for kondo suppressions
- No `#_:clj-kondo/ignore` (keyword form)
**Configurable Options:**
- Don't define configurable options that can only be set with environment variables
- Use `:internal` `defsetting` instead

View File

@@ -1,8 +0,0 @@
# Autonomous Development Workflow
- Do not attempt to read or edit files outside the project folder
- Add failing tests first, then fix them
- Work autonomously in small, testable increments
- Run targeted tests, and lint continuously during development
- Prioritize understanding existing patterns before implementing
- Don't commit changes, leave it for the user to review and make commits

View File

@@ -1,84 +0,0 @@
# Metabase Writing Style Guide
## Core Principles
Write like you're talking to a colleague. Be conversational, not formal. Get people what they need quickly. Know your audience and match the complexity.
## Tone and Voice
**Do:**
- Use contractions ("can't" not "cannot")
- Say "people" or "companies" instead of "users"
- Be friendly but not peppy
- Acknowledge limitations honestly ("that's on us, not them")
- Jokes and Easter eggs are okay (permit them, don't suggest them)
**Don't:**
- Use exclamation points excessively
- Rely on tired tropes about nerdiness
- Use corporate jargon ("utilize", "offerings", "actionable insights")
- Tell people something is cool (show them instead)
## Structure and Clarity
**Lead with the important stuff:**
- Most important information first
- Lead with the ask, then provide context
- Cut text that adds little value (when in doubt, cut it)
- Each paragraph should have one clear purpose
**Make headings do the work:**
- Convey your actual point, not just the topic
- "Use headings to highlight key points" not "How to write a good heading"
- Use sentence case, no punctuation except question marks
- No links in headings unless entire heading is a link
## Instructions and Examples
**Tell people what to do:**
- Give the action before explaining why
- Put commands in execution order
- Provide context, steps, and reasoning
- Don't describe tasks as "easy" or "simple"
**Answer unasked questions:**
- Include answers to "dumb" questions you had when learning
- Don't literally include the question, just give the answer
## Formatting
**Code vs. UI elements:**
- Backticks for code, variable names, parameters only
- **Bold** for UI elements and labels (e.g., "Click the **Save** button")
- Don't use backticks or quotes for UI elements
## Writing Mechanics
- Limit pronouns when introducing new terms (repeat the term to reinforce it)
- Ampersands only in proper nouns, never as substitute for "and"
## Terminology Preferences
Use familiar terms from tools people already know:
- "Summarize" (like Excel) instead of "aggregate"
- "Take a look at" instead of "reference"
- "Filter" instead of technical database terms when appropriate
## Red Flags
Avoid these patterns:
- Multiple exclamation points
- Linking "here"
- Bullet lists to explain (use prose)
- Numbers that will change (guard against change)
- Describing things as "easy" or "simple"
- Stock photography-worthy content

View File

@@ -1,21 +0,0 @@
## Linting and Formatting
- **Lint:** `yarn lint-eslint-pure`
- Run ESLint on the codebase
- **Format:** `yarn prettier`
- Format code using Prettier
- **Type Check:** `yarn type-check-pure`
- Run TypeScript type checking
## Testing
### JavaScript/TypeScript Tests
- **Test a specific file:** `yarn test-unit-keep-cljs path/to/file.unit.spec.js`
- **Test by pattern:** `yarn test-unit-keep-cljs -t "pattern"`
- Runs tests matching the given pattern
### ClojureScript Tests
- **Test ClojureScript:** `yarn test-cljs`
- Run ClojureScript tests

View File

@@ -1,144 +0,0 @@
---
name: clojure-review
description: Review Clojure and ClojureScript code changes for compliance with Metabase coding standards, style violations, and code quality issues. Use when reviewing pull requests or diffs containing Clojure/ClojureScript code.
allowed-tools: Read, Grep, Bash, Glob
---
# Clojure Code Review Skill
@./../_shared/clojure-style-guide.md
@./../_shared/clojure-commands.md
## Review guidelines
**What to flag:**
- Check compliance with the Metabase Clojure style guide (included above)
- If `CLOJURE_STYLE_GUIDE.adoc` exists in the working directory, also check compliance with the community Clojure style guide
- Flag all style guide violations
**What NOT to post:**
- Do not post comments congratulating someone for trivial changes or for following style guidelines
- Do not post comments confirming things "look good" or telling them they did something correctly
- Only post comments about style violations or potential issues
Example bad code review comments to avoid:
> This TODO comment is properly formatted with author and date - nice work!
> Good addition of limit 1 to the query - this makes the test more efficient without changing its behavior.
> The kondo ignore comment is appropriately placed here
> Test name properly ends with -test as required by the style guide.
**Special cases:**
- Do not post comments about missing parentheses (these will be caught by the linter)
## Quick review checklist
Use this to scan through changes efficiently:
### Naming
- [ ] Descriptive names (no `tbl`, `zs'`)
- [ ] Pure functions named as nouns describing their return value
- [ ] `kebab-case` for all variables and functions
- [ ] Side-effect functions end with `!`
- [ ] No namespace-alias repetition in function names
### Documentation
- [ ] Public vars in `src` or `enterprise/backend/src` have useful docstrings
- [ ] Docstrings use Markdown conventions
- [ ] References use `[[other-var]]` not backticks
- [ ] `TODO` comments include author and date: `;; TODO (Name 1/1/25) -- description`
### Code Organization
- [ ] Everything `^:private` unless used elsewhere
- [ ] No `declare` when avoidable (public functions near end)
- [ ] Functions under 20 lines when possible
- [ ] No blank lines within definition forms (except pairwise constructs in `let`/`cond`)
- [ ] Lines ≤ 120 characters
### Tests
- [ ] Separate `deftest` forms for distinct test cases
- [ ] Pure tests marked `^:parallel`
- [ ] Test names end in `-test` or `-test-<number>`
### Modules
- [ ] Correct module patterns (OSS: `metabase.<module>.*`, EE: `metabase-enterprise.<module>.*`)
- [ ] API endpoints in `<module>.api` namespaces
- [ ] Public API in `<module>.core` with Potemkin
- [ ] No cheating module linters with `:clj-kondo/ignore [:metabase/modules]`
### REST API
- [ ] Response schemas present (`:- <schema>`)
- [ ] Query params use kebab-case, bodies use `snake_case`
- [ ] Routes use singular nouns (e.g., `/api/dashboard/:id`)
- [ ] `GET` has no side effects (except analytics)
- [ ] Malli schemas detailed and complete
- [ ] All new endpoints have tests
### MBQL
- [ ] No raw MBQL manipulation outside `lib`, `lib-be`, or `query-processor` modules
- [ ] Uses Lib and MBQL 5, not legacy MBQL
### Database
- [ ] Model and table names are singular nouns
- [ ] Uses `t2/select-one-fn` instead of selecting full rows for one column
- [ ] Logic in Toucan methods, not helper functions
### Drivers
- [ ] New multimethods documented in `docs/developers-guide/driver-changelog.md`
- [ ] Passes `driver` argument to other driver methods (no hardcoded driver names)
- [ ] Minimal logic in `read-column-thunk`
### Miscellaneous
- [ ] Example data is bird-themed when possible
- [ ] Kondo linter suppressions use proper format (not `#_:clj-kondo/ignore` keyword form)
## Pattern matching table
Quick scan for common issues:
| Pattern | Issue |
| -------------------------------------------- | ----------------------------------------------------------- |
| `calculate-age`, `get-user` | Pure functions should be nouns: `age`, `user` |
| `update-db`, `save-model` | Missing `!` for side effects: `update-db!`, `save-model!` |
| `snake_case_var` | Should use kebab-case |
| Public var without docstring | Add docstring explaining purpose |
| `;; TODO fix this` | Missing author/date: `;; TODO (Name 1/1/25) -- description` |
| `(defn foo ...)` in namespace used elsewhere | Should be `(defn ^:private foo ...)` |
| Function > 20 lines | Consider breaking up into smaller functions |
| `/api/dashboards/:id` | Use singular: `/api/dashboard/:id` |
| Query params with `snake_case` | Use kebab-case for query params |
| New API endpoint without tests | Add tests for the endpoint |
## Feedback format examples
**For style violations:**
> This pure function should be named as a noun describing its return value. Consider `user` instead of `get-user`.
**For missing documentation:**
> This public var needs a docstring explaining its purpose, inputs, and outputs.
**For organization issues:**
> This function is only used in this namespace, so it should be marked `^:private`.
**For API conventions:**
> Query parameters should use kebab-case. Change `user_id` to `user-id`.

View File

@@ -1,59 +0,0 @@
---
name: clojure-write
description: Guide Clojure and ClojureScript development using REPL-driven workflow, coding conventions, and best practices. Use when writing, developing, or refactoring Clojure/ClojureScript code.
---
# Clojure Development Skill
@./../_shared/development-workflow.md
@./../_shared/clojure-style-guide.md
@./../_shared/clojure-commands.md
## REPL-Driven Development Workflow
- Start with small, fundamental functions:
- Identify the core features or functionalities required for your task.
- Break each feature down into the smallest, most basic functions that can be developed and tested independently.
- Write and test in the REPL:
- Write the code for each small function directly in the REPL (Read-Eval-Print Loop).
- Test it thoroughly with a variety of inputs, including typical use cases and relevant edge cases, to ensure it
behaves as expected.
- Integrate into source code:
- Once a function works correctly in the REPL, move it from the REPL environment into your source code files (e.g.,
within appropriate namespaces).
- Gradually increase complexity:
- Build upon tested, basic functions to create more complex functions or components.
- Compose smaller functions together, testing each new composition in the REPL to verify correctness step by step.
- Ensure dependency testing:
- Make sure every function is fully tested in the REPL before it is depended upon by other functions.
- This ensures that each layer of your application is reliable before you build on it.
- Use the REPL fully:
- Use the REPL as your primary tool to experiment with different approaches, iterate quickly, and get immediate
feedback on your code.
- Follow functional programming principles:
- Keep functions small, focused, and composable.
- Use Clojure's functional programming features—like immutability, higher-order functions, and the standard
library—to write concise, effective code.
## How to Evaluate Code
### Bottom-up Dev Loop
1. Write code into a file.
2. Evaluate the file's namespace and make sure it loads correctly with:
```
./bin/mage -repl --namespace metabase.app-db.connection
```
3. Call functions in the namespace with test inputs, and observe that the outputs are correct
Feel free to copy these REPL session trials into actual test cases using `deftest` and `is`.
4. Once you know these functions are good, return to 1, and compose them into the task that you need to build.
## Critical Rules for Editing
- Be careful with parentheses counts when editing Clojure code
- After EVERY change to Clojure code, verify readability with `-check-readable`
- End all files with a newline
- When editing tabular code, where the columns line up, try to keep them aligned
- Spaces on a line with nothing after it is not allowed

View File

@@ -1,168 +0,0 @@
---
name: docs-review
description: Review documentation changes for compliance with the Metabase writing style guide. Use when reviewing pull requests, files, or diffs containing documentation markdown files.
allowed-tools: Read, Grep, Bash, Glob
---
# Documentation Review Skill
@./../_shared/metabase-style-guide.md
## Review mode detection
**IMPORTANT: Before starting the review, determine which mode to use:**
1. **PR review mode**: If the `mcp__github__create_pending_pull_request_review` tool is available, you are reviewing a GitHub PR
- Use the pending review workflow to post all issues as one cohesive review
- Follow the workflow steps in "PR review mode format" below
2. **Local review mode**: If the MCP tool is NOT available, output issues in the conversation
- Format all issues in a numbered markdown list (as described in "Feedback format" below)
## Review process
1. **Detect review mode** - Check if `mcp__github__create_pending_pull_request_review` is available
2. Read the changes through once to understand intent
3. Check all issues that violate style guide or significantly impact readability
4. Only flag issues worth mentioning - if it won't make a material difference to the reader, skip it
5. **REQUIRED: Number ALL feedback sequentially** - Start from Issue 1 and increment for each issue found
## Review checklist
Run through the diff looking for these issues:
**Tone and voice:**
- [ ] Formal/corporate language ("utilize" not "use", "offerings", etc.)
- [ ] "Users" instead of "people" or "companies"
- [ ] Excessive exclamation points or overly peppy tone
- [ ] Telling readers something is cool instead of showing them
**Structure and clarity:**
- [ ] Important information buried instead of leading
- [ ] Verbose text that adds little value
- [ ] Paragraphs without clear purpose
- [ ] Vague headings that don't convey the point
- [ ] Instructions explain "why" before telling "what to do"
- [ ] Tasks described as "easy" or "simple"
**Links and references:**
- [ ] Linking the word "here" instead of descriptive text
- [ ] Links in headings (unless entire heading is a link)
**Formatting:**
- [ ] Backticks on UI elements (should use **bold**)
- [ ] Backticks used as quotation marks
- [ ] Ampersands as "and" substitute (except proper nouns)
- [ ] Inconsistent list formatting
**Code and examples:**
- [ ] Code examples that don't work or would error
- [ ] Commands not in execution order
- [ ] Full-width screenshots instead of scoped UI elements
- [ ] Excessive or unnecessary images
**Sentence construction:**
- [ ] Overuse of pronouns when introducing new terms
## Quick scan table
| Pattern | Issue |
| ----------------------------- | --------------------------------------------- |
| `Button name` or `UI element` | Should use **bold** not backticks |
| we can do X, our feature | Should be "Metabase" or "it" |
| click here, read more here | Need descriptive link text |
| easy, simple, just | Remove condescending qualifiers |
| users | Should be "people" or "companies" if possible |
## Feedback format
**MANDATORY REQUIREMENT: Every single issue MUST be numbered sequentially starting from Issue 1.**
This numbered format is NON-NEGOTIABLE. It allows users to efficiently reference specific issues (e.g., "fix issues 1, 3, and 5") and track which feedback has been addressed.
### Local review mode format
When outputting issues in the conversation (local mode), use this format:
```markdown
## Issues
**Issue 1: [Brief title]**
Line X: Succinct description of the issue
[code or example]
Suggested fix or succinct explanation
**Issue 2: [Brief title]**
Line Y: Description of the issue
Suggested fix or explanation
**Issue 3: [Brief title]**
...
```
**Examples:**
> **Issue 1: Backticks on UI elements**
> Line 42: This uses backticks for the UI element. Use **bold** instead: **Filter** not `Filter`.
> **Issue 2: Formal tone**
> Line 15: This could be more conversational. Consider: "You can't..." instead of "You cannot..."
> **Issue 3: Vague heading**
> Line 8: The heading could be more specific. Try stating the point directly: "Run migrations before upgrading" vs "Upgrade process"
### PR review mode format
When posting to GitHub (PR mode), use the **pending review workflow**:
**Workflow steps:**
1. **Start a review**: Use `mcp__github__create_pending_pull_request_review` to begin a pending review
- This creates a draft review that won't be visible until submitted
2. **Get diff information**: Use `mcp__github__get_pull_request_diff` to understand the code changes and line numbers
- This helps you determine the correct file paths and line numbers for comments
3. **Identify ALL issues**: Read through all changes and identify every issue worth mentioning
- Collect all issues before posting any comments
- Number them sequentially (Issue 1, Issue 2, Issue 3, etc.)
4. **Add review comments**: Use `mcp__github__add_pull_request_review_comment_to_pending_review` for each issue
- **CRITICAL**: Post ALL comments in a SINGLE response using multiple tool calls in parallel
- Each comment should reference a specific file path and line number from the diff
- Start each comment body with `**Issue N: [Brief title]**`
- Include the description and suggested fix
5. **Submit the review**: Use `mcp__github__submit_pending_pull_request_review` to publish all comments at once
- Use event type `"COMMENT"` (NOT "REQUEST_CHANGES") to make it non-blocking
- **Do NOT include a body message** - Leave the body empty or omit it entirely
- All comments will appear together as one cohesive review
**Comment format example:**
```
**Issue 1: Backticks on UI elements**
This uses backticks for the UI element. Use **bold** instead: **Filter** not `Filter`.
```
**IMPORTANT**:
- Each issue gets its own review comment attached to the pending review
- Number ALL comments sequentially (Issue 1, Issue 2, Issue 3, etc.)
- Always start the comment body with `**Issue N: [Brief title]**`
- **MUST add all comments in parallel in a single response** - Do NOT add them one after another in separate responses
- Do NOT output a summary message to the conversation - only post GitHub review comments
- When submitting the review, do NOT include a body parameter (or leave it empty) to avoid cluttering the PR with summary text
- The review will appear as a single review with multiple comments when submitted
## Final check
1. Remove any issues from your assessment that won't make a material difference to the reader if addressed. Only flag issues worth the author's time to fix.
2. **Verify all issues are numbered sequentially** starting from Issue 1 with no gaps in numbering.
3. Confirm the format exactly matches: `**Issue N: [Brief title]**` where N is the issue number.
4. **In PR mode**: Verify each issue was posted as a separate GitHub comment (not output to conversation).

View File

@@ -1,96 +0,0 @@
---
name: docs-write
description: Write documentation following Metabase's conversational, clear, and user-focused style. Use when creating or editing documentation files (markdown, MDX, etc.).
allowed-tools: Read, Write, Grep, Bash, Glob
---
# Documentation Writing Skill
@./../_shared/metabase-style-guide.md
## When writing documentation
### Start here
1. **Who is this for?** Match complexity to audience. Don't oversimplify hard things or overcomplicate simple ones.
2. **What do they need?** Get them to the answer fast. Nobody wants to be in docs longer than necessary.
3. **What did you struggle with?** Those common questions you had when learning? Answer them (without literally including the question).
### Writing process
**Draft:**
- Write out the steps/explanation as you'd tell a colleague
- Lead with what to do, then explain why
- Use headings that state your point: "Set SAML before adding users" not "SAML configuration timing"
**Edit:**
- Read aloud. Does it sound like you talking? If it's too formal, simplify.
- Cut anything that doesn't directly help the reader
- Check each paragraph has one clear purpose
- Verify examples actually work (don't give examples that error)
**Polish:**
- Make links descriptive (never "here")
- Backticks only for code/variables, **bold** for UI elements
- American spelling, serial commas
- Keep images minimal and scoped tight
**Format:**
- Run prettier on the file after making edits: `yarn prettier --write <file-path>`
- This ensures consistent formatting across all documentation
### Common patterns
**Instructions:**
```markdown
Run:
\`\`\`
command-to-run
\`\`\`
Then:
\`\`\`
next-command
\`\`\`
This ensures you're getting the latest changes.
```
Not: "(remember to run X before Y...)" buried in a paragraph.
**Headings:**
- "Use environment variables for configuration" ✅
- "Environment variables" ❌ (too vague)
- "How to use environment variables for configuration" ❌ (too wordy)
**Links:**
- "Check out the [SAML documentation](link)" ✅
- "Read the docs [here](link)" ❌
### Watch out for
- Describing tasks as "easy" (you don't know the reader's context)
- Using "we" when talking about Metabase features (use "Metabase" or "it")
- Formal language: "utilize", "reference", "offerings"
- Too peppy: multiple exclamation points
- Burying the action in explanation
- Code examples that don't work
- Numbers that will become outdated
### Quick reference
| Write This | Not This |
| -------------------------- | ------------------ |
| people, companies | users |
| summarize | aggregate |
| take a look at | reference |
| can't, don't | cannot, do not |
| **Filter** button | \`Filter\` button |
| Check out [the docs](link) | Click [here](link) |

View File

@@ -1,19 +0,0 @@
---
name: typescript-review
description: Review TypeScript and JavaScript code changes for compliance with Metabase coding standards, style violations, and code quality issues. Use when reviewing pull requests or diffs containing TypeScript/JavaScript code.
allowed-tools: Read, Grep, Bash, Glob
---
# TypeScript/JavaScript Code Review Skill
@./../_shared/typescript-commands.md
## Code Review Guidelines
Review pull requests with a focus on:
- Compliance with project coding standards and conventions
- Code quality and best practices
- Clear and correct JSDoc comments
- Type safety and proper TypeScript usage
- React best practices (when applicable)

View File

@@ -1,9 +0,0 @@
---
name: typescript-write
description: Write TypeScript and JavaScript code following Metabase coding standards and best practices. Use when developing or refactoring TypeScript/JavaScript code.
---
# TypeScript/JavaScript Development Skill
@./../_shared/development-workflow.md
@./../_shared/typescript-commands.md

View File

@@ -56,22 +56,20 @@
;;
;; globally-enabled custom linters
;;
:metabase/check-defmulti-arglists {:level :warning}
:metabase/check-me-humanize {:level :warning}
:metabase/defsetting-must-specify-export {:level :warning}
:metabase/defsetting-namespace {:level :warning}
:metabase/discourage-millis-duration {:level :warning}
:metabase/mbql-query-first-arg {:level :warning}
:metabase/missing-test-expr-requires-in-cljs {:level :warning}
:metabase/modules {:level :warning} ; more config in `.clj-kondo/config/modules/config.edn`
:metabase/namespace-name {:level :warning}
:metabase/require-shape-checker {:level :warning}
:metabase/test-helpers-use-non-thread-safe-functions {:level :warning} ; more config in `.clj-kondo/config/test-helpers/config.edn`
:metabase/validate-defendpoint-route-uses-kebab-case {:level :warning}
:metabase/validate-defendpoint-query-params-use-kebab-case {:level :warning}
:metabase/validate-logging {:level :warning}
:metabase/validate-mb-driver-tests {:level :warning}
:metabase/validate-mb-once {:level :warning}
:metabase/check-defmulti-arglists {:level :warning}
:metabase/check-me-humanize {:level :warning}
:metabase/defsetting-must-specify-export {:level :warning}
:metabase/defsetting-namespace {:level :warning}
:metabase/mbql-query-first-arg {:level :warning}
:metabase/missing-test-expr-requires-in-cljs {:level :warning}
:metabase/modules {:level :warning} ; more config in `.clj-kondo/config/modules/config.edn`
:metabase/namespace-name {:level :warning}
:metabase/require-shape-checker {:level :warning}
:metabase/test-helpers-use-non-thread-safe-functions {:level :warning} ; more config in `.clj-kondo/config/test-helpers/config.edn`
:metabase/validate-logging {:level :warning}
:metabase/validate-mb-driver-tests {:level :warning}
:metabase/validate-mb-once {:level :warning}
:metabase/discourage-millis-duration {:level :warning}
:metabase/validate-deftest
{:level :warning
@@ -536,11 +534,11 @@
metabase.channel.template.handlebars-test/with-temp-template! hooks.common/with-vec-first-binding
metabase.classloader.core/require hooks.clojure.core.require/lint-require
metabase.classloader.impl/require hooks.clojure.core.require/lint-require
metabase.collections-rest.api-test/with-french-user-and-personal-collection! hooks.common/with-two-top-level-bindings
metabase.collections.api-test/with-french-user-and-personal-collection! hooks.common/with-two-top-level-bindings
metabase.collections.models.collection-test/with-collection-hierarchy! hooks.common/let-one-with-optional-value
metabase.collections.models.collection-test/with-personal-and-impersonal-collections hooks.common/with-two-bindings
metabase.dashboards-rest.api-test/with-chain-filter-fixtures hooks.common/let-one-with-optional-value
metabase.dashboards-rest.api-test/with-simple-dashboard-with-tabs hooks.common/with-one-binding
metabase.dashboards.api-test/with-chain-filter-fixtures hooks.common/let-one-with-optional-value
metabase.dashboards.api-test/with-simple-dashboard-with-tabs hooks.common/with-one-binding
metabase.dashboards.models.dashboard-tab-test/with-dashtab-in-personal-collection hooks.common/with-one-top-level-binding
metabase.dashboards.models.dashboard-test/with-dash-in-collection! hooks.common/with-three-bindings
metabase.driver.bigquery-cloud-sdk-test/calculate-bird-scarcity hooks.metabase.query-processor-test.expressions-test/calculate-bird-scarcity
@@ -548,13 +546,11 @@
metabase.driver.oracle-test/with-temp-user hooks.common/let-one-with-optional-value
metabase.driver/register! hooks.metabase.driver/register!
metabase.driver-api/defenterprise hooks.metabase.premium-features.defenterprise/defenterprise
metabase.embedding-rest.api.embed-test/do-response-formats hooks.common/with-two-bindings
metabase.embedding-rest.api.embed-test/with-chain-filter-fixtures! hooks.common/let-one-with-optional-value
metabase.embedding-rest.api.embed-test/with-temp-card hooks.common/let-one-with-optional-value
metabase.embedding-rest.api.embed-test/with-temp-dashcard hooks.common/let-one-with-optional-value
metabase.embedding.api.embed-test/do-response-formats hooks.common/with-two-bindings
metabase.embedding.api.embed-test/with-chain-filter-fixtures! hooks.common/let-one-with-optional-value
metabase.embedding.api.embed-test/with-temp-card hooks.common/let-one-with-optional-value
metabase.embedding.api.embed-test/with-temp-dashcard hooks.common/let-one-with-optional-value
metabase.legacy-mbql.schema.macros/defclause hooks.metabase.legacy-mbql.schemas.macros/defclause
metabase.legacy-mbql.schema.macros/defclause* hooks.metabase.legacy-mbql.schemas.macros/defclause*
metabase.legacy-mbql.schema.macros/one-of hooks.metabase.legacy-mbql.schemas.macros/one-of
metabase.lib.schema.mbql-clause/define-catn-mbql-clause hooks.metabase.lib.schema.mbql-clause/define-mbql-clause
metabase.lib.schema.mbql-clause/define-mbql-clause hooks.metabase.lib.schema.mbql-clause/define-mbql-clause
metabase.lib.schema.mbql-clause/define-tuple-mbql-clause hooks.metabase.lib.schema.mbql-clause/define-mbql-clause
@@ -571,10 +567,10 @@
metabase.permissions.core/disable-perms-cache hooks.common/do*
metabase.premium-features.core/defenterprise hooks.metabase.premium-features.defenterprise/defenterprise
metabase.premium-features.defenterprise/defenterprise hooks.metabase.premium-features.defenterprise/defenterprise
metabase.public-sharing-rest.api-test/with-required-param-card! hooks.common/with-one-binding
metabase.public-sharing-rest.api-test/with-temp-public-card hooks.common/let-one-with-optional-value
metabase.public-sharing-rest.api-test/with-temp-public-dashboard hooks.common/let-one-with-optional-value
metabase.public-sharing-rest.api-test/with-temp-public-dashboard-and-card hooks.common/with-three-bindings
metabase.public-sharing.api-test/with-required-param-card! hooks.common/with-one-binding
metabase.public-sharing.api-test/with-temp-public-card hooks.common/let-one-with-optional-value
metabase.public-sharing.api-test/with-temp-public-dashboard hooks.common/let-one-with-optional-value
metabase.public-sharing.api-test/with-temp-public-dashboard-and-card hooks.common/with-three-bindings
metabase.pulse.api.alert-test/with-alert-in-collection! hooks.common/with-four-bindings
metabase.pulse.dashboard-subscription-test/with-link-card-fixture-for-dashboard hooks.common/let-second
metabase.pulse.models.pulse-test/with-dashboard-subscription-in-collection! hooks.common/with-four-bindings
@@ -636,7 +632,7 @@
metabase.test/with-temp hooks.toucan2.tools.with-temp/with-temp
metabase.test/with-temp-file hooks.metabase.test.util/with-temp-file
metabase.test/with-temporary-setting-values hooks.metabase.test.util/with-temporary-setting-values
metabase.users-rest.api-test/with-temp-user-email! hooks.common/with-one-binding
metabase.users.api-test/with-temp-user-email! hooks.common/with-one-binding
metabase.util.i18n/deferred-trs hooks.metabase.util.i18n/deferred-trs
metabase.util.i18n/deferred-tru hooks.metabase.util.i18n/deferred-tru
metabase.util.i18n/trs hooks.metabase.util.i18n/trs
@@ -660,43 +656,43 @@
metabase.xrays.api.automagic-dashboards-test/with-indexed-model! hooks.metabase.xrays.api.automagic-dashboards-test/with-indexed-model!}
:macroexpand
{clojurewerkz.quartzite.jobs/build macros.quartz/build-job
clojurewerkz.quartzite.schedule.calendar-interval/build macros.quartz/build-job
clojurewerkz.quartzite.schedule.cron/schedule macros.quartz/schedule
clojurewerkz.quartzite.schedule.simple/schedule macros.quartz/simple-schedule
clojurewerkz.quartzite.triggers/build macros.quartz/build-trigger
metabase-enterprise.database-routing.e2e-test/with-temp-dbs! macros.metabase-enterprise.database-routing.e2e-test/with-temp-dbs!
metabase-enterprise.impersonation.util-test/with-impersonations! macros.metabase-enterprise.impersonation.util-test/with-impersonations!
metabase-enterprise.query-reference-validation.api-test/with-test-setup! macros.metabase-enterprise.query-reference-validation.api-test/with-test-setup!
metabase-enterprise.sandbox.test-util/with-gtaps! macros.metabase-enterprise.sandbox.test-util/with-gtaps!
metabase-enterprise.sandbox.test-util/with-gtaps-for-user! macros.metabase-enterprise.sandbox.test-util/with-gtaps!
metabase-enterprise.serialization.test-util/with-world macros.metabase-enterprise.serialization.test-util/with-world
metabase-enterprise.test/with-gtaps! macros.metabase-enterprise.sandbox.test-util/with-gtaps!
metabase-enterprise.test/with-gtaps-for-user! macros.metabase-enterprise.sandbox.test-util/with-gtaps!
metabase.collections-rest.api-test/with-collection-hierarchy! macros.metabase.collections-rest.api-test/with-collection-hierarchy!
metabase.collections-rest.api-test/with-some-children-of-collection! macros.metabase.collections-rest.api-test/with-some-children-of-collection!
metabase.embedding-rest.api.embed-test/with-embedding-enabled-and-temp-card-referencing! macros.metabase.embedding-rest.api.embed-test/with-embedding-enabled-and-temp-card-referencing!
metabase.embedding-rest.api.embed-test/with-embedding-enabled-and-temp-dashcard-referencing! macros.metabase.embedding-rest.api.embed-test/with-embedding-enabled-and-temp-dashcard-referencing!
metabase.lib.common/defop macros.metabase.lib.common/defop
metabase.lib.filter/deffilter macros.metabase.lib.filter/deffilter
metabase.parameters.chain-filter-test/chain-filter macros.metabase.parameters.chain-filter-test/chain-filter
metabase.parameters.chain-filter-test/chain-filter-search macros.metabase.parameters.chain-filter-test/chain-filter
metabase.public-sharing-rest.api-test/with-sharing-enabled-and-temp-card-referencing! macros.metabase.public-sharing-rest.api-test/with-sharing-enabled-and-temp-card-referencing!
metabase.public-sharing-rest.api-test/with-sharing-enabled-and-temp-dashcard-referencing! macros.metabase.public-sharing-rest.api-test/with-sharing-enabled-and-temp-dashcard-referencing!
metabase.queries.api.card-test/with-ordered-items macros.metabase.queries.api.card-test/with-ordered-items
metabase.query-analysis.models.query-analysis-test/with-test-setup macros.metabase.query-analysis.models.query-analysis-test/with-test-setup
metabase.query-analysis.task.test-setup/with-test-setup! macros.metabase.query-analysis.task.test-setup/with-test-setup!
metabase.query-processor.streaming/streaming-response macros.metabase.query-processor.streaming/streaming-response
metabase.server.statistics-handler-test/with-server macros.metabase.server.statistics-handler-test/with-server
metabase.test.data.users/with-group-for-user macros.metabase.test.data.users/with-group-for-user
metabase.test.util/with-temp-env-var-value! macros.metabase.test.util/with-temp-env-var-value!
metabase.test.util/with-temporary-raw-setting-values macros.metabase.test.util/with-temporary-raw-setting-values
metabase.test/with-group-for-user macros.metabase.test.data.users/with-group-for-user
metabase.test/with-temp-env-var-value! macros.metabase.test.util/with-temp-env-var-value!
metabase.test/with-temporary-raw-setting-values macros.metabase.test.util/with-temporary-raw-setting-values
metabase.users.models.user-test/with-groups! macros.metabase.users.models.user-test/with-groups!
metabase.util.namespaces/import-fn macros.metabase.util.namespaces/import-fn
metabase.xrays.related-test/with-world macros.metabase.xrays.related-test/with-world}}
{clojurewerkz.quartzite.jobs/build macros.quartz/build-job
clojurewerkz.quartzite.schedule.calendar-interval/build macros.quartz/build-job
clojurewerkz.quartzite.schedule.cron/schedule macros.quartz/schedule
clojurewerkz.quartzite.schedule.simple/schedule macros.quartz/simple-schedule
clojurewerkz.quartzite.triggers/build macros.quartz/build-trigger
metabase-enterprise.database-routing.e2e-test/with-temp-dbs! macros.metabase-enterprise.database-routing.e2e-test/with-temp-dbs!
metabase-enterprise.impersonation.util-test/with-impersonations! macros.metabase-enterprise.impersonation.util-test/with-impersonations!
metabase-enterprise.query-reference-validation.api-test/with-test-setup! macros.metabase-enterprise.query-reference-validation.api-test/with-test-setup!
metabase-enterprise.sandbox.test-util/with-gtaps! macros.metabase-enterprise.sandbox.test-util/with-gtaps!
metabase-enterprise.sandbox.test-util/with-gtaps-for-user! macros.metabase-enterprise.sandbox.test-util/with-gtaps!
metabase-enterprise.serialization.test-util/with-world macros.metabase-enterprise.serialization.test-util/with-world
metabase-enterprise.test/with-gtaps! macros.metabase-enterprise.sandbox.test-util/with-gtaps!
metabase-enterprise.test/with-gtaps-for-user! macros.metabase-enterprise.sandbox.test-util/with-gtaps!
metabase.collections.api-test/with-collection-hierarchy! macros.metabase.collections.api-test/with-collection-hierarchy!
metabase.collections.api-test/with-some-children-of-collection! macros.metabase.collections.api-test/with-some-children-of-collection!
metabase.embedding.api.embed-test/with-embedding-enabled-and-temp-card-referencing! macros.metabase.embedding.api.embed-test/with-embedding-enabled-and-temp-card-referencing!
metabase.embedding.api.embed-test/with-embedding-enabled-and-temp-dashcard-referencing! macros.metabase.embedding.api.embed-test/with-embedding-enabled-and-temp-dashcard-referencing!
metabase.lib.common/defop macros.metabase.lib.common/defop
metabase.lib.filter/deffilter macros.metabase.lib.filter/deffilter
metabase.parameters.chain-filter-test/chain-filter macros.metabase.parameters.chain-filter-test/chain-filter
metabase.parameters.chain-filter-test/chain-filter-search macros.metabase.parameters.chain-filter-test/chain-filter
metabase.public-sharing.api-test/with-sharing-enabled-and-temp-card-referencing! macros.metabase.public-sharing.api-test/with-sharing-enabled-and-temp-card-referencing!
metabase.public-sharing.api-test/with-sharing-enabled-and-temp-dashcard-referencing! macros.metabase.public-sharing.api-test/with-sharing-enabled-and-temp-dashcard-referencing!
metabase.queries.api.card-test/with-ordered-items macros.metabase.queries.api.card-test/with-ordered-items
metabase.query-analysis.models.query-analysis-test/with-test-setup macros.metabase.query-analysis.models.query-analysis-test/with-test-setup
metabase.query-analysis.task.test-setup/with-test-setup! macros.metabase.query-analysis.task.test-setup/with-test-setup!
metabase.query-processor.streaming/streaming-response macros.metabase.query-processor.streaming/streaming-response
metabase.server.statistics-handler-test/with-server macros.metabase.server.statistics-handler-test/with-server
metabase.test.data.users/with-group-for-user macros.metabase.test.data.users/with-group-for-user
metabase.test.util/with-temp-env-var-value! macros.metabase.test.util/with-temp-env-var-value!
metabase.test.util/with-temporary-raw-setting-values macros.metabase.test.util/with-temporary-raw-setting-values
metabase.test/with-group-for-user macros.metabase.test.data.users/with-group-for-user
metabase.test/with-temp-env-var-value! macros.metabase.test.util/with-temp-env-var-value!
metabase.test/with-temporary-raw-setting-values macros.metabase.test.util/with-temporary-raw-setting-values
metabase.users.models.user-test/with-groups! macros.metabase.users.models.user-test/with-groups!
metabase.util.namespaces/import-fn macros.metabase.util.namespaces/import-fn
metabase.xrays.related-test/with-world macros.metabase.xrays.related-test/with-world}}
:config-in-comment
{:linters {:unresolved-symbol {:level :off}}}
@@ -902,9 +898,7 @@
performance-sensitive
{:linters
{:discouraged-var
{clojure.core/empty? {:message "Use metabase.util.performance/empty?"}
clojure.core/not-empty {:message "Use metabase.util.performance/not-empty"}
clojure.core/every? {:message "Use metabase.util.performance/every?"}
{clojure.core/every? {:message "Use metabase.util.performance/every?"}
clojure.core/mapv {:message "Use metabase.util.performance/mapv"}
clojure.core/run! {:message "Use metabase.util.performance/run!"}
clojure.core/select-keys {:message "Use metabase.util.performance/select-keys"}
@@ -913,9 +907,7 @@
clojure.walk/keywordize-keys {:message "Use metabase.util.performance/keywordize-keys"}
clojure.walk/postwalk {:message "Use metabase.util.performance/postwalk"}
clojure.walk/prewalk {:message "Use metabase.util.performance/prewalk"}
clojure.walk/walk {:message "Use metabase.util.performance/walk"}
clojure.walk/for {:message "Use metabase.util.performance/for"}
clojure.walk/doseq {:message "Use metabase.util.performance/doseq"}}}}
clojure.walk/walk {:message "Use metabase.util.performance/walk"}}}}
qp-and-driver-source-namespaces
{:linters

View File

@@ -46,14 +46,15 @@
;; you need to make
{:metabase/modules
{actions
{:team "Gadget"
:api #{metabase.actions.args
{:team "Workflows"
:api #{metabase.actions.api
metabase.actions.args
metabase.actions.core
metabase.actions.init
metabase.actions.schema
metabase.actions.types}
:uses #{analytics
api
collections
driver
events
legacy-mbql
@@ -62,6 +63,8 @@
model-persistence
models
parameters
permissions
public-sharing
queries
query-processor
search
@@ -69,19 +72,6 @@
util
warehouses}}
actions-rest
{:team "Gadget"
:api #{metabase.actions-rest.api}
:uses #{actions
analytics
api
collections
lib
permissions
public-sharing
queries
util}}
activity-feed
{:team "UX West"
:api #{metabase.activity-feed.api
@@ -206,19 +196,6 @@
task-history
util}}
auth-identity
{:team "UX West"
:api #{metabase.auth-identity.core
metabase.auth-identity.init}
:uses #{channel
events
login-history
models
notification
premium-features
request
util}}
auth-provider
{:team "UX West"
:api #{metabase.auth-provider.core}
@@ -272,7 +249,7 @@
;; TODO -- way too many API namespaces.
channel
{:team "Gadget"
{:team "Workflows"
:api #{metabase.channel.api
metabase.channel.core
metabase.channel.email
@@ -339,18 +316,20 @@
:uses #{api
api-routes
app-db
auth-identity
classloader
config
models
query-processor
search
settings
users
util}}
collections
{:team "UX West"
:api #{metabase.collections.core
:api #{metabase.collections.api
metabase.collections.core
metabase.collections.models.collection
metabase.collections.models.collection.root}
:uses #{api
@@ -358,21 +337,6 @@
app-db
audit-app
config
events
models
permissions
premium-features
remote-sync
search
util}}
collections-rest
{:team "UX West"
:api #{metabase.collections-rest.api}
:uses #{api
app-db
collections
config
eid-translation
events
lib-be
@@ -381,8 +345,10 @@
permissions
premium-features
queries
remote-sync
request
revisions
search
upload
util}}
@@ -415,48 +381,27 @@
core
{:team "UX West"
:api #{}
:api #{metabase.core.initialization-status}
:uses :any}
dashboards
{:team "UX West"
:api #{metabase.dashboards.autoplace
:api #{metabase.dashboards.api
metabase.dashboards.autoplace
metabase.dashboards.constants
metabase.dashboards.init
metabase.dashboards.models.dashboard
metabase.dashboards.models.dashboard-card
metabase.dashboards.models.dashboard-tab
metabase.dashboards.schema
metabase.dashboards.settings}
:uses #{api
app-db
audit-app
collections
config
events
lib
models
parameters
permissions
public-sharing
queries
query-processor
search
settings
util
warehouse-schema}}
dashboards-rest
{:team "UX West"
:api #{metabase.dashboards-rest.api}
metabase.dashboards.schema}
:uses #{actions
analytics
api
app-db
audit-app
channel
collections
collections-rest
dashboards
config
eid-translation
embedding
events
@@ -472,7 +417,10 @@
query-processor
request
revisions
search
settings
util
warehouse-schema
xrays}}
database-routing
@@ -558,7 +506,8 @@
embedding
{:team "Embedding"
:api #{metabase.embedding.init
:api #{metabase.embedding.api
metabase.embedding.init
metabase.embedding.jwt
metabase.embedding.settings
metabase.embedding.util
@@ -566,26 +515,19 @@
:uses #{analytics
api
config
premium-features
server
settings
util}}
embedding-rest
{:team "Embedding"
:api #{metabase.embedding-rest.api}
:uses #{api
dashboards
eid-translation
embedding
events
models
notification
parameters
public-sharing-rest
premium-features
public-sharing
queries
query-processor
request
server
settings
tiles
util}}
@@ -615,10 +557,9 @@
util}}
glossary
{:team "Gadget"
{:team "Workflows"
:api #{metabase.glossary.api}
:uses #{api
events
models
util}}
@@ -643,11 +584,6 @@
task
util}}
initialization-status
{:team "UX West"
:api #{metabase.initialization-status.core}
:uses #{}}
internal-stats
{:team "UX West"
:api #{metabase.internal-stats.core}
@@ -668,7 +604,6 @@
:api #{metabase.lib.binning
metabase.lib.binning.util
metabase.lib.card
metabase.lib.computed ; temporarily so that `qp.compile` can use it as lib memoization evolves
metabase.lib.core
metabase.lib.equality
metabase.lib.field
@@ -805,7 +740,7 @@
util}}
notification
{:team "Gadget"
{:team "Workflows"
:api #{metabase.notification.api
metabase.notification.core
metabase.notification.init
@@ -814,7 +749,6 @@
:uses #{analytics
api
appearance
auth-identity
channel
config
dashboards
@@ -860,9 +794,9 @@
permissions
{:team "UX West"
:api #{metabase.permissions.core
metabase.permissions.init
metabase.permissions.schema}
:api #{metabase.permissions.api
metabase.permissions.core
metabase.permissions.init}
:uses #{api
app-db
audit-app
@@ -873,21 +807,11 @@
models
premium-features
remote-sync
request
settings
util
enterprise/advanced-permissions}}
permissions-rest
{:team "UX West"
:api #{metabase.permissions-rest.api}
:uses #{api
audit-app
events
permissions
premium-features
request
util}}
pivot
{:team "UX West"
:api #{metabase.pivot.core}
@@ -931,34 +855,27 @@
public-sharing
{:team "UX West"
:api #{metabase.public-sharing.core
:api #{metabase.public-sharing.api
metabase.public-sharing.core
metabase.public-sharing.init
metabase.public-sharing.validation}
:uses #{api
settings
util}}
public-sharing-rest
{:team "UX West"
:api #{metabase.public-sharing-rest.api}
:uses #{actions
analytics
api
dashboards
dashboards-rest
events
lib
models
parameters
public-sharing
queries
query-processor
request
settings
tiles
util}}
pulse
{:team "Gadget"
{:team "Workflows"
:api #{metabase.pulse.api
metabase.pulse.core
metabase.pulse.init}
@@ -1133,7 +1050,6 @@
config
dashboards
events
lib
models
queries
query-permissions
@@ -1215,9 +1131,9 @@
app-db
appearance
config
core
driver
embedding
initialization-status
lib-be
premium-features
query-processor
@@ -1233,62 +1149,53 @@
:api #{metabase.session.api
metabase.session.core
metabase.session.init
metabase.session.settings} ; TODO -- shouldn't be exposed as an API namespace
metabase.session.models.session ; TODO -- shouldn't be exposed as an API namespace
metabase.session.settings} ; TODO -- shouldn't be exposed as an API namespace
:uses #{api
app-db
auth-identity
channel
config
events
login-history
request
settings
sso
system
task
users
util}}
settings
{:team "DevEx"
:api #{metabase.settings.core
:api #{metabase.settings.api
metabase.settings.core
metabase.settings.init}
:uses #{api
app-db
config
events
models
permissions
premium-features
util
enterprise/advanced-permissions}}
settings-rest
{:team "DevEx"
:api #{metabase.settings-rest.api}
:uses #{api
permissions
settings
util}}
setup
{:team "UX West"
:api #{metabase.setup.core
:api #{metabase.setup.api
metabase.setup.core
metabase.setup.init}
:uses #{app-db
config
settings
util}}
setup-rest
{:team "UX West"
:api #{metabase.setup-rest.api}
:uses #{analytics
api
app-db
appearance
auth-identity
config
events
request
session
settings
setup
system
users
util}}
sso
@@ -1297,11 +1204,11 @@
metabase.sso.core
metabase.sso.init}
:uses #{api
auth-identity
config
permissions
premium-features
settings
users
util}}
startup
@@ -1330,7 +1237,6 @@
app-db
audit-app
config
database-routing
driver
events
lib
@@ -1446,31 +1352,17 @@
users
{:team "UX West"
:api #{metabase.users.init
:api #{metabase.users.api
metabase.users.core
metabase.users.init
metabase.users.models.user
metabase.users.models.user-parameter-value
metabase.users.schema
metabase.users.settings}
:uses #{api
batch-processing
channel
config
events
models
permissions
premium-features
settings
setup
system
util}}
users-rest
{:team "UX West"
:api #{metabase.users-rest.api}
metabase.users.schema}
:uses #{analytics
api
appearance
auth-identity
batch-processing
channel
collections
config
events
@@ -1478,8 +1370,11 @@
permissions
premium-features
request
session
settings
setup
sso
users
system
util
enterprise/advanced-permissions}}
@@ -1518,7 +1413,8 @@
warehouse-schema
{:team "UX West"
:api #{metabase.warehouse-schema.field
:api #{metabase.warehouse-schema.api
metabase.warehouse-schema.field
metabase.warehouse-schema.metadata-from-qp
metabase.warehouse-schema.metadata-queries
metabase.warehouse-schema.models.field
@@ -1526,11 +1422,14 @@
metabase.warehouse-schema.models.field-values
metabase.warehouse-schema.models.table
metabase.warehouse-schema.table}
:uses #{analyze
:uses #{analytics
analyze
api
app-db
audit-app
database-routing
driver
events
lib
lib-be
models
@@ -1538,58 +1437,25 @@
permissions
premium-features
query-processor
search
util
warehouses}}
warehouse-schema-rest
{:team "UX West"
:api #{metabase.warehouse-schema-rest.api}
:uses #{analytics
api
app-db
database-routing
driver
events
lib
models
parameters
query-processor
request
search
sync
types
upload
util
warehouse-schema
warehouses-rest
warehouses
xrays}}
warehouses
{:team "UX West"
:api #{metabase.warehouses.core
:api #{metabase.warehouses.api
metabase.warehouses.core
metabase.warehouses.init
metabase.warehouses.models.database}
:uses #{analytics
api
app-db
audit-app
driver
lib
models
permissions
premium-features
search
secrets
settings
sync
util}}
warehouses-rest
{:team "UX West"
:api #{metabase.warehouses-rest.api}
:uses #{analytics
api
app-db
classloader
collections
config
@@ -1604,16 +1470,15 @@
queries
request
sample-data
search
secrets
settings
sync
upload
util
warehouse-schema
warehouses
enterprise/advanced-permissions}}
xrays
{:team "Querying"
:api #{metabase.xrays.api
@@ -1638,13 +1503,12 @@
warehouse-schema}}
enterprise/action-v2
{:team "Gadget"
{:team "Workflows"
:api #{metabase-enterprise.action-v2.api
metabase-enterprise.action-v2.init}
:uses #{actions
api
driver
events
lib
models
query-processor
@@ -1696,8 +1560,6 @@
:api #{metabase-enterprise.ai-sql-fixer.api}
:uses #{api
driver
lib
lib-be
query-processor
util
enterprise/metabot-v3}}
@@ -1722,14 +1584,8 @@
enterprise/api
{:team "UX West"
:api #{metabase-enterprise.api.core}
:uses #{api
premium-features
util}}
enterprise/api-routes
{:team "UX West"
:api #{metabase-enterprise.api-routes.routes}
:api #{metabase-enterprise.api.routes
metabase-enterprise.api.routes.common}
:uses :any}
enterprise/audit-app
@@ -1751,15 +1607,10 @@
query-processor
settings
sync
users-rest
users
util
enterprise/serialization}}
enterprise/auth-identity
{:team "UX West"
:api #{}
:uses #{premium-features}}
enterprise/auth-provider
{:team "UX West"
:api #{}
@@ -1770,9 +1621,7 @@
{:team "UX West"
:api #{metabase-enterprise.billing.api.routes}
:uses #{api
premium-features
store-api
util}}
premium-features util}}
enterprise/bookmarks
{:team "UX West"
@@ -1794,12 +1643,11 @@
:uses #{api
events
premium-features
store-api
util
enterprise/harbormaster}}
enterprise/comments
{:team "UX West"
{:team "Workflows"
:api #{metabase-enterprise.comments.api
metabase-enterprise.comments.init}
:uses #{analytics
@@ -1809,7 +1657,6 @@
models
request
users
users-rest
util}}
enterprise/content-translation
@@ -1836,7 +1683,7 @@
:uses :any}
enterprise/dashboard-subscription-filters
{:team "Querying"
{:team "Workflows"
:api #{}
:uses #{premium-features}}
@@ -1866,7 +1713,7 @@
settings
util
warehouse-schema
warehouses-rest}}
warehouses}}
enterprise/dependencies
{:team "Querying"
@@ -1884,7 +1731,6 @@
lib-be
models
native-query-snippets
permissions
premium-features
queries
query-processor
@@ -1908,10 +1754,8 @@
events
models
premium-features
public-sharing
queries
query-permissions
query-processor
revisions
search
util
@@ -1995,8 +1839,7 @@
metabase-enterprise.metabot-v3.core
metabase-enterprise.metabot-v3.init
metabase-enterprise.metabot-v3.tools.api}
:uses #{activity-feed
api
:uses #{api
app-db
audit-app
collections
@@ -2019,10 +1862,10 @@
sync
system
task
users-rest
users
util
warehouse-schema
warehouses-rest
warehouses
enterprise/dependencies
enterprise/documents
enterprise/transforms
@@ -2044,17 +1887,6 @@
:uses #{premium-features
util}}
enterprise/public-sharing
{:team "UX West"
:api #{metabase-enterprise.public-sharing.api}
:uses #{api
events
premium-features
public-sharing
public-sharing-rest
query-processor
util}}
enterprise/remote-sync
{:team "UX West"
:api #{metabase-enterprise.remote-sync.api
@@ -2068,8 +1900,8 @@
models
premium-features
settings
startup
task
task-history
util
enterprise/serialization}}
@@ -2140,7 +1972,7 @@
enterprise/metabot-v3}}
enterprise/serialization
{:team "Querying"
{:team "Workflows"
:api #{metabase-enterprise.serialization.api
metabase-enterprise.serialization.cmd
metabase-enterprise.serialization.core
@@ -2174,9 +2006,10 @@
metabase-enterprise.sso.settings}
:uses #{api
appearance
auth-identity
channel
embedding
events
notification
premium-features
request
session
@@ -2193,7 +2026,6 @@
:uses #{analytics
api
collections
collections-rest
embedding
premium-features
queries
@@ -2201,19 +2033,6 @@
settings
util}}
enterprise/support-access-grants
{:team "UX West"
:api #{metabase-enterprise.support-access-grants.api
metabase-enterprise.support-access-grants.init}
:uses #{api
auth-identity
events
models
request
settings
system
util}}
enterprise/transforms
{:team "Querying"
:api #{metabase-enterprise.transforms.api

View File

@@ -100,7 +100,7 @@
metabase.permissions.models.permissions/update-data-perms-graph!
metabase.permissions.models.permissions/update-group-permissions!
metabase.permissions.test-util/with-restored-perms!
metabase.public-sharing-rest.api-test/add-card-to-dashboard!
metabase.public-sharing.api-test/add-card-to-dashboard!
metabase.pulse.send/send-notifications!
metabase.pulse.send/send-pulse!
metabase.query-processor.streaming.interface/begin!

View File

@@ -1,4 +1,4 @@
(ns macros.metabase.collections-rest.api-test)
(ns macros.metabase.collections.api-test)
(defmacro with-collection-hierarchy! [bindings & body]
`(let [~'a nil

View File

@@ -1,4 +1,4 @@
(ns macros.metabase.embedding-rest.api.embed-test)
(ns macros.metabase.embedding.api.embed-test)
(defmacro with-embedding-enabled-and-temp-card-referencing!
[table-kw field-kw [card-binding] & body]

View File

@@ -1,4 +1,4 @@
(ns macros.metabase.public-sharing-rest.api-test)
(ns macros.metabase.public-sharing.api-test)
(defmacro with-sharing-enabled-and-temp-card-referencing!
[table-kw field-kw [card-binding] & body]

View File

@@ -1,67 +1,7 @@
(ns hooks.metabase.api.macros
(:require
[clj-kondo.hooks-api :as api]
[clojure.string :as str]))
(defn- validate-route!
"Make sure the route uses kebab-case rather than snake_case."
[route]
(letfn [(validate-route-string! [route-string]
(when (and (api/string-node? route-string)
(str/includes? (api/sexpr route-string) "_"))
(api/reg-finding! (assoc (meta route-string)
:message "REST API routes and query params should use kebab-case, not snake_case"
:type :metabase/validate-defendpoint-route-uses-kebab-case))))]
(cond
(api/string-node? route)
(validate-route-string! route)
;; otherwise route is a vector like ["/abc/:d/" :d <regex>]
(api/vector-node? route)
(validate-route-string! (first (:children route))))))
(defn- validate-query-params!
"Make sure query params use kebab-case rather than snake_case.
This only works on inline schemas for now. If you define the schema elsewhere then we'd have to implement this
validation in Clojure-land since we can't resolve the schemas in Kondo. It should however still work on complicated
schemas like
[:merge <map-1> <map-2>]"
[query-param-schema-node]
(letfn [(map-schema? [node]
(and (api/vector-node? node)
(when-let [first-child (first (:children node))]
(and (api/keyword-node? first-child)
(= (api/sexpr first-child) :map)))))
(map-schema-keys [{:keys [children], :as _node}]
(let [child-schemas (if (api/map-node? (second children))
(drop 2 children)
(drop 1 children))]
(keep (fn [child-schema]
(when (api/vector-node? child-schema)
(when-let [k (first (:children child-schema))]
(when (api/keyword-node? k)
k))))
child-schemas)))
(validate-map-schema! [form]
;; only warn once per endpoint -- just once on the first bad node
(when-let [bad-node (some (fn [key-node]
(when (str/includes? (str (api/sexpr key-node)) "_")
key-node))
(map-schema-keys form))]
(api/reg-finding! (assoc (meta bad-node)
:message "REST API routes and query params should use kebab-case, not snake_case"
:type :metabase/validate-defendpoint-query-params-use-kebab-case))))
(validate! [node]
(cond
(and (api/vector-node? node)
(map-schema? node))
(validate-map-schema! node)
(api/vector-node? node)
(doseq [child (:children node)]
(validate! child))))]
(validate! query-param-schema-node)))
[hooks.common]))
(defn defendpoint
[arg]
@@ -78,21 +18,16 @@
(cons nil more))
[params & body] more
[bindings schemas] (when (api/vector-node? params)
(loop [bindings [], schemas [], [x y z & more] (:children params)]
(loop [bindings [], schemas [], [x y & more] (:children params)]
(cond
(not x)
[bindings schemas]
(and (api/keyword-node? y)
(= (api/sexpr y) :-))
(recur (conj bindings x) (conj schemas z) more)
(= (api/sexpr x) :-)
(recur bindings (conj schemas y) more)
:else
(recur (conj bindings x) (conj schemas nil) (list* y z more)))))]
(validate-route! route)
(let [[_route-param-schema query-param-schema _body-param-schema] schemas]
(when query-param-schema
(validate-query-params! query-param-schema)))
(recur (conj bindings x) schemas (cons y more)))))]
(-> (api/list-node
(list
(api/token-node 'do)
@@ -119,27 +54,3 @@
body))))
(with-meta (meta node)))))]
(update arg :node update-defendpoint)))
(comment
(defn -defendpoint [form]
(-> {:node (-> form pr-str api/parse-string)}
defendpoint
:node
api/sexpr))
(defn x []
(-defendpoint '(api.macros/defendpoint :get "/:id/syncable_schemas" ; snake_case route should trigger a warning
"Returns a list of all syncable schemas found for the database `id`."
[{:keys [id]} :- [:map
[:id ms/PositiveInt]]
;; snake_case query parameter, should trigger a warning
{:keys [filter_by]} :- [:map
[:filter_by {:optional true} :string]]]
(let [db (get-database id)]
(api/check-403 (or (:is_attached_dwh db)
(and (mi/can-write? db)
(mi/can-read? db))))
(->> db
(driver/syncable-schemas (:engine db))
(vec)
(sort)))))))

View File

@@ -33,36 +33,18 @@
(defclause [ag:var var] field-or-expression FieldOrExpressionDef)
=>
(def ag:var [FieldOrExpressionDef])"
[{:keys [node], :as x}]
(let [[_defclause clause-name & arg-specs] (:children node)]
(assoc x :node (-> (hooks/list-node
(list
(hooks/token-node 'do)
(-> (hooks/keyword-node (keyword "metabase.legacy-mbql.schema" (name (hooks/sexpr clause-name))))
(with-meta (meta clause-name))
(hooks/reg-keyword! 'metabase.legacy-mbql.schemas.macros/defclause))
(collect-defclause-body-schemas arg-specs)))
(with-meta (meta node))))))
(defn defclause*
"e.g.
(defclause [ag:var var] field-or-expression FieldOrExpressionDef)
=>
(def ag:var [FieldOrExpressionDef])"
[{:keys [node], :as x}]
(let [[_defclause clause-name schema] (:children node)]
(assoc x :node (-> (hooks/list-node
(list
(hooks/token-node 'do)
(-> (hooks/keyword-node (keyword "metabase.legacy-mbql.schema" (name (hooks/sexpr clause-name))))
(with-meta (meta clause-name))
(hooks/reg-keyword! 'metabase.legacy-mbql.schemas.macros/defclause*))
schema))
(with-meta (meta node))))))
[{:keys [node]}]
(let [[_defclause clause-name-form & arg-specs] (:children node)]
{:node (-> (hooks/list-node
(list
(hooks/token-node 'def)
(unwrap-defclause-clause-name clause-name-form)
(hooks/string-node "Docstring.")
(collect-defclause-body-schemas arg-specs)))
(with-meta (meta node)))}))
(comment
(defn- expand-defclause [form]
(defn- defclause* [form]
(hooks/sexpr
(:node
(defclause
@@ -73,35 +55,15 @@
form)))}))))
(defn- x []
(expand-defclause '(defclause var
field-or-expression FieldOrExpressionDef)))
(defclause* '(defclause [ag:var var]
field-or-expression FieldOrExpressionDef)))
;; =>
(do :metabase.legacy-mbql.schema/var [FieldOrExpressionDef])
(def ag:var "Docstring." [FieldOrExpressionDef])
(defn- y []
(expand-defclause '(defclause var
x (some-list-schema)
y (optional FieldOrExpressionDef)
z (rest (some-other-schema)))))
(defclause* '(defclause var
x (some-list-schema)
y (optional FieldOrExpressionDef)
z (rest (some-other-schema)))))
;; =>
(do :metabase.legacy-mbql.schema/var [(some-list-schema) FieldOrExpressionDef (some-other-schema)]))
(defn one-of [{:keys [node], :as x}]
(let [[_one-of & schema-nodes] (:children node)
node' (-> (hooks/vector-node
(mapv (fn [schema-node]
(if (and (hooks/token-node? schema-node)
(simple-symbol? (hooks/sexpr schema-node)))
(-> (hooks/keyword-node (keyword "mteabase.legacy-mbql.schema" (name (hooks/sexpr schema-node))))
(with-meta (meta schema-node)))
schema-node))
schema-nodes))
(with-meta (meta node)))]
(assoc x :node node')))
(comment
(defn- expand-one-of [form]
(hooks/sexpr (:node (one-of {:node (hooks/parse-string (with-out-str (clojure.pprint/pprint form)))}))))
(defn- z []
(expand-one-of '(one-of absolute-datetime relative-datetime time))))
(def var "Docstring." [(some-list-schema) FieldOrExpressionDef (some-other-schema)]))

View File

@@ -1,7 +1,6 @@
(ns hooks.metabase.api.macros-test
(:require
[clj-kondo.hooks-api :as api]
[clj-kondo.impl.utils]
[clojure.test :refer :all]
[hooks.metabase.api.macros]))
@@ -70,41 +69,3 @@
(respond :x)
(catch Throwable e
(raise e))))))))
(defn- defendpoint [form]
(hooks.metabase.api.macros/defendpoint
{:node (-> form pr-str api/parse-string)}))
(defn- defendpoint-warnings
[form]
(binding [clj-kondo.impl.utils/*ctx* {:config {:linters {:metabase/validate-defendpoint-route-uses-kebab-case {:level :warning}
:metabase/validate-defendpoint-query-params-use-kebab-case {:level :warning}}}
:ignores (atom nil)
:findings (atom [])
:namespaces (atom {})}]
(defendpoint form)
@(:findings clj-kondo.impl.utils/*ctx*)))
(deftest ^:parallel validate-route-kebab-case-test
(is (=? [{:message "REST API routes and query params should use kebab-case, not snake_case"
:type :metabase/validate-defendpoint-route-uses-kebab-case
:row pos-int?
:col pos-int?}]
(defendpoint-warnings
'(api.macros/defendpoint :get "/:id/syncable_schemas" ; snake_case route should trigger a warning
"Returns a list of all syncable schemas found for the database `id`."
[{:keys [id]}]
id)))))
(deftest ^:parallel validate-query-params-kebab-case-test
(is (=? [{:message "REST API routes and query params should use kebab-case, not snake_case",
:type :metabase/validate-defendpoint-query-params-use-kebab-case
:row pos-int?
:col pos-int?}]
(defendpoint-warnings
'(api.macros/defendpoint :get "/:id/syncable-schemas"
"Returns a list of all syncable schemas found for the database `id`."
[{:keys [id]}
{:keys [filter_by]} :- [:map
[:filter_by {:optional true} :string]]]
id)))))

View File

@@ -38,321 +38,321 @@
;; zero get indented 2 spaces if not on the first line regardless of number of elements on the first line.
:extra-indents
{;; clojure.core stuff
fn* [[:inner 0]]
let* [[:block 1]]
with-meta [[:default]]
fn* [[:inner 0]]
let* [[:block 1]]
with-meta [[:default]]
;; library stuff. Libraries that specify their own `:style/indent` specs are included in the section after this
;; since it is generated automatically.
clojure.core.logic/fresh [[:block 1]]
clojure.core.logic/matcha [[:block 1]]
clojure.core.logic/run [[:block 2]]
clojure.core.match/match [[:block 1]]
clojure.test.check.properties/for-all [[:block 1]]
clojure.test.check.properties/for-all* [[:block 1]]
clojure.test.check/quick-check [[:block 1]]
clojure.tools.macro/macrolet [[:block 1] [:inner 2 0]]
clojurewerkz.quartzite.jobs/build [[:default]]
clojurewerkz.quartzite.jobs/with-description [[:default]]
clojurewerkz.quartzite.jobs/with-identity [[:default]]
clojurewerkz.quartzite.schedule.cron/schedule [[:default]]
clojurewerkz.quartzite.schedule.cron/with-misfire-handling-instruction-do-nothing [[:default]]
clojurewerkz.quartzite.triggers/build [[:default]]
clojurewerkz.quartzite.triggers/with-description [[:default]]
clojurewerkz.quartzite.triggers/with-identity [[:default]]
clojurewerkz.quartzite.triggers/with-priority [[:default]]
clojurewerkz.quartzite.triggers/with-schedule [[:default]]
def-map-type [[:block 2] [:inner 1]]
defprotocol+ [[:block 1] [:inner 1]]
defrecord+ [[:block 2] [:inner 1]]
deftype+ [[:block 2] [:inner 1]]
net.cgrand.macrovich/case [[:block 0]]
potemkin.namespaces/import-vars [[:default]]
potemkin.types/def-map-type [[:block 2] [:inner 1]]
potemkin.types/defprotocol+ [[:block 1] [:inner 1]]
potemkin.types/defrecord+ [[:block 2] [:inner 1]]
potemkin.types/deftype+ [[:block 2] [:inner 1]]
potemkin/def-map-type [[:block 2] [:inner 1]]
potemkin/defprotocol+ [[:block 1] [:inner 1]]
potemkin/defrecord+ [[:block 2] [:inner 1]]
potemkin/deftype+ [[:block 2] [:inner 1]]
potemkin/import-vars [[:default]]
taoensso.nippy/extend-freeze [[:inner 0]]
taoensso.nippy/extend-thaw [[:inner 0]]
throttle.core/with-throttling [[:block 1]]
clojure.core.logic/fresh [[:block 1]]
clojure.core.logic/matcha [[:block 1]]
clojure.core.logic/run [[:block 2]]
clojure.core.match/match [[:block 1]]
clojure.test.check.properties/for-all [[:block 1]]
clojure.test.check.properties/for-all* [[:block 1]]
clojure.test.check/quick-check [[:block 1]]
clojure.tools.macro/macrolet [[:block 1] [:inner 2 0]]
clojurewerkz.quartzite.jobs/build [[:default]]
clojurewerkz.quartzite.jobs/with-description [[:default]]
clojurewerkz.quartzite.jobs/with-identity [[:default]]
clojurewerkz.quartzite.schedule.cron/schedule [[:default]]
clojurewerkz.quartzite.schedule.cron/with-misfire-handling-instruction-do-nothing [[:default]]
clojurewerkz.quartzite.triggers/build [[:default]]
clojurewerkz.quartzite.triggers/with-description [[:default]]
clojurewerkz.quartzite.triggers/with-identity [[:default]]
clojurewerkz.quartzite.triggers/with-priority [[:default]]
clojurewerkz.quartzite.triggers/with-schedule [[:default]]
def-map-type [[:block 2] [:inner 1]]
defprotocol+ [[:block 1] [:inner 1]]
defrecord+ [[:block 2] [:inner 1]]
deftype+ [[:block 2] [:inner 1]]
net.cgrand.macrovich/case [[:block 0]]
potemkin.namespaces/import-vars [[:default]]
potemkin.types/def-map-type [[:block 2] [:inner 1]]
potemkin.types/defprotocol+ [[:block 1] [:inner 1]]
potemkin.types/defrecord+ [[:block 2] [:inner 1]]
potemkin.types/deftype+ [[:block 2] [:inner 1]]
potemkin/def-map-type [[:block 2] [:inner 1]]
potemkin/defprotocol+ [[:block 1] [:inner 1]]
potemkin/defrecord+ [[:block 2] [:inner 1]]
potemkin/deftype+ [[:block 2] [:inner 1]]
potemkin/import-vars [[:default]]
taoensso.nippy/extend-freeze [[:inner 0]]
taoensso.nippy/extend-thaw [[:inner 0]]
throttle.core/with-throttling [[:block 1]]
;; the rest of the stuff in this list is generated automatically based on `:style/indent` specs
;; by running [[dev.generate-cljfmt-config/specs]]... use that code to update this list
metabase-enterprise.cache.config-test/with-temp-persist-models [[:block 1]]
metabase-enterprise.impersonation.util-test/with-impersonations! [[:inner 0]]
metabase-enterprise.sandbox.test-util/with-gtaps! [[:inner 0]]
metabase-enterprise.sandbox.test-util/with-gtaps-for-user! [[:inner 0]]
metabase-enterprise.sandbox.test-util/with-user-attributes [[:block 2]]
metabase-enterprise.serialization.test-util/with-dbs [[:inner 0]]
metabase-enterprise.serialization.test-util/with-random-dump-dir [[:block 1]]
metabase-enterprise.serialization.test-util/with-world [[:block 0]]
metabase-enterprise.sso.integrations.jwt-test/with-users-with-email-deleted [[:block 1]]
metabase-enterprise.test/with-gtaps! [[:inner 0]]
metabase-enterprise.test/with-gtaps-for-user! [[:inner 0]]
metabase-enterprise.test/with-user-attributes [[:block 2]]
metabase.actions.actions-test/with-actions-test-data-and-actions-permissively-enabled [[:block 0]]
metabase.actions.test-util/with-actions [[:block 1]]
metabase.actions.test-util/with-actions-disabled [[:block 0]]
metabase.actions.test-util/with-actions-enabled [[:block 0]]
metabase.actions.test-util/with-actions-test-data [[:inner 0]]
metabase.actions.test-util/with-actions-test-data-and-actions-enabled [[:block 0]]
metabase.actions.test-util/with-actions-test-data-tables [[:block 1]]
metabase.actions.test-util/with-empty-db [[:inner 0]]
metabase.actions.test-util/with-temp-test-data [[:inner 0]]
metabase.api.common/check [[:default]]
metabase.api.common/define-routes [[:block 0]]
metabase.api.common/let-404 [[:block 1]]
metabase.app-db.core/with-application-db [[:inner 0]]
metabase.app-db.liquibase/with-liquibase [[:block 1]]
metabase.app-db.liquibase/with-scope-locked [[:block 1]]
metabase.app-db.schema-migrations-test.impl/test-migrations [[:block 2]]
metabase.cmd.copy/step [[:block 1]]
metabase.cmd.copy/with-connection-rollback-only [[:block 1]]
metabase.cmd.copy/with-disabled-db-constraints [[:block 2]]
metabase.collections-rest.api-test/with-collection-hierarchy [[:block 1]]
metabase.collections-rest.api-test/with-french-user-and-personal-collection [[:block 2]]
metabase.collections-rest.api-test/with-some-children-of-collection [[:block 1]]
metabase.collections.models.collection-test/with-collection-hierarchy [[:block 1]]
metabase.collections.models.collection-test/with-current-user-perms-for-collections [[:block 1]]
metabase.collections.models.collection-test/with-personal-and-impersonal-collections [[:block 1]]
metabase.config/build-type-case [[:block 0]]
metabase.dashboards.models.dashboard-test/with-dash-in-collection [[:inner 0]]
metabase.driver-api.core/match [[:inner 0]]
metabase.driver-api.core/match-one [[:inner 0]]
metabase.driver-api.core/replace [[:inner 0]]
metabase.driver-api.core/replace-in [[:inner 0]]
metabase.driver.bigquery-cloud-sdk.query-processor/with-temporal-type [[:default]]
metabase.driver.druid.client/DELETE [[:default]]
metabase.driver.druid.client/GET [[:default]]
metabase.driver.druid.client/POST [[:default]]
metabase.driver.druid.query-processor-test/druid-query [[:block 0]]
metabase.driver.druid.query-processor-test/druid-query-returning-rows [[:block 0]]
metabase.driver.mongo.query-processor/mongo-let [[:block 1]]
metabase.driver.sql-jdbc.actions/with-jdbc-transaction [[:block 1]]
metabase.driver.sql-jdbc.connection/with-connection-spec-for-testing-connection [[:block 1]]
metabase.driver.sql-jdbc.execute.diagnostic/capturing-diagnostic-info [[:block 1]]
metabase.driver.sql.query-processor/apply-top-level-clause [[:default]]
metabase.driver/with-driver [[:block 1]]
metabase.embedding-rest.api.embed-test/do-response-formats [[:block 1]]
metabase.embedding-rest.api.embed-test/with-embedding-enabled-and-new-secret-key [[:block 0]]
metabase.embedding-rest.api.embed-test/with-embedding-enabled-and-temp-card-referencing [[:block 3]]
metabase.embedding-rest.api.embed-test/with-embedding-enabled-and-temp-dashcard-referencing [[:block 3]]
metabase.embedding-rest.api.embed-test/with-new-secret-key [[:block 0]]
metabase.embedding-rest.api.embed-test/with-temp-card [[:block 1]]
metabase.embedding-rest.api.embed-test/with-temp-dashcard [[:block 1]]
metabase.embedding-rest.api.preview-embed-test/with-embedding-enabled-and-new-secret-key [[:block 0]]
metabase.embedding-rest.api.preview-embed-test/with-new-secret-key [[:block 0]]
metabase.lib.binning/with-binning [[:default]]
metabase.lib.core/with-binning [[:default]]
metabase.lib.core/with-join-alias [[:default]]
metabase.lib.core/with-join-conditions [[:default]]
metabase.lib.join/with-join-alias [[:default]]
metabase.lib.join/with-join-conditions [[:default]]
metabase.lib.test-util.macros/$ids [[:inner 0]]
metabase.lib.test-util.macros/mbql-5-query [[:inner 0]]
metabase.lib.test-util.macros/mbql-query [[:inner 0]]
metabase.lib.util.match/match [[:inner 0]]
metabase.lib.util.match/match-one [[:inner 0]]
metabase.lib.util.match/match-lite [[:inner 0]]
metabase.lib.util.match/match-lite-recursive [[:inner 0]]
metabase.lib.util.match/replace [[:inner 0]]
metabase.lib.util.match/replace-in [[:inner 0]]
metabase.models.interface/define-batched-hydration-method [[:inner 0]]
metabase.models.interface/define-simple-hydration-method [[:inner 0]]
metabase.parameters.chain-filter-test/with-clean-field-values-for-field [[:block 1]]
metabase.parameters.chain-filter-test/with-fk-field-to-field-remapping! [[:block 0]]
metabase.parameters.chain-filter-test/with-human-readable-values-remapping! [[:block 0]]
metabase.permissions.core/allow-changing-all-users-group-members [[:block 0]]
metabase.permissions.core/disable-perms-cache [[:block 0]]
metabase.public-sharing-rest.api-test/with-sharing-enabled-and-temp-card-referencing [[:block 3]]
metabase.public-sharing-rest.api-test/with-sharing-enabled-and-temp-dashcard-referencing [[:block 3]]
metabase.public-sharing-rest.api-test/with-temp-public-card [[:block 1]]
metabase.public-sharing-rest.api-test/with-temp-public-dashboard [[:block 1]]
metabase.public-sharing-rest.api-test/with-temp-public-dashboard-and-card [[:block 1]]
metabase.pulse.api.alert-test/with-alert-in-collection [[:block 1]]
metabase.pulse.dashboard-subscription-test/with-link-card-fixture-for-dashboard [[:block 2]]
metabase.pulse.models.pulse-test/with-dashboard-subscription-in-collection [[:block 1]]
metabase.pulse.models.pulse-test/with-pulse-in-collection [[:inner 0]]
metabase.queries.api.card-test/with-cards-in-readable-collection [[:block 1]]
metabase.queries.api.card-test/with-cards-in-writeable-collection [[:block 1]]
metabase.queries.api.card-test/with-ordered-items [[:inner 0]]
metabase.queries.api.card-test/with-temp-native-card [[:block 1]]
metabase.queries.api.card-test/with-temp-native-card-with-params [[:block 1]]
metabase.query-processor-test.pipeline-queries-test/pmbql-query [[:block 1]]
metabase.query-processor-test.pipeline-queries-test/run-pmbql-query [[:block 1]]
metabase.query-processor.debug/debug> [[:block 0]]
metabase.query-processor.error-type/deferror [[:block 1]]
metabase.query-processor.middleware.cache-backend.interface/with-cached-results [[:block 4]]
metabase.query-processor.middleware.process-userland-query-test/with-query-execution [[:block 1]]
metabase.query-processor.store/cached [[:block 1]]
metabase.query-processor.store/with-metadata-provider [[:inner 0]]
metabase.query-processor.streaming/streaming-response [[:block 1]]
metabase.query-processor.test-util/format-rows-by [[:default]]
metabase.request.core/as-admin [[:block 0]]
metabase.request.core/with-current-request [[:inner 0]]
metabase.request.core/with-current-user [[:inner 0]]
metabase.request.core/with-limit-and-offset [[:inner 0]]
metabase.request.session/as-admin [[:block 0]]
metabase.request.session/with-current-user [[:inner 0]]
metabase.sample-data.impl-test/with-temp-sample-database-db [[:block 1]]
metabase.search.api-test/do-test-users [[:block 1]]
metabase.server.middleware.session/as-admin [[:block 0]]
metabase.server.middleware.session/with-current-user [[:inner 0]]
metabase.server.streaming-response-test/with-streaming-response-thread-pool [[:block 0]]
metabase.server.streaming-response-test/with-test-driver-db! [[:block 0]]
metabase.server.streaming-response/streaming-response [[:block 2]]
metabase.session.api/http-401-on-error [[:block 0]]
metabase.settings.core/define-multi-setting [[:inner 0]]
metabase.settings.core/defsetting [[:block 1]]
metabase.settings.models.setting.multi-setting/define-multi-setting [[:inner 0]]
metabase.settings.models.setting/defsetting [[:block 1]]
metabase.sync.fetch-metadata/log-if-error [[:block 1]]
metabase.sync.sync-metadata.fields-test/with-test-db [[:block 0]]
metabase.sync.util/do-with-start-and-finish-debug-logging [[:default]]
metabase.sync.util/sum-for [[:block 1]]
metabase.sync.util/sync-operation [[:block 3]]
metabase.sync.util/with-db-logging-disabled [[:default]]
metabase.sync.util/with-duplicate-ops-prevented [[:default]]
metabase.sync.util/with-emoji-progress-bar [[:block 1]]
metabase.sync.util/with-error-handling [[:block 1]]
metabase.sync.util/with-start-and-finish-logging [[:default]]
metabase.sync.util/with-start-and-finish-logging* [[:default]]
metabase.sync.util/with-sync-events [[:default]]
metabase.task-history.models.task-history/with-task-history [[:block 1]]
metabase.task.core/rerun-on-error [[:block 1]]
metabase.task.impl/rerun-on-error [[:block 1]]
metabase.test.data.datasets/test-driver [[:inner 0]]
metabase.test.data.datasets/test-drivers [[:inner 0]]
metabase.test.data.one-off-dbs/with-blank-db [[:block 0]]
metabase.test.data.one-off-dbs/with-blueberries-db [[:block 0]]
metabase.test.data.users/with-test-user [[:inner 0]]
metabase.test.data/$ids [[:inner 0]]
metabase.test.data/dataset [[:inner 0]]
metabase.test.data/mbql-query [[:inner 0]]
metabase.test.data/query [[:block 1]]
metabase.test.data/run-mbql-query [[:inner 0]]
metabase.test.data/with-empty-h2-app-db! [[:block 0]]
metabase.test.data/with-temp-copy-of-db [[:block 0]]
metabase.test.util.public-settings/with-additional-premium-features [[:block 1]]
metabase.test.util.public-settings/with-premium-features [[:block 1]]
metabase.test.util.thread-local/test-helpers-set-global-values! [[:block 0]]
metabase.test.util/discard-setting-changes [[:block 1]]
metabase.test.util/let-url [[:block 1]]
metabase.test.util/throw-if-called! [[:block 1]]
metabase.test.util/with-temp-scheduler [[:block 0]]
metabase.test.util/with-temp-vals-in-db [[:block 3]]
metabase.test.util/with-user-in-groups [[:inner 0]]
metabase.test/$ids [[:inner 0]]
metabase.test/as-admin [[:inner 0]]
metabase.test/dataset [[:inner 0]]
metabase.test/discard-setting-changes [[:block 1]]
metabase.test/format-rows-by [[:default]]
metabase.test/let-url [[:block 1]]
metabase.test/mbql-query [[:inner 0]]
metabase.test/query [[:block 1]]
metabase.test/run-mbql-query [[:inner 0]]
metabase.test/test-driver [[:inner 0]]
metabase.test/test-drivers [[:inner 0]]
metabase.test/test-helpers-set-global-values! [[:block 0]]
metabase.test/throw-if-called! [[:block 1]]
metabase.test/with-actions [[:block 1]]
metabase.test/with-actions-disabled [[:block 0]]
metabase.test/with-actions-enabled [[:block 0]]
metabase.test/with-actions-test-data [[:inner 0]]
metabase.test/with-actions-test-data-and-actions-enabled [[:block 0]]
metabase.test/with-actions-test-data-tables [[:block 1]]
metabase.test/with-additional-premium-features [[:block 1]]
metabase.test/with-current-user [[:inner 0]]
metabase.test/with-driver [[:block 1]]
metabase.test/with-empty-db [[:inner 0]]
metabase.test/with-empty-h2-app-db! [[:block 0]]
metabase.test/with-metadata-provider [[:inner 0]]
metabase.test/with-premium-features [[:block 1]]
metabase.test/with-temp [[:inner 0]]
metabase.test/with-temp-copy-of-db [[:block 0]]
metabase.test/with-temp-scheduler [[:block 0]]
metabase.test/with-temp-test-data [[:inner 0]]
metabase.test/with-temp-vals-in-db [[:block 3]]
metabase.test/with-test-user [[:inner 0]]
metabase.test/with-user-in-groups [[:inner 0]]
metabase.timeseries-query-processor-test.util/test-timeseries-drivers [[:block 0]]
metabase.timeseries-query-processor-test/run-mbql-query [[:inner 0]]
metabase.upload.impl-test/with-upload-table! [[:inner 0]]
metabase.upload.types/with-currency [[:default]]
metabase.upload.types/with-parens [[:default]]
metabase.util/for-map [[:block 1]]
metabase.util.async/cancelable-thread [[:block 0]]
metabase.util.date-2/with-time-zone-same-instant [[:default]]
metabase.util.encryption-test/with-secret-key [[:block 1]]
metabase.util.honey-sql-2/with-database-type-info [[:default]]
metabase.util.i18n/deferred-trs [[:default]]
metabase.util.i18n/deferred-tru [[:default]]
metabase.util.i18n/trs [[:default]]
metabase.util.i18n/trsn [[:default]]
metabase.util.i18n/tru [[:default]]
metabase.util.i18n/trun [[:default]]
metabase.util.jvm/auto-retry [[:block 1]]
metabase.util.jvm/varargs [[:block 1]]
metabase.util.jvm/with-us-locale [[:block 0]]
metabase.util.malli/disable-enforcement [[:block 0]]
metabase.util.malli/with [[:default]]
metabase.util.malli/with-api-error-message [[:default]]
metabase.util.namespaces/import-fns [[:default]]
metabase.util.queue-test/await-test-while [[:block 1]]
metabase.util/auto-retry [[:block 1]]
metabase.util/case-enum [[:block 1]]
metabase.util/ignore-exceptions [[:block 0]]
metabase.util/or-with [[:block 1]]
metabase.util/profile [[:block 1]]
metabase.util/prog1 [[:inner 0]]
metabase.util/varargs [[:inner 0]]
metabase.util/with-us-locale [[:block 0]]
metabuild-common.core/exit-when-finished-nonzero-on-exception [[:block 0]]
metabuild-common.core/step [[:block 1]]
metabuild-common.entrypoint/exit-when-finished-nonzero-on-exception [[:block 0]]
metabuild-common.steps/step [[:block 1]]
methodical.core/defmethod [[:inner 0]]
methodical.core/defmulti [[:inner 0]]
methodical.core/with-dispatcher [[:default]]
methodical.core/with-method-table [[:default]]
methodical.core/with-prefers [[:default]]
methodical.interface/with-dispatcher [[:default]]
methodical.interface/with-method-table [[:default]]
methodical.interface/with-prefers [[:default]]
methodical.macros/defmethod [[:inner 0]]
methodical.macros/defmulti [[:inner 0]]
saml20-clj.sp.response/validate-confirmation-datas [[:block 1]]
toucan2.connection/with-transaction [[:block 1]]
toucan2.core/build [[:block 0]]
toucan2.core/compile [[:block 0]]
toucan2.core/debug [[:block 0]]
toucan2.core/define-after-insert [[:inner 0]]
toucan2.core/define-after-select [[:inner 0]]
toucan2.core/define-after-update [[:inner 0]]
toucan2.core/define-before-delete [[:inner 0]]
toucan2.core/define-before-insert [[:inner 0]]
toucan2.core/define-before-select [[:inner 0]]
toucan2.core/define-default-fields [[:inner 0]]
toucan2.core/define-named-query [[:block 1]]
toucan2.core/deftransforms [[:block 1]]
toucan2.core/with-transaction [[:block 1]]
toucan2.tools.after-insert/define-after-insert [[:inner 0]]
toucan2.tools.after-select/define-after-select [[:inner 0]]
toucan2.tools.after-update/define-after-update [[:inner 0]]
toucan2.tools.after/define-after-impl [[:default]]
toucan2.tools.before-delete/define-before-delete [[:inner 0]]
toucan2.tools.before-insert/define-before-insert [[:inner 0]]
toucan2.tools.before-select/define-before-select [[:inner 0]]
toucan2.tools.compile/build [[:block 0]]
toucan2.tools.compile/compile [[:block 0]]
toucan2.tools.compile/resolved [[:block 0]]
toucan2.tools.debug/debug [[:block 0]]
toucan2.tools.default-fields/define-default-fields [[:inner 0]]
toucan2.tools.named-query/define-named-query [[:block 1]]
toucan2.tools.simple-out-transform/define-out-transform [[:inner 0]]
toucan2.tools.transformed/deftransforms [[:block 1]]
toucan2.tools.with-temp/with-temp [[:inner 0]]
toucan2.util/try-with-error-context [[:inner 0]]}}
metabase-enterprise.cache.config-test/with-temp-persist-models [[:block 1]]
metabase-enterprise.impersonation.util-test/with-impersonations! [[:inner 0]]
metabase-enterprise.sandbox.test-util/with-gtaps! [[:inner 0]]
metabase-enterprise.sandbox.test-util/with-gtaps-for-user! [[:inner 0]]
metabase-enterprise.sandbox.test-util/with-user-attributes [[:block 2]]
metabase-enterprise.serialization.test-util/with-dbs [[:inner 0]]
metabase-enterprise.serialization.test-util/with-random-dump-dir [[:block 1]]
metabase-enterprise.serialization.test-util/with-world [[:block 0]]
metabase-enterprise.sso.integrations.jwt-test/with-users-with-email-deleted [[:block 1]]
metabase-enterprise.test/with-gtaps! [[:inner 0]]
metabase-enterprise.test/with-gtaps-for-user! [[:inner 0]]
metabase-enterprise.test/with-user-attributes [[:block 2]]
metabase.actions.actions-test/with-actions-test-data-and-actions-permissively-enabled [[:block 0]]
metabase.actions.test-util/with-actions [[:block 1]]
metabase.actions.test-util/with-actions-disabled [[:block 0]]
metabase.actions.test-util/with-actions-enabled [[:block 0]]
metabase.actions.test-util/with-actions-test-data [[:inner 0]]
metabase.actions.test-util/with-actions-test-data-and-actions-enabled [[:block 0]]
metabase.actions.test-util/with-actions-test-data-tables [[:block 1]]
metabase.actions.test-util/with-empty-db [[:inner 0]]
metabase.actions.test-util/with-temp-test-data [[:inner 0]]
metabase.api.common/check [[:default]]
metabase.api.common/define-routes [[:block 0]]
metabase.api.common/let-404 [[:block 1]]
metabase.app-db.core/with-application-db [[:inner 0]]
metabase.app-db.liquibase/with-liquibase [[:block 1]]
metabase.app-db.liquibase/with-scope-locked [[:block 1]]
metabase.app-db.schema-migrations-test.impl/test-migrations [[:block 2]]
metabase.cmd.copy/step [[:block 1]]
metabase.cmd.copy/with-connection-rollback-only [[:block 1]]
metabase.cmd.copy/with-disabled-db-constraints [[:block 2]]
metabase.collections.api-test/with-collection-hierarchy [[:block 1]]
metabase.collections.api-test/with-french-user-and-personal-collection [[:block 2]]
metabase.collections.api-test/with-some-children-of-collection [[:block 1]]
metabase.collections.models.collection-test/with-collection-hierarchy [[:block 1]]
metabase.collections.models.collection-test/with-current-user-perms-for-collections [[:block 1]]
metabase.collections.models.collection-test/with-personal-and-impersonal-collections [[:block 1]]
metabase.config/build-type-case [[:block 0]]
metabase.dashboards.models.dashboard-test/with-dash-in-collection [[:inner 0]]
metabase.driver-api.core/match [[:inner 0]]
metabase.driver-api.core/match-one [[:inner 0]]
metabase.driver-api.core/replace [[:inner 0]]
metabase.driver-api.core/replace-in [[:inner 0]]
metabase.driver.bigquery-cloud-sdk.query-processor/with-temporal-type [[:default]]
metabase.driver.druid.client/DELETE [[:default]]
metabase.driver.druid.client/GET [[:default]]
metabase.driver.druid.client/POST [[:default]]
metabase.driver.druid.query-processor-test/druid-query [[:block 0]]
metabase.driver.druid.query-processor-test/druid-query-returning-rows [[:block 0]]
metabase.driver.mongo.query-processor/mongo-let [[:block 1]]
metabase.driver.sql-jdbc.actions/with-jdbc-transaction [[:block 1]]
metabase.driver.sql-jdbc.connection/with-connection-spec-for-testing-connection [[:block 1]]
metabase.driver.sql-jdbc.execute.diagnostic/capturing-diagnostic-info [[:block 1]]
metabase.driver.sql.query-processor/apply-top-level-clause [[:default]]
metabase.driver/with-driver [[:block 1]]
metabase.embedding.api.embed-test/do-response-formats [[:block 1]]
metabase.embedding.api.embed-test/with-embedding-enabled-and-new-secret-key [[:block 0]]
metabase.embedding.api.embed-test/with-embedding-enabled-and-temp-card-referencing [[:block 3]]
metabase.embedding.api.embed-test/with-embedding-enabled-and-temp-dashcard-referencing [[:block 3]]
metabase.embedding.api.embed-test/with-new-secret-key [[:block 0]]
metabase.embedding.api.embed-test/with-temp-card [[:block 1]]
metabase.embedding.api.embed-test/with-temp-dashcard [[:block 1]]
metabase.embedding.api.preview-embed-test/with-embedding-enabled-and-new-secret-key [[:block 0]]
metabase.embedding.api.preview-embed-test/with-new-secret-key [[:block 0]]
metabase.lib.binning/with-binning [[:default]]
metabase.lib.core/with-binning [[:default]]
metabase.lib.core/with-join-alias [[:default]]
metabase.lib.core/with-join-conditions [[:default]]
metabase.lib.join/with-join-alias [[:default]]
metabase.lib.join/with-join-conditions [[:default]]
metabase.lib.test-util.macros/$ids [[:inner 0]]
metabase.lib.test-util.macros/mbql-5-query [[:inner 0]]
metabase.lib.test-util.macros/mbql-query [[:inner 0]]
metabase.lib.util.match/match [[:inner 0]]
metabase.lib.util.match/match-one [[:inner 0]]
metabase.lib.util.match/match-lite [[:inner 0]]
metabase.lib.util.match/match-lite-recursive [[:inner 0]]
metabase.lib.util.match/replace [[:inner 0]]
metabase.lib.util.match/replace-in [[:inner 0]]
metabase.models.interface/define-batched-hydration-method [[:inner 0]]
metabase.models.interface/define-simple-hydration-method [[:inner 0]]
metabase.parameters.chain-filter-test/with-clean-field-values-for-field [[:block 1]]
metabase.parameters.chain-filter-test/with-fk-field-to-field-remapping! [[:block 0]]
metabase.parameters.chain-filter-test/with-human-readable-values-remapping! [[:block 0]]
metabase.permissions.core/allow-changing-all-users-group-members [[:block 0]]
metabase.permissions.core/disable-perms-cache [[:block 0]]
metabase.public-sharing.api-test/with-sharing-enabled-and-temp-card-referencing [[:block 3]]
metabase.public-sharing.api-test/with-sharing-enabled-and-temp-dashcard-referencing [[:block 3]]
metabase.public-sharing.api-test/with-temp-public-card [[:block 1]]
metabase.public-sharing.api-test/with-temp-public-dashboard [[:block 1]]
metabase.public-sharing.api-test/with-temp-public-dashboard-and-card [[:block 1]]
metabase.pulse.api.alert-test/with-alert-in-collection [[:block 1]]
metabase.pulse.dashboard-subscription-test/with-link-card-fixture-for-dashboard [[:block 2]]
metabase.pulse.models.pulse-test/with-dashboard-subscription-in-collection [[:block 1]]
metabase.pulse.models.pulse-test/with-pulse-in-collection [[:inner 0]]
metabase.queries.api.card-test/with-cards-in-readable-collection [[:block 1]]
metabase.queries.api.card-test/with-cards-in-writeable-collection [[:block 1]]
metabase.queries.api.card-test/with-ordered-items [[:inner 0]]
metabase.queries.api.card-test/with-temp-native-card [[:block 1]]
metabase.queries.api.card-test/with-temp-native-card-with-params [[:block 1]]
metabase.query-processor-test.pipeline-queries-test/pmbql-query [[:block 1]]
metabase.query-processor-test.pipeline-queries-test/run-pmbql-query [[:block 1]]
metabase.query-processor.debug/debug> [[:block 0]]
metabase.query-processor.error-type/deferror [[:block 1]]
metabase.query-processor.middleware.cache-backend.interface/with-cached-results [[:block 4]]
metabase.query-processor.middleware.process-userland-query-test/with-query-execution [[:block 1]]
metabase.query-processor.store/cached [[:block 1]]
metabase.query-processor.store/with-metadata-provider [[:inner 0]]
metabase.query-processor.streaming/streaming-response [[:block 1]]
metabase.query-processor.test-util/format-rows-by [[:default]]
metabase.request.core/as-admin [[:block 0]]
metabase.request.core/with-current-request [[:inner 0]]
metabase.request.core/with-current-user [[:inner 0]]
metabase.request.core/with-limit-and-offset [[:inner 0]]
metabase.request.session/as-admin [[:block 0]]
metabase.request.session/with-current-user [[:inner 0]]
metabase.sample-data.impl-test/with-temp-sample-database-db [[:block 1]]
metabase.search.api-test/do-test-users [[:block 1]]
metabase.server.middleware.session/as-admin [[:block 0]]
metabase.server.middleware.session/with-current-user [[:inner 0]]
metabase.server.streaming-response-test/with-streaming-response-thread-pool [[:block 0]]
metabase.server.streaming-response-test/with-test-driver-db! [[:block 0]]
metabase.server.streaming-response/streaming-response [[:block 2]]
metabase.session.api/http-401-on-error [[:block 0]]
metabase.settings.core/define-multi-setting [[:inner 0]]
metabase.settings.core/defsetting [[:block 1]]
metabase.settings.models.setting.multi-setting/define-multi-setting [[:inner 0]]
metabase.settings.models.setting/defsetting [[:block 1]]
metabase.sync.fetch-metadata/log-if-error [[:block 1]]
metabase.sync.sync-metadata.fields-test/with-test-db [[:block 0]]
metabase.sync.util/do-with-start-and-finish-debug-logging [[:default]]
metabase.sync.util/sum-for [[:block 1]]
metabase.sync.util/sync-operation [[:block 3]]
metabase.sync.util/with-db-logging-disabled [[:default]]
metabase.sync.util/with-duplicate-ops-prevented [[:default]]
metabase.sync.util/with-emoji-progress-bar [[:block 1]]
metabase.sync.util/with-error-handling [[:block 1]]
metabase.sync.util/with-start-and-finish-logging [[:default]]
metabase.sync.util/with-start-and-finish-logging* [[:default]]
metabase.sync.util/with-sync-events [[:default]]
metabase.task-history.models.task-history/with-task-history [[:block 1]]
metabase.task.core/rerun-on-error [[:block 1]]
metabase.task.impl/rerun-on-error [[:block 1]]
metabase.test.data.datasets/test-driver [[:inner 0]]
metabase.test.data.datasets/test-drivers [[:inner 0]]
metabase.test.data.one-off-dbs/with-blank-db [[:block 0]]
metabase.test.data.one-off-dbs/with-blueberries-db [[:block 0]]
metabase.test.data.users/with-test-user [[:inner 0]]
metabase.test.data/$ids [[:inner 0]]
metabase.test.data/dataset [[:inner 0]]
metabase.test.data/mbql-query [[:inner 0]]
metabase.test.data/query [[:block 1]]
metabase.test.data/run-mbql-query [[:inner 0]]
metabase.test.data/with-empty-h2-app-db! [[:block 0]]
metabase.test.data/with-temp-copy-of-db [[:block 0]]
metabase.test.util.public-settings/with-additional-premium-features [[:block 1]]
metabase.test.util.public-settings/with-premium-features [[:block 1]]
metabase.test.util.thread-local/test-helpers-set-global-values! [[:block 0]]
metabase.test.util/discard-setting-changes [[:block 1]]
metabase.test.util/let-url [[:block 1]]
metabase.test.util/throw-if-called! [[:block 1]]
metabase.test.util/with-temp-scheduler [[:block 0]]
metabase.test.util/with-temp-vals-in-db [[:block 3]]
metabase.test.util/with-user-in-groups [[:inner 0]]
metabase.test/$ids [[:inner 0]]
metabase.test/as-admin [[:inner 0]]
metabase.test/dataset [[:inner 0]]
metabase.test/discard-setting-changes [[:block 1]]
metabase.test/format-rows-by [[:default]]
metabase.test/let-url [[:block 1]]
metabase.test/mbql-query [[:inner 0]]
metabase.test/query [[:block 1]]
metabase.test/run-mbql-query [[:inner 0]]
metabase.test/test-driver [[:inner 0]]
metabase.test/test-drivers [[:inner 0]]
metabase.test/test-helpers-set-global-values! [[:block 0]]
metabase.test/throw-if-called! [[:block 1]]
metabase.test/with-actions [[:block 1]]
metabase.test/with-actions-disabled [[:block 0]]
metabase.test/with-actions-enabled [[:block 0]]
metabase.test/with-actions-test-data [[:inner 0]]
metabase.test/with-actions-test-data-and-actions-enabled [[:block 0]]
metabase.test/with-actions-test-data-tables [[:block 1]]
metabase.test/with-additional-premium-features [[:block 1]]
metabase.test/with-current-user [[:inner 0]]
metabase.test/with-driver [[:block 1]]
metabase.test/with-empty-db [[:inner 0]]
metabase.test/with-empty-h2-app-db! [[:block 0]]
metabase.test/with-metadata-provider [[:inner 0]]
metabase.test/with-premium-features [[:block 1]]
metabase.test/with-temp [[:inner 0]]
metabase.test/with-temp-copy-of-db [[:block 0]]
metabase.test/with-temp-scheduler [[:block 0]]
metabase.test/with-temp-test-data [[:inner 0]]
metabase.test/with-temp-vals-in-db [[:block 3]]
metabase.test/with-test-user [[:inner 0]]
metabase.test/with-user-in-groups [[:inner 0]]
metabase.timeseries-query-processor-test.util/test-timeseries-drivers [[:block 0]]
metabase.timeseries-query-processor-test/run-mbql-query [[:inner 0]]
metabase.upload.impl-test/with-upload-table! [[:inner 0]]
metabase.upload.types/with-currency [[:default]]
metabase.upload.types/with-parens [[:default]]
metabase.util/for-map [[:block 1]]
metabase.util.async/cancelable-thread [[:block 0]]
metabase.util.date-2/with-time-zone-same-instant [[:default]]
metabase.util.encryption-test/with-secret-key [[:block 1]]
metabase.util.honey-sql-2/with-database-type-info [[:default]]
metabase.util.i18n/deferred-trs [[:default]]
metabase.util.i18n/deferred-tru [[:default]]
metabase.util.i18n/trs [[:default]]
metabase.util.i18n/trsn [[:default]]
metabase.util.i18n/tru [[:default]]
metabase.util.i18n/trun [[:default]]
metabase.util.jvm/auto-retry [[:block 1]]
metabase.util.jvm/varargs [[:block 1]]
metabase.util.jvm/with-us-locale [[:block 0]]
metabase.util.malli/disable-enforcement [[:block 0]]
metabase.util.malli/with [[:default]]
metabase.util.malli/with-api-error-message [[:default]]
metabase.util.namespaces/import-fns [[:default]]
metabase.util.queue-test/await-test-while [[:block 1]]
metabase.util/auto-retry [[:block 1]]
metabase.util/case-enum [[:block 1]]
metabase.util/ignore-exceptions [[:block 0]]
metabase.util/or-with [[:block 1]]
metabase.util/profile [[:block 1]]
metabase.util/prog1 [[:inner 0]]
metabase.util/varargs [[:inner 0]]
metabase.util/with-us-locale [[:block 0]]
metabuild-common.core/exit-when-finished-nonzero-on-exception [[:block 0]]
metabuild-common.core/step [[:block 1]]
metabuild-common.entrypoint/exit-when-finished-nonzero-on-exception [[:block 0]]
metabuild-common.steps/step [[:block 1]]
methodical.core/defmethod [[:inner 0]]
methodical.core/defmulti [[:inner 0]]
methodical.core/with-dispatcher [[:default]]
methodical.core/with-method-table [[:default]]
methodical.core/with-prefers [[:default]]
methodical.interface/with-dispatcher [[:default]]
methodical.interface/with-method-table [[:default]]
methodical.interface/with-prefers [[:default]]
methodical.macros/defmethod [[:inner 0]]
methodical.macros/defmulti [[:inner 0]]
saml20-clj.sp.response/validate-confirmation-datas [[:block 1]]
toucan2.connection/with-transaction [[:block 1]]
toucan2.core/build [[:block 0]]
toucan2.core/compile [[:block 0]]
toucan2.core/debug [[:block 0]]
toucan2.core/define-after-insert [[:inner 0]]
toucan2.core/define-after-select [[:inner 0]]
toucan2.core/define-after-update [[:inner 0]]
toucan2.core/define-before-delete [[:inner 0]]
toucan2.core/define-before-insert [[:inner 0]]
toucan2.core/define-before-select [[:inner 0]]
toucan2.core/define-default-fields [[:inner 0]]
toucan2.core/define-named-query [[:block 1]]
toucan2.core/deftransforms [[:block 1]]
toucan2.core/with-transaction [[:block 1]]
toucan2.tools.after-insert/define-after-insert [[:inner 0]]
toucan2.tools.after-select/define-after-select [[:inner 0]]
toucan2.tools.after-update/define-after-update [[:inner 0]]
toucan2.tools.after/define-after-impl [[:default]]
toucan2.tools.before-delete/define-before-delete [[:inner 0]]
toucan2.tools.before-insert/define-before-insert [[:inner 0]]
toucan2.tools.before-select/define-before-select [[:inner 0]]
toucan2.tools.compile/build [[:block 0]]
toucan2.tools.compile/compile [[:block 0]]
toucan2.tools.compile/resolved [[:block 0]]
toucan2.tools.debug/debug [[:block 0]]
toucan2.tools.default-fields/define-default-fields [[:inner 0]]
toucan2.tools.named-query/define-named-query [[:block 1]]
toucan2.tools.simple-out-transform/define-out-transform [[:inner 0]]
toucan2.tools.transformed/deftransforms [[:block 1]]
toucan2.tools.with-temp/with-temp [[:inner 0]]
toucan2.util/try-with-error-context [[:inner 0]]}}

View File

@@ -1,23 +0,0 @@
Brite
afterAll
ags
altho
cancelation
cancelations
datas
edn
eduction
hasTables
horizontalX
horizontalY
juxt
methodd
notIn
pass-thru
ser
serieses
sortings
trs
trsn
tru
trun

View File

@@ -1,3 +0,0 @@
[codespell]
skip = *.csv,*.edn,*.geojson,*.json,*.tsv,*.xml,docs/developers-guide/drivers/driver-tests.md,docs/developers-guide/e2e-tests.md,docs/questions/query-builder/expressions-list.md,docs/questions/visualizations/country-codes.md,e2e/test/scenarios/admin/i18n/content-translation/constants.ts,frontend/src/cljs,frontend/src/metabase/querying/expressions/tokenizer/lezer.js,frontend/src/metabase/static-viz/constants/char-sizes.ts,frontend/src/metabase/visualizations/lib/mapping_codes.ts,modules/drivers/mongo/src/metabase/driver/mongo/operators.clj
ignore-words = .codespellignore

View File

@@ -2,4 +2,3 @@ frontend/src/cljs
frontend/src/cljs_release
e2e/support/cypress_sample_database.js
e2e/support/cypress_sample_instance_data.js
e2e/embedding-sdk-host-apps

View File

@@ -94,7 +94,10 @@ module.exports = {
"react/jsx-no-target-blank": 2,
"react/jsx-key": 2,
"react/forbid-component-props": [2, { forbid: ["sx"] }],
"react-hooks/exhaustive-deps": ["warn"],
"react-hooks/exhaustive-deps": [
"warn",
{ additionalHooks: "(useSafeAsyncFunction)" },
],
"prefer-const": [1, { destructuring: "all" }],
"no-restricted-globals": ["error", "close"],
"no-useless-escape": 0,
@@ -115,6 +118,7 @@ module.exports = {
"And",
"When",
"Then",
"describeWithSnowplow",
],
},
],
@@ -344,11 +348,5 @@ module.exports = {
"import/no-commonjs": "off",
},
},
{
files: ["**/*.stories.tsx", "**/preview.tsx"],
rules: {
"import/no-default-export": "off",
},
},
],
};

View File

@@ -10,12 +10,6 @@ outputs:
node-cache-key:
description: "The cache key for the Node.js cache."
value: ${{ steps.keys-factory.outputs.node-cache-key }}
eslint-cache-key:
description: "The cache key for Eslint cache"
value: ${{ steps.keys-factory.outputs.eslint-cache-key }}
cypress-cache-key:
description: "The cache key for Cypress cache"
value: ${{ steps.keys-factory.outputs.cypress-cache-key }}
runs:
using: "composite"
@@ -25,13 +19,9 @@ runs:
run: |
M2_CACHE_PREFIX='M2'
NODE_CACHE_PREFIX='node-modules'
ESLINT_CACHE_PREFIX='eslint'
CYPRESS_CACHE_PREFIX='cypress'
WEEK_OF_YEAR=$(/bin/date "+%G-%V")
CACHE_SUFFIX="${{ runner.os }}-$WEEK_OF_YEAR"
echo "m2-cache-key=$M2_CACHE_PREFIX-$CACHE_SUFFIX" >> $GITHUB_OUTPUT
echo "node-cache-key=$NODE_CACHE_PREFIX-$CACHE_SUFFIX" >> $GITHUB_OUTPUT
echo "eslint-cache-key=$ESLINT_CACHE_PREFIX-$CACHE_SUFFIX" >> $GITHUB_OUTPUT
echo "cypress-cache-key=$CYPRESS_CACHE_PREFIX-$CACHE_SUFFIX" >> $GITHUB_OUTPUT
shell: bash

View File

@@ -25,6 +25,11 @@ runs:
curl -O https://download.clojure.org/install/linux-install-${{ inputs.clojure-version }}.sh &&
sudo bash ./linux-install-${{ inputs.clojure-version }}.sh
- name: Fetch CI config
shell: bash
run: |
curl -o ci-test-config.json https://raw.githubusercontent.com/metabase/metabase/refs/heads/master/ci-test-config.json
- name: Get M2 cache key
uses: ./.github/actions/get-cache-keys
id: get-cache-keys

View File

@@ -22,16 +22,16 @@ runs:
chrome-version: 1097615
id: setup-chrome
- name: Get cache keys
uses: ./.github/actions/get-cache-keys
id: get-cache-keys
- name: Get current week of year
id: get-date
run: |
echo "week-of-year=$(/bin/date "+%G-%V")" >> $GITHUB_OUTPUT
shell: bash
- name: Get Cypress cache
uses: actions/cache/restore@v4
uses: actions/cache@v4
with:
path: ~/.cache/Cypress
key: ${{ steps.get-cache-keys.outputs.cypress-cache-key }}
key: ${{ runner.os }}-Cypress-${{ steps.get-date.outputs.week-of-year }}
- name: Ensure that Cypress executable is ready
run: |
yarn cypress install

View File

@@ -6,7 +6,7 @@ inputs:
apply-patches:
description: "Whether to patch frontend dependencies with patch-package."
required: true
default: "true"
default: 'true'
runs:
using: "composite"
steps:
@@ -28,21 +28,11 @@ runs:
~/.gitlibs
key: ${{ steps.get-cache-keys.outputs.m2-cache-key }}
- name: Get yarn cache directory
id: yarn-cache-dir
run: |
YARN_CACHE_DIR=$(yarn cache dir)
echo "Yarn cache directory: $YARN_CACHE_DIR"
echo "dir=$YARN_CACHE_DIR" >> $GITHUB_OUTPUT
shell: bash
- name: Restore node_modules cache
uses: actions/cache/restore@v4
id: node-modules-cache
with:
path: |
${{ steps.yarn-cache-dir.outputs.dir }}
node_modules
path: node_modules
key: ${{ steps.get-cache-keys.outputs.node-cache-key }}
# postinstall lifecycle hook can be dangerous in CI so we only run it locally (see package.json)

View File

@@ -0,0 +1,31 @@
name: Run Embedding SDK Storybook
runs:
using: "composite"
steps:
- name: Prepare frontend environment
uses: ./.github/actions/prepare-frontend
- name: Prepare back-end environment
uses: ./.github/actions/prepare-backend
- name: Compile CLJS
run: yarn build-pure:cljs
shell: bash
- name: Build Storybook
run: yarn build-storybook-embedding-sdk
shell: bash
- name: Serve Storybook
run: yarn http-server storybook-static -p 6006 &
shell: bash
- name: Wait for Storybook to become available
run: |
echo "Waiting for Storybook to be served..."
while ! curl --silent --fail http://localhost:6006; do
sleep 5
echo "Retrying..."
done
echo "Storybook is being served!"
shell: bash

View File

@@ -10,67 +10,26 @@ inputs:
required: true
default: false
outputs:
quarantine-skipped:
description: Whether the driver test was skipped due to quarantine
value: ${{ steps.check-driver.outputs.skip }}
runs:
using: "composite"
steps:
- name: Check if driver is ignored
id: check-driver
shell: bash
run: |
# If DRIVERS env var is not set, proceed with tests
if [ -z "$DRIVERS" ]; then
echo "No DRIVERS env var set. Proceeding with tests."
echo "skip=false" >> $GITHUB_OUTPUT
exit 0
fi
# Fetch CI config and check if driver should be skipped
curl -sSf -o ci-test-config.json https://raw.githubusercontent.com/metabase/ci-test-config/refs/heads/master/ci-test-config.json || true
# Check if the driver is in the ignored list (gracefully handle missing/invalid config)
IS_IGNORED=$(jq --arg driver "$DRIVERS" '.ignored.drivers // [] | contains([$driver])' ci-test-config.json 2>/dev/null || echo "false")
if [ "$IS_IGNORED" = "true" ]; then
echo "⚠️ Driver '$DRIVERS' is in the ignored list. Skipping tests."
echo "View the ignored list here: https://github.com/metabase/ci-test-config/blob/master/ci-test-config.json"
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "Driver '$DRIVERS' is not ignored. Proceeding with tests."
echo "skip=false" >> $GITHUB_OUTPUT
fi
- name: Prepare back-end environment
if: steps.check-driver.outputs.skip != 'true'
uses: ./.github/actions/prepare-backend
- name: Prepare front-end environment
if: ${{ steps.check-driver.outputs.skip != 'true' && inputs.build-static-viz == 'true' }}
uses: ./.github/actions/prepare-frontend
if: ${{ inputs.build-static-viz == 'true' }}
- name: Build static viz frontend
if: ${{ steps.check-driver.outputs.skip != 'true' && inputs.build-static-viz == 'true' }}
run: yarn build-static-viz
shell: bash
if: ${{ inputs.build-static-viz == 'true' }}
env:
MB_EDITION: ee
- name: Test database driver
if: steps.check-driver.outputs.skip != 'true'
run: clojure -X:dev:ci:ee:ee-dev:drivers:drivers-dev:test ${{ inputs.test-args }}
shell: bash
id: run-test
# Trunk Quarantine controls the final state of the run in the CI!
# Even if this step fails we let it pass, and then `trunk-io/analytics-uploader` determines
# whether to fail or to pass the whole job, based on the quarantine list.
continue-on-error: true
- name: Publish Test Report (JUnit)
uses: dorny/test-reporter@v1
# As per the documentation: https://docs.github.com/en/actions/reference/workflows-and-actions/contexts#steps-context
# test.outcome gives us access to the result before `continue-on-error` overrides it and sets it to 'success'.
# We want test reports even if the test step gets cancelled (e.g. times out), hence we match anything that is not a 'success'.
if: ${{ steps.run-test.outcome != 'success' && steps.check-driver.outputs.skip != 'true' }}
if: failure()
with:
path: "target/junit/**/*_test.xml"
name: JUnit Test Report ${{ inputs.junit-name }}
@@ -79,11 +38,9 @@ runs:
list-tests: failed
fail-on-error: false
- name: Upload Logs on Failure
if: always() && steps.run-test.outcome != 'success' && steps.check-driver.outputs.skip != 'true'
if: always() && !success()
uses: actions/upload-artifact@v4
with:
name: test-logs-${{ github.job }}
path: logs/test-log.json
retention-days: 1
# Failing to upload the artifact should never affect the outcome of the whole test run.
continue-on-error: true

View File

@@ -48,11 +48,10 @@ runs:
aws s3 cp ${OUTPUT_FILE}.zip s3://$BUCKET/$DATE/$GITHUB_RUN_ID/$GITHUB_RUN_ATTEMPT/
- name: Upload results to Trunk
if: always() # Important! This step must always run because it controls job's exit code.
if: ${{ always() && !(github.event_name == 'pull_request' && github.event.pull_request.draft == true) }}
uses: trunk-io/analytics-uploader@main
with:
junit-paths: ${{ inputs.input-path }}
variant: ${{ inputs.variant || inputs.output-name }}
org-slug: metabase
dry-run: ${{ github.event_name == 'pull_request' && github.event.pull_request.draft == true }} # quarantine, but do not upload results
token: ${{ inputs.trunk-api-token }}

View File

@@ -1,5 +1,3 @@
version: 1
name: "Metabase CodeQL config"
paths-ignore:

View File

@@ -16,6 +16,11 @@ frontend_ci: &frontend_ci
- ".github/actions/prepare-backend/**"
- ".github/workflows/frontend.yml"
frontend_embedding_sdk_ci: &frontend_embedding_sdk_ci
- ".github/actions/prepare-frontend/**"
- ".github/actions/prepare-backend/**"
- ".github/workflows/*embedding-sdk*.yml"
frontend_configs: &frontend_configs
- "yarn.lock"
- "**/tsconfig*.json"
@@ -122,19 +127,18 @@ e2e_ci: &e2e_ci
e2e_specs: &e2e_specs
- e2e/!(test-host-app)**/*.cy.*.{js,ts}
embedding_sdk_ci: &embedding_sdk_ci
- ".github/actions/prepare-frontend/**"
- ".github/actions/prepare-backend/**"
- ".github/workflows/embedding-sdk*.yml"
e2e_cross_version: &e2e_cross_version
- ".github/workflows/e2e-cross-version.yml"
- "e2e/test/scenarios/cross-version/**"
embedding_sdk_components:
- *embedding_sdk_ci
e2e_embedding_sdk:
- *frontend_embedding_sdk_ci
- *sources
- "e2e/test-component/**/*.cy.*.{js,jsx,ts,tsx}"
- "e2e/component/**/*.cy.*.{js,ts}"
embedding_sdk_host_sample_apps:
e2e_embedding_sdk_compatibility:
- *default
- *embedding_sdk_ci
- *frontend_embedding_sdk_ci
- *frontend_sources
- *backend_sources
- "e2e/embedding-sdk-host-apps/**"
@@ -166,7 +170,6 @@ yaml:
- "**/*.yaml"
codeql:
- ".github/workflows/codeql.yml"
- "frontend/src/**"
- "enterprise/frontend/src/**"

View File

@@ -3,8 +3,14 @@
// grouping some specific tests together, other tests are split into chunks
const DEFAULT_SPEC_PATTERN = "./e2e/test/scenarios/**/*.cy.spec.*";
const EMBEDDING_SDK_SPEC_PATTERN =
"./e2e/test/scenarios/embedding-sdk/**.cy.spec.*";
const specialTestConfigs = [
{
name: "embedding-sdk",
specs: EMBEDDING_SDK_SPEC_PATTERN,
},
{
name: "oss-subset",
edition: "oss",

View File

@@ -11,7 +11,7 @@ describe("buildMatrix", () => {
it("should calculate correct regular chunks with default parameters", () => {
const result = testBuildMatrix("./e2e/test/scenarios/**/*.cy.spec.*", 30);
expect(result.regularChunks).toBe(27);
expect(result.regularChunks).toBe(26);
});
it("should create correct number of configuration objects", () => {
@@ -23,7 +23,7 @@ describe("buildMatrix", () => {
it("should apply default specs path when empty entry is provided", () => {
const result = testBuildMatrix("", 6);
expect(result.regularChunks).toBe(3);
expect(result.regularChunks).toBe(2);
expect(result.config.length).toBe(6);
});
@@ -39,13 +39,14 @@ describe("buildMatrix", () => {
});
it("should handle case when input chunks equals special tests count", () => {
const result = testBuildMatrix("./e2e/test/scenarios/**/*.cy.spec.*", 3);
const result = testBuildMatrix("./e2e/test/scenarios/**/*.cy.spec.*", 4);
expect(result.regularChunks).toBe(0);
expect(result.config.length).toBe(3);
expect(result.config[0].name).toBe("oss-subset");
expect(result.config[1].name).toBe("mongo");
expect(result.config[2].name).toBe("python");
expect(result.config.length).toBe(4);
expect(result.config[0].name).toBe("embedding-sdk");
expect(result.config[1].name).toBe("oss-subset");
expect(result.config[2].name).toBe("mongo");
expect(result.config[3].name).toBe("python");
});
it("should correctly calculate regularChunks for non-default spec pattern", () => {
@@ -83,6 +84,7 @@ describe("buildMatrix", () => {
it("should return special test configs when default spec pattern is provided", () => {
const result = testBuildMatrix("./e2e/test/scenarios/**/*.cy.spec.*", 50);
expect(result.config[46].name).toBe("embedding-sdk");
expect(result.config[47].name).toBe("oss-subset");
expect(result.config[48].name).toBe("mongo");
expect(result.config[49].name).toBe("python");

16
.github/team.json vendored
View File

@@ -32,6 +32,15 @@
"lorem--ipsum"
]
},
{
"name": "Workflows",
"label": ".Team/Workflows",
"members": [
"piranha",
"crisptrutski",
"spalt08"
]
},
{
"name": "Admin Webapp",
"label": ".Team/AdminWebapp",
@@ -60,9 +69,7 @@
"sloansparger",
"somtom",
"tsplude",
"lbrdnk",
"piranha",
"crisptrutski"
"lbrdnk"
]
},
{
@@ -106,8 +113,7 @@
"chodorowicz",
"kamilmielnik",
"stasgavrylov",
"uladzimirdev",
"egorgrushin"
"uladzimirdev"
]
}
]

View File

@@ -137,13 +137,11 @@ jobs:
image: cimg/mysql:8.0
env:
enable-ssl-tests: 'false'
enable-aws-iam-tests: 'false'
- name: MySQL Latest
junit-name: be-tests-mysql-latest-ee
image: mysql:latest
env:
enable-ssl-tests: 'true'
enable-aws-iam-tests: 'true'
job:
- name: Enterprise Tests
build-static-viz: false
@@ -213,14 +211,6 @@ jobs:
MB_MYSQL_SSL_TEST_SSL_CERT: ${{ secrets.MB_MYSQL_SSL_TEST_SSL_CERT }}
MB_MYSQL_SSL_TEST_USER: metabase
MB_MYSQL_SSL_TEST_PASSWORD: ${{ secrets.MYSQL_RDS_SSL_INSTANCE_PASSWORD }}
MB_MYSQL_AWS_IAM_TEST: ${{ matrix.version.env.enable-aws-iam-tests }}
MB_MYSQL_AWS_IAM_TEST_HOST: ${{ secrets.MYSQL_AURORA_IAM_INSTANCE_HOST }}
MB_MYSQL_AWS_IAM_TEST_PORT: 3306
MB_MYSQL_AWS_IAM_TEST_USER: my_iam_user
MB_MYSQL_AWS_IAM_TEST_DBNAME: test_db
MB_MYSQL_AWS_IAM_TEST_SSL_CERT: trust
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_AURORA_IAM_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_AURORA_IAM_SECRET_ACCESS_KEY }}
# for historic reasons (I don't want to mess around with required jobs) the job name should be something like
# "be-tests-mariadb-10-2-ee (0)"
name: "${{ matrix.version.name }} ${{ matrix.job.name }}"
@@ -259,13 +249,11 @@ jobs:
docker-image: postgres:12-alpine
env:
enable-ssl-tests: 'false'
enable-aws-iam-tests: 'false'
- name: Postgres Latest
junit-name: postgres-latest-ee
docker-image: postgres:latest
env:
enable-ssl-tests: 'true'
enable-aws-iam-tests: 'true'
job:
- name: Enterprise Tests
build-static-viz: false
@@ -317,13 +305,6 @@ jobs:
MB_POSTGRES_SSL_TEST_SSL: ${{ matrix.version.env.enable-ssl-tests }}
MB_POSTGRES_SSL_TEST_SSL_MODE: verify-full
MB_POSTGRES_SSL_TEST_SSL_ROOT_CERT_PATH: 'test-resources/certificates/us-east-2-bundle.pem'
MB_POSTGRES_AWS_IAM_TEST: ${{ matrix.version.env.enable-aws-iam-tests }}
MB_POSTGRES_AWS_IAM_TEST_HOST: ${{ secrets.POSTGRES_AURORA_IAM_INSTANCE_HOST }}
MB_POSTGRES_AWS_IAM_TEST_PORT: 5432
MB_POSTGRES_AWS_IAM_TEST_USER: my_iam_user
MB_POSTGRES_AWS_IAM_TEST_DBNAME: test_db
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_AURORA_IAM_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_AURORA_IAM_SECRET_ACCESS_KEY }}
services:
postgres:
image: ${{ matrix.version.docker-image }}

View File

@@ -10,202 +10,19 @@ jobs:
# don't run this workflow on a cron for forks
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
runs-on: ubuntu-22.04
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
modules:
- actions
- activity-feed
- analytics
- analyze
- api
- api-keys
- api-routes
- app-db
- appearance
- audit-app
- auth-provider
- batch-processing
- bookmarks
- bug-reporting
- cache
- channel
- classloader
- cloud-migration
- cmd
- collections
- config
- connection-pool
- content-translation
- content-verification
- core
- dashboards
- database-routing
- driver
- driver-api
- eid-translation
- embedding
- events
- formatter
- geojson
- glossary
- graph
- indexed-entities
- internal-stats
- legacy-mbql
- lib
- lib-be
- logger
- login-history
- model-persistence
- models
- native-query-snippets
- notification
- parameters
- permissions
- pivot
- plugins
- premium-features
- product-feedback
- public-sharing
- pulse
- queries
- query-permissions
- query-processor
- remote-sync
- request
- revisions
- sample-data
- search
- secrets
- segments
- server
- session
- settings
- setup
- sso
- startup
- store-api
- sync
- system
- task
- task-history
- testing-api
- tiles
- timeline
- types
- upload
- user-key-value
- users
- util
- version
- view-log
- warehouse-schema
- warehouses
- xrays
timeout-minutes: 90
steps:
- uses: actions/checkout@v4
- name: Prepare front-end environment
uses: ./.github/actions/prepare-frontend
- name: Prepare back-end environment
uses: ./.github/actions/prepare-backend
- name: Build static viz frontend
run: yarn build-static-viz
env:
MB_EDITION: ee
- name: Collect the test coverage
run: |
clojure -X:dev:ci:ee:ee-dev:test:cloverage \
:ns-regex '["^metabase\\.${{ matrix.modules }}\\..*"]' \
:test-ns-path '["test/metabase/${{ matrix.modules }}"]'
continue-on-error: true
run: clojure -X:dev:ci:ee:ee-dev:test:cloverage
- name: Upload coverage to codecov.io
if: always()
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./target/coverage/codecov.json
flags: back-end
be-linter-cloverage-enterprise:
# don't run this workflow on a cron for forks
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
runs-on: ubuntu-22.04
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
modules:
- action-v2
- advanced-config
- advanced-permissions
- ai-entity-analysis
- ai-sql-fixer
- ai-sql-generation
- analytics
- api
- audit-app
- auth-provider
- billing
- bookmarks
- cache
- cloud-add-ons
- comments
- content-translation
- content-verification
- core
- dashboard-subscription-filters
- database-replication
- database-routing
- dependencies
- documents
- email
- embedding-hub
- gsheets
- harbormaster
- impersonation
- internal-stats
- llm
- metabot-v3
- permission-debug
- premium-features
- public-sharing
- remote-sync
- sandbox
- scim
- search
- semantic-search
- serialization
- snippet-collections
- sso
- stale
- transforms
- transforms-python
- upload-management
steps:
- uses: actions/checkout@v4
- name: Prepare front-end environment
uses: ./.github/actions/prepare-frontend
- name: Prepare back-end environment
uses: ./.github/actions/prepare-backend
- name: Build static viz frontend
run: yarn build-static-viz
env:
MB_EDITION: ee
- name: Collect the test coverage
run: |
clojure -X:dev:ci:ee:ee-dev:test:cloverage \
:ns-regex '["^metabase-enterprise\\.${{ matrix.modules }}\\..*"]' \
:test-ns-path '["enterprise/backend/test/metabase/${{ matrix.modules }}"]'
continue-on-error: true
- name: Upload coverage to codecov.io
if: always()
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./target/coverage/codecov.json

View File

@@ -48,11 +48,13 @@ jobs:
- name: Enterprise Tests
edition: ee
test-group-name: ee
build-static-viz: 'false'
test-args: >-
:only '"enterprise/backend/test"'
- name: EE App DB Tests (Part 1)
edition: ee
test-group-name: ee-app-db-1
build-static-viz: 'true'
test-args: >-
:only '["test" ".clj-kondo/test"]'
:partition/total 2
@@ -60,6 +62,7 @@ jobs:
- name: EE App DB Tests (Part 2)
edition: ee
test-group-name: ee-app-db-2
build-static-viz: 'true'
test-args: >-
:only '["test" ".clj-kondo/test"]'
:partition/total 2
@@ -67,6 +70,7 @@ jobs:
- name: OSS App DB Tests (Part 1)
edition: oss
test-group-name: oss-app-db-1
build-static-viz: 'true'
test-args: >-
:only '["test" ".clj-kondo/test"]'
:partition/total 2
@@ -74,6 +78,7 @@ jobs:
- name: OSS App DB Tests (Part 2)
edition: oss
test-group-name: oss-app-db-2
build-static-viz: 'true'
test-args: >-
:only '["test" ".clj-kondo/test"]'
:partition/total 2
@@ -98,15 +103,25 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Prepare front-end environment
if: ${{ matrix.job.build-static-viz == 'true' }}
uses: ./.github/actions/prepare-frontend
- name: Prepare back-end environment
uses: ./.github/actions/prepare-backend
with:
java-version: "${{ matrix.java-version }}"
# Depending on which files changed, we might either have to build static-viz
# from scratch or download the previously built artifact
- name: Build static-viz frontend
if: ${{ matrix.job.build-static-viz == 'true' && !inputs.skip }}
run: yarn build-static-viz
env:
MB_EDITION: ${{ matrix.job.edition }}
- name: Download Static Viz Bundle Artifact
if: ${{ matrix.job.build-static-viz == 'true' && !inputs.skip }}
uses: actions/download-artifact@v4
with:
name: static-viz-bundle-${{ github.sha }}
path: resources/frontend_client/app/dist
- name: "Test Java ${{ matrix.java-version }} ${{ matrix.job.name }}"
id: run-java-tests
@@ -114,10 +129,6 @@ jobs:
clojure -X:dev:ci:test:${{ matrix.job.edition }}:${{ matrix.job.edition }}-dev
:exclude-tags '[:mb/driver-tests ${{ env.__ADDITIONAL_EXCLUDED_TAG__ }}]'
${{ matrix.job.test-args }}
# Trunk Quarantine controls the final state of the run in the CI!
# Even if this step fails we let it pass, and then `trunk-io/analytics-uploader` determines
# whether to fail or to pass the whole job, based on the quarantine list.
continue-on-error: true
- name: Upload Test Results
uses: ./.github/actions/upload-test-results

View File

@@ -30,8 +30,8 @@ name: Cache Generator
on:
schedule:
- cron: "0 3 * * 1" # Mondays at 03:00 UTC
workflow_dispatch: # manually update the cache
- cron: '0 3 * * 1' # Mondays at 03:00 UTC
workflow_dispatch: # manually update the cache
concurrency:
group: cache-generator
@@ -48,8 +48,6 @@ jobs:
outputs:
m2-cache-key: ${{ steps.get-cache-keys.outputs.m2-cache-key }}
node-cache-key: ${{ steps.get-cache-keys.outputs.node-cache-key }}
eslint-cache-key: ${{ steps.get-cache-keys.outputs.eslint-cache-key }}
cypress-cache-key: ${{ steps.get-cache-keys.outputs.cypress-cache-key }}
generate-m2-cache:
runs-on: ubuntu-22.04
@@ -64,7 +62,7 @@ jobs:
# Add or remove aliases to affect the cache size and its contents.
# Adjust to taste, then redeploy this workflow.
- name: Pre-resolve Clojure dependencies
- name: Pre-resolve dependencies
run: clojure -A:build:dev:ee:ee-dev:drivers:drivers-dev:cljs -P
- name: Update M2 cache
@@ -75,14 +73,12 @@ jobs:
~/.gitlibs
key: ${{ env.M2_CACHE_KEY }}
generate-frontend-caches:
generate-node-modules-cache:
runs-on: ubuntu-22.04
timeout-minutes: 15
needs: get-cache-keys
env:
NODE_CACHE_KEY: ${{ needs.get-cache-keys.outputs.node-cache-key }}
ESLINT_CACHE_KEY: ${{ needs.get-cache-keys.outputs.eslint-cache-key }}
CYPRESS_CACHE_KEY: ${{ needs.get-cache-keys.outputs.cypress-cache-key }}
steps:
- uses: actions/checkout@v4
# `prepare-frontend` action (1) prepares Node.js and (2) fetches the node_modules cache if it exists.
@@ -94,39 +90,10 @@ jobs:
- name: Install front-end dependencies
uses: ./.github/actions/prepare-frontend
with:
apply-patches: "false"
- name: Get yarn cache directory
id: yarn-cache-dir
run: |
YARN_CACHE_DIR=$(yarn cache dir)
echo "Yarn cache directory: $YARN_CACHE_DIR"
echo "dir=$YARN_CACHE_DIR" >> $GITHUB_OUTPUT
- name: Ensure that Cypress executable is ready
run: |
yarn cypress install
yarn cypress cache path
yarn cypress cache list
yarn cypress verify
shell: bash
apply-patches: 'false'
- name: Update node_modules cache
uses: ./.github/actions/update-cache
with:
path: |
${{ steps.yarn-cache-dir.outputs.dir }}
node_modules
path: node_modules
key: ${{ env.NODE_CACHE_KEY }}
- name: Update eslint cache
uses: ./.github/actions/update-cache
with:
path: ${{ github.workspace}}/.eslintcache
key: ${{ env.ESLINT_CACHE_KEY }}
- name: Update Cypress cache
uses: ./.github/actions/update-cache
with:
path: ~/.cache/Cypress
key: ${{ env.CYPRESS_CACHE_KEY }}

View File

@@ -159,15 +159,15 @@ jobs:
1. **Analyze the issue description and comments** - Understand what the issue is describing
a. Pay particular attention to reproduction steps and if there's an agreed upon fix.
2. **Search the codebase** - Use Grep and Glob tools to find relevant files
3. **Implement a fix** - Use the appropriate write skill based on the file types:
- Use clojure-write for Clojure/ClojureScript files
- Use typescript-write for TypeScript/JavaScript files
- Follow the coding standards and patterns in each skill
4. **Test the fix** - Run appropriate tests as specified in the skills
3. **Implement a fix** - Follow existing code patterns and conventions
4. **Test the fix** - Run appropriate tests using the commands in CLAUDE.md:
- JavaScript/TypeScript: `yarn test-unit path/to/file.unit.spec.js`
- Clojure: `clojure -X:dev:test :only namespace/test-name`
- Lint: `yarn lint-eslint-pure` for JS/TS, `./bin/mage kondo <file>` for Clojure
5. **Verify the fix** - Ensure tests pass and code follows conventions
## Important Guidelines
- Use the appropriate write skill for the language you're working in
- Follow the development patterns in CLAUDE.md
- Write failing tests first, then fix them
- Work in small, testable increments
- Don't commit changes - leave that for the workflow
@@ -213,14 +213,15 @@ jobs:
private-key: ${{ secrets.METABASE_BOT_APP_PRIVATE_KEY }}
- name: Generate Bug Fix with Claude Code
uses: anthropics/claude-code-action@v1
uses: anthropics/claude-code-action@beta
with:
mode: agent
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
github_token: ${{ steps.claude-app-token.outputs.token }}
prompt: ${{ env.BUG_FIX_PROMPT }}
claude_args: |
--allowedTools "Bash(yarn test-unit*),Bash(yarn lint-eslint-pure),Bash(./bin/mage*),Bash(clojure*),Bash(find*),Bash(ls*),Bash(grep*),Write,Read,Glob,Grep,Edit,MultiEdit"
--disallowedTools "Bash(git commit*),Bash(git push*),Bash(gh*)"
direct_prompt: ${{ env.BUG_FIX_PROMPT }}
timeout_minutes: "25"
allowed_tools: "Bash(yarn test-unit*),Bash(yarn lint-eslint-pure),Bash(./bin/mage*),Bash(clojure*),Bash(find*),Bash(ls*),Bash(grep*),Write,Read,Glob,Grep,Edit,MultiEdit"
disallowed_tools: "Bash(git commit*),Bash(git push*),Bash(gh*)"
- name: Clean up scratchpad directory
run: |

View File

@@ -2,10 +2,6 @@ name: "Claude Code Review"
on:
pull_request:
types:
- closed
- synchronize
- labeled
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}-claude-code-review
@@ -13,12 +9,7 @@ concurrency:
jobs:
review:
if: |
(
github.event.action == 'closed' &&
github.event.pull_request.merged == true
) ||
contains(github.event.pull_request.labels.*.name, 'Claude Code Review')
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
permissions:
contents: read
@@ -79,14 +70,17 @@ jobs:
REPO: ${{ github.repository }}
PR NUMBER: ${{ github.event.pull_request.number }}
Review this pull request for compliance with project coding standards and conventions.
Review this pull request with a focus on:
**Use the appropriate review skill based on the file types changed:**
- Use clojure-review for Clojure/ClojureScript files
- Use typescript-review for TypeScript/JavaScript files
- Follow the review guidelines and checklists in each skill
- Compliance with project coding standards and conventions
- Code quality and best practices
- Clear and correct docstrings
- Clear and correct comments explaining what the code is doing
DO NOT post any comments confirming that something is correct - only post comments for style violations or concerns.
DO NOT post any comments confirming that something is correct or congratulating someone for following our
guidelines -- only post comments for style violations or concerns.
Follow additional guidelines in the "Code Review Guidelines" section of CLAUDE.md .
Note: The PR branch is already checked out in the current working directory.

View File

@@ -1,100 +0,0 @@
name: "Claude Docs Review"
on:
pull_request:
types:
- opened
- synchronize
- reopened
paths:
- 'docs/**/*.md'
- 'docs/**/*.mdx'
workflow_dispatch:
inputs:
pr_number:
description: 'PR number to review'
required: true
type: number
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}-claude-docs-review
cancel-in-progress: true
jobs:
review:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
# Make sure the value of GITHUB_TOKEN will not be persisted in repo's config
persist-credentials: false
- name: Generate GitHub App Token for Claude
uses: actions/create-github-app-token@v1
id: claude-app-token
with:
app-id: ${{ secrets.METABASE_BOT_APP_ID }}
private-key: ${{ secrets.METABASE_BOT_APP_PRIVATE_KEY }}
permission-contents: read # Required for reading repository files and PR diffs
permission-pull-requests: write # Required for creating inline comments and PR comments
- name: Delete old Claude docs review comments
env:
GH_TOKEN: ${{ steps.claude-app-token.outputs.token }}
REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number || github.event.inputs.pr_number }}
shell: bash
run: |
# Delete pending reviews first (these can be deleted entirely)
pending_review_ids=$(gh api "/repos/$REPO/pulls/$PR_NUMBER/reviews" --jq '.[] | select(.user.login=="metabase-bot[bot]" and .state=="PENDING") | .id')
if [ -n "$pending_review_ids" ]; then
echo "Deleting pending reviews: $pending_review_ids"
for id in $pending_review_ids; do
gh api -X DELETE "/repos/$REPO/pulls/$PR_NUMBER/reviews/$id"
echo "Deleted pending review $id"
done
fi
# Delete individual inline comments from submitted reviews (can't delete the review itself)
old_line_comment_ids=$(gh api "/repos/$REPO/pulls/$PR_NUMBER/comments" --jq '.[] | select(.user.login=="metabase-bot[bot]") | .id')
if [ -n "$old_line_comment_ids" ]; then
echo "Deleting line comments: $old_line_comment_ids"
for id in $old_line_comment_ids; do
gh api -X DELETE "/repos/$REPO/pulls/comments/$id"
echo "Deleted line comment $id"
done
fi
# Delete top-level review comments
old_top_level_comment_ids=$(gh api "/repos/$REPO/issues/$PR_NUMBER/comments" --jq '.[] | select(.user.login=="metabase-bot[bot]") | .id')
if [ -n "$old_top_level_comment_ids" ]; then
echo "Deleting top-level comments: $old_top_level_comment_ids"
for id in $old_top_level_comment_ids; do
gh api -X DELETE "/repos/$REPO/issues/comments/$id"
echo "Deleted top-level comment $id"
done
fi
- uses: anthropics/claude-code-action@v1
timeout-minutes: 10
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
github_token: ${{ steps.claude-app-token.outputs.token }}
show_full_output: true
prompt: |
REPO: ${{ github.repository }}
PR NUMBER: ${{ github.event.pull_request.number || github.event.inputs.pr_number }}
Review the documentation changes in this pull request.
Use the docs-review skill to review all changed documentation files for compliance with the Metabase writing style guide.
DO NOT post any comments confirming that something is correct - only post comments for style violations or concerns.
claude_args: |
--allowedTools "Skill,mcp__github__get_pull_request,mcp__github__get_pull_request_files,mcp__github__create_pending_pull_request_review,mcp__github__get_pull_request_diff,mcp__github__add_comment_to_pending_review,mcp__github__add_pull_request_review_comment_to_pending_review,mcp__github__delete_pending_pull_request_review,mcp__github__submit_pending_pull_request_review"

View File

@@ -30,10 +30,9 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
uses: github/codeql-action/init@v2
with:
languages: javascript
config-file: ./.github/codeql/codeql-config.yml
trap-caching: false
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
uses: github/codeql-action/analyze@v2

View File

@@ -1,19 +0,0 @@
name: Codespell
on:
push:
branches:
- 'master'
- 'release-**'
pull_request:
jobs:
codespell:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: codespell-project/actions-codespell@v1
with:
# keep this list in sync with the list in `mage/src/mage/codespell.yml`
path: >-
docs

View File

@@ -60,14 +60,6 @@ jobs:
clojure -M:ee:doc api-documentation
continue-on-error: false
- name: Generate command documentation
id: generate_command_docs
run: |
set -e # Exit on any error
echo "Generating API documentation..."
clojure -M:ee:doc command-documentation
continue-on-error: false
- name: Check for changes
id: check_changes
run: |

View File

@@ -2,6 +2,8 @@ name: Check doc links
on:
pull_request:
paths:
- "docs/**"
workflow_dispatch:
jobs:
@@ -13,7 +15,7 @@ jobs:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
sparse-checkout: |
docs
@@ -27,7 +29,7 @@ jobs:
- name: Run link checker
id: link-check
uses: lycheeverse/lychee-action@v2.3.0
uses: lycheeverse/lychee-action@v2
with:
fail: true
args: docs --config ./.lychee/config.toml --offline

View File

@@ -4,18 +4,12 @@ on:
schedule:
- cron: "0 2 * * 1-5" # Every Monday through Friday at 2 AM UTC
workflow_dispatch:
inputs:
target_branch:
description: "Target branch for documentation update"
required: false
default: "release-x.57.x"
type: string
jobs:
update-release-docs:
name: Call documentation update for release branch
uses: ./.github/workflows/docs-generate-base.yml
with:
target_branch: ${{ inputs.target_branch || 'release-x.57.x' }}
target_branch: "release-x.56.x"
secrets:
automation_token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}

View File

@@ -17,7 +17,7 @@ jobs:
determine-driver-skips:
runs-on: ubuntu-latest
outputs:
skip: ${{ steps.check-branch-and-files-changed.outputs.skip }}
skip: ${{ steps.check_team_and_branch.outputs.skip }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
@@ -25,31 +25,49 @@ jobs:
if: github.event_name == 'pull_request' && github.event.pull_request.base.ref != ''
run: |
# Fetch the base branch to ensure its history is available for diffing
git fetch --depth=1 origin ${{ github.event.pull_request.base.ref }}:${{ github.event.pull_request.base.ref }}
git fetch origin ${{ github.event.pull_request.base.ref }}:${{ github.event.pull_request.base.ref }}
# Only run this if it's a pull request and the base ref exists
- name: Check if driver tests should run
id: check-branch-and-files-changed
- name: Check if tier3 drivers should run
id: check_team_and_branch
run: |
IS_MASTER_OR_RELEASE="${{ github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-') }}"
echo "Is master or release: ${IS_MASTER_OR_RELEASE}"
HAS_TEAM_LABEL="${{ contains(github.event.pull_request.labels.*.name, '.Team/Drivers') || contains(github.event.pull_request.labels.*.name, '.Team/Querying') || contains(github.event.pull_request.labels.*.name, '.Team/SemanticLayer') || contains(github.event.pull_request.labels.*.name, '.Team/DevEx') }}"
echo "Is master or release: ${IS_MASTER_OR_RELEASE}"
echo "Has '.Team/Drivers' or '.Team/Querying' or '.Team/SemanticLayer' label: ${HAS_TEAM_LABEL}"
if [[ "${IS_MASTER_OR_RELEASE}" == "true" ]]; then
echo "skip=false" >> $GITHUB_OUTPUT
echo "Merging into master or a release branch. All driver tests will run."
echo "Merging into master or a release branch. Tier 3 driver tests will run."
elif [[ "${HAS_TEAM_LABEL}" == "true" ]]; then
echo "skip=false" >> $GITHUB_OUTPUT
echo "A relevant team label is present. Tier 3 driver tests will run."
else
echo "Checking for changes in relevant modules..."
if ./bin/mage can-skip-driver-tests "${{ github.event.pull_request.base.ref }}"; then
echo 'skip=true' >> $GITHUB_OUTPUT
echo "Checking for changes in relevant folders..."
CHANGED_FILES=$(git diff --name-only $(git merge-base ${{ github.event.pull_request.base.sha }} ${{ github.sha }}) ${{ github.sha }})
# Initialize an empty array to store folders with changes
CHANGED_FOLDERS=()
for folder in "src/metabase/lib" "src/metabase/query_processor" "modules/drivers" "src/metabase/driver"; do
if echo "$CHANGED_FILES" | grep -q "$folder"; then
CHANGED_FOLDERS+=("$folder")
fi
done
if [[ ${#CHANGED_FOLDERS[@]} -gt 0 ]]; then
echo "skip=false" >> $GITHUB_OUTPUT
echo "Changes detected in the following folders: ${CHANGED_FOLDERS[*]}."
echo "Tier 3 driver tests will run."
else
echo 'skip=false' >> $GITHUB_OUTPUT
echo "skip=true" >> $GITHUB_OUTPUT
echo "No relevant team label, not merging into master/release, and no changes in relevant folders. Tier 3 driver tests will be skipped."
fi
fi
shell: bash
be-tests-athena-ee:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
timeout-minutes: 60
env:
CI: 'true'
@@ -67,7 +85,6 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Test Athena driver
id: test-driver
uses: ./.github/actions/test-driver
with:
junit-name: 'be-tests-athena-ee'
@@ -76,7 +93,7 @@ jobs:
:exclude-tags [:mb/transforms-python-test]
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -87,9 +104,8 @@ jobs:
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
be-tests-bigquery-cloud-sdk-ee:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
runs-on: ubuntu-latest
if: ${{ !inputs.skip }}
runs-on: ubuntu-22.04
timeout-minutes: 60
strategy:
fail-fast: false
@@ -130,7 +146,7 @@ jobs:
test-args: ${{ matrix.job.test-args }}
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -147,9 +163,8 @@ jobs:
logs-artifact-suffix: ${{ matrix.job.name }} ${{ matrix.version.name }}
be-tests-clickhouse-ee:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
runs-on: ubuntu-latest
if: ${{ !inputs.skip }}
runs-on: ubuntu-22.04
timeout-minutes: 60
strategy:
fail-fast: false
@@ -201,7 +216,7 @@ jobs:
test-args: ${{ matrix.job.test-args }}
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -220,7 +235,7 @@ jobs:
be-tests-druid-ee:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
timeout-minutes: 60
env:
CI: 'true'
@@ -236,7 +251,6 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Test Druid driver
id: test-driver
uses: ./.github/actions/test-driver
with:
junit-name: 'be-tests-druid-ee'
@@ -245,7 +259,7 @@ jobs:
:exclude-tags [:mb/transforms-python-test]
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -258,7 +272,7 @@ jobs:
be-tests-druid-jdbc-ee:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
timeout-minutes: 60
env:
CI: 'true'
@@ -274,7 +288,6 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Test Druid JDBC driver
id: test-driver
uses: ./.github/actions/test-driver
with:
junit-name: 'be-tests-druid-jdbc-ee'
@@ -283,7 +296,7 @@ jobs:
:exclude-tags [:mb/transforms-python-test]
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -296,7 +309,7 @@ jobs:
be-tests-databricks-ee:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
timeout-minutes: 60
env:
CI: 'true'
@@ -312,7 +325,6 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Test Databricks driver
id: test-driver
uses: ./.github/actions/test-driver
with:
junit-name: 'be-tests-databricks-ee'
@@ -321,7 +333,7 @@ jobs:
:exclude-tags [:mb/transforms-python-test]
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -334,7 +346,7 @@ jobs:
be-tests-databricks-multi-catalog-ee:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
timeout-minutes: 60
env:
CI: 'true'
@@ -351,7 +363,6 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Test Databricks driver with multi-catalog
id: test-driver
uses: ./.github/actions/test-driver
with:
junit-name: 'be-tests-databricks-multi-catalog-ee'
@@ -360,7 +371,7 @@ jobs:
:exclude-tags [:mb/transforms-python-test]
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -372,13 +383,17 @@ jobs:
be-tests-h2:
if: ${{ !inputs.skip }}
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
timeout-minutes: 60
env:
CI: 'true'
name: H2
steps:
- uses: actions/checkout@v4
- name: Setup python-runner
uses: ./.github/actions/setup-python-runner
with:
metabase-automation-token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
- name: Test H2 driver
id: test-driver
uses: ./.github/actions/test-driver
@@ -389,7 +404,7 @@ jobs:
:exclude-tags [:mb/transforms-python-test]
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -398,11 +413,16 @@ jobs:
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
- name: Cleanup python-runner
if: always()
uses: ./.github/actions/cleanup-python-runner
with:
upload-logs: ${{ steps.test-driver.outcome == 'failure' }}
logs-artifact-suffix: ${{ matrix.job.name }} ${{ matrix.version.name }}
be-tests-mysql-mariadb:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
runs-on: ubuntu-latest
if: ${{ !inputs.skip }}
runs-on: ubuntu-22.04
timeout-minutes: 60
strategy:
fail-fast: false
@@ -413,25 +433,21 @@ jobs:
image: circleci/mariadb:10.2.23
env:
enable-ssl-tests: 'false'
enable-aws-iam-tests: 'false'
- name: MariaDB Latest
junit-name: be-tests-mariadb-latest-ee
image: circleci/mariadb:latest
env:
enable-ssl-tests: 'false'
enable-aws-iam-tests: 'false'
- name: MySQL 8.0
junit-name: be-tests-mysql-8-0-ee
image: mysql:8.0
env:
enable-ssl-tests: 'false'
enable-aws-iam-tests: 'false'
- name: MySQL Latest
junit-name: be-tests-mysql-latest-ee
image: mysql:latest
env:
enable-ssl-tests: 'true'
enable-aws-iam-tests: 'true'
job:
- name: Driver Tests
test-args: >-
@@ -490,14 +506,6 @@ jobs:
MB_MYSQL_SSL_TEST_SSL_CERT: ${{ secrets.MB_MYSQL_SSL_TEST_SSL_CERT }}
MB_MYSQL_SSL_TEST_USER: metabase
MB_MYSQL_SSL_TEST_PASSWORD: ${{ secrets.MYSQL_RDS_SSL_INSTANCE_PASSWORD }}
MB_MYSQL_AWS_IAM_TEST: ${{ matrix.version.env.enable-aws-iam-tests }}
MB_MYSQL_AWS_IAM_TEST_HOST: ${{ secrets.MYSQL_AURORA_IAM_INSTANCE_HOST }}
MB_MYSQL_AWS_IAM_TEST_PORT: 3306
MB_MYSQL_AWS_IAM_TEST_USER: my_iam_user
MB_MYSQL_AWS_IAM_TEST_DBNAME: test_db
MB_MYSQL_AWS_IAM_TEST_SSL_CERT: trust
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_AURORA_IAM_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_AURORA_IAM_SECRET_ACCESS_KEY }}
# for historic reasons (I don't want to mess around with required jobs) the job name should be something like
# "be-tests-mariadb-10-2-ee (0)"
name: "${{ matrix.version.name }} ${{ matrix.job.name }}"
@@ -518,7 +526,7 @@ jobs:
:exclude-tags '[ ${{ matrix.job.exclude-tag }} ${{ env.__ADDITIONAL_EXCLUDED_TAG__ }}]'
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -537,7 +545,7 @@ jobs:
be-tests-mongo:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
timeout-minutes: 60
strategy:
fail-fast: false
@@ -587,7 +595,7 @@ jobs:
test-args: ${{ matrix.job.test-args }}
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -606,7 +614,7 @@ jobs:
be-tests-mongo-ssl:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
timeout-minutes: 60
strategy:
fail-fast: false
@@ -650,7 +658,6 @@ jobs:
curl https://raw.githubusercontent.com/metabase/metabase-qa/master/dbs/mongo/certificates/metaca.crt \
-o ./test_resources/ssl/mongo/metaca.crt
- name: Test ${{ matrix.version.name }}
id: test-driver
uses: ./.github/actions/test-driver
with:
junit-name: ${{ matrix.version.junit-name }}
@@ -659,7 +666,7 @@ jobs:
:only-tags [:mb/driver-tests]
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -672,7 +679,7 @@ jobs:
be-tests-mongo-sharded-cluster-ee:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
timeout-minutes: 60
env:
CI: 'true'
@@ -686,7 +693,6 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Test MongoDB driver (Sharded cluster)
id: test-driver
uses: ./.github/actions/test-driver
with:
junit-name: 'be-tests-mongo-sharded-cluster-ee'
@@ -694,7 +700,7 @@ jobs:
:only metabase.driver.mongo.sharded-cluster-test
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -707,7 +713,7 @@ jobs:
be-tests-oracle:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
timeout-minutes: 60
strategy:
fail-fast: false
@@ -759,7 +765,6 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Test ${{ matrix.version.name }}
id: test-driver
uses: ./.github/actions/test-driver
with:
junit-name: ${{ matrix.version.junit-name }}
@@ -768,7 +773,7 @@ jobs:
:exclude-tags [:mb/transforms-python-test]
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -779,9 +784,8 @@ jobs:
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
be-tests-postgres:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
runs-on: ubuntu-latest
if: ${{ !inputs.skip }}
runs-on: ubuntu-22.04
timeout-minutes: 60
strategy:
fail-fast: false
@@ -792,13 +796,11 @@ jobs:
docker-image: postgres:12-alpine
env:
enable-ssl-tests: 'false'
enable-aws-iam-tests: 'false'
- name: Postgres Latest
junit-name: postgres-latest-ee
docker-image: postgres:latest
env:
enable-ssl-tests: 'true'
enable-aws-iam-tests: 'true'
job:
- name: Driver Tests
test-args: >-
@@ -838,13 +840,6 @@ jobs:
MB_POSTGRES_SSL_TEST_SSL: ${{ matrix.version.env.enable-ssl-tests }}
MB_POSTGRES_SSL_TEST_SSL_MODE: verify-full
MB_POSTGRES_SSL_TEST_SSL_ROOT_CERT_PATH: 'test-resources/certificates/us-east-2-bundle.pem'
MB_POSTGRES_AWS_IAM_TEST: ${{ matrix.version.env.enable-aws-iam-tests }}
MB_POSTGRES_AWS_IAM_TEST_HOST: ${{ secrets.POSTGRES_AURORA_IAM_INSTANCE_HOST }}
MB_POSTGRES_AWS_IAM_TEST_PORT: 5432
MB_POSTGRES_AWS_IAM_TEST_USER: my_iam_user
MB_POSTGRES_AWS_IAM_TEST_DBNAME: test_db
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_AURORA_IAM_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_AURORA_IAM_SECRET_ACCESS_KEY }}
services:
postgres:
image: ${{ matrix.version.docker-image }}
@@ -871,7 +866,7 @@ jobs:
:exclude-tags '[ ${{ matrix.job.exclude-tag }} ${{ env.__ADDITIONAL_EXCLUDED_TAG__ }}]'
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -890,7 +885,7 @@ jobs:
be-tests-presto-jdbc-ee:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
timeout-minutes: 60
env:
CI: 'true'
@@ -939,7 +934,6 @@ jobs:
-file /tmp/presto-ssl-ca.der
-trustcacerts
- name: Test Presto JDBC driver
id: test-driver
uses: ./.github/actions/test-driver
with:
junit-name: 'be-tests-presto-jdbc-ee'
@@ -948,7 +942,7 @@ jobs:
:exclude-tags [:mb/transforms-python-test]
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -959,9 +953,8 @@ jobs:
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
be-tests-redshift-ee:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
runs-on: ubuntu-latest
if: ${{ !inputs.skip }}
runs-on: ubuntu-22.04
timeout-minutes: 60
strategy:
fail-fast: false
@@ -1011,7 +1004,7 @@ jobs:
${{ matrix.job.test-args }}
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: ${{ !matrix.job.skip && !cancelled() && steps.test-driver.outputs.quarantine-skipped != 'true' }}
if: ${{ !matrix.job.skip && !cancelled() }}
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -1027,80 +1020,80 @@ jobs:
upload-logs: ${{ steps.test-driver.outcome == 'failure' }}
logs-artifact-suffix: ${{ matrix.job.name }} ${{ matrix.version.name }}
be-tests-snowflake-ee:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
job:
- name: Driver Tests
test-args: >-
:only-tags [:mb/driver-tests]
:exclude-tags [:mb/transforms-python-test]
# dan sutton 2025-10-23: disable snowflake because of account
# issues. https://linear.app/metabase/issue/DEV-997/reenable-snowflake-in-ci. Note that the transforms tests were
# disabled previously
# be-tests-snowflake-ee:
# if: ${{ !inputs.skip }}
# runs-on: ubuntu-22.04
# timeout-minutes: 60
# strategy:
# fail-fast: false
# matrix:
# job:
# - name: Driver Tests
# test-args: >-
# :only-tags [:mb/driver-tests]
# :exclude-tags [:mb/transforms-python-test]
# DS 2025-09-26 temporarily disabled while we diagnose flakes
# - name: Transforms Python Tests
# test-args: >-
# :only-tags [:mb/transforms-python-test]
# needs-python-runner: true
env:
CI: 'true'
DRIVERS: snowflake
MB_SNOWFLAKE_TEST_USER: METABASE CI
MB_SNOWFLAKE_TEST_ACCOUNT: ${{ secrets.MB_SNOWFLAKE_TEST_ACCOUNT }}
MB_SNOWFLAKE_TEST_PASSWORD: ${{ secrets.MB_SNOWFLAKE_TEST_PASSWORD }}
MB_SNOWFLAKE_TEST_PRIVATE_KEY: ${{ secrets.MB_SNOWFLAKE_TEST_PRIVATE_KEY }}
MB_SNOWFLAKE_TEST_WAREHOUSE: ${{ secrets.MB_SNOWFLAKE_TEST_WAREHOUSE }}
MB_SNOWFLAKE_TEST_PK_USER: METABASE PK
MB_SNOWFLAKE_TEST_PK_PRIVATE_KEY: ${{ secrets.MB_SNOWFLAKE_TEST_PK_PRIVATE_KEY }}
MB_SNOWFLAKE_TEST_PK_PRIVATE_KEY_2: ${{ secrets.MB_SNOWFLAKE_TEST_PK_PRIVATE_KEY_2 }}
MB_SNOWFLAKE_TEST_PK_PUBLIC_KEY: ${{ secrets.MB_SNOWFLAKE_TEST_PK_PUBLIC_KEY }}
MB_SNOWFLAKE_TEST_PK_PUBLIC_KEY_2: ${{ secrets.MB_SNOWFLAKE_TEST_PK_PUBLIC_KEY_2 }}
# env:
# CI: 'true'
# DRIVERS: snowflake
# MB_SNOWFLAKE_TEST_USER: METABASE CI
# MB_SNOWFLAKE_TEST_ACCOUNT: ${{ secrets.MB_SNOWFLAKE_TEST_ACCOUNT }}
# MB_SNOWFLAKE_TEST_PASSWORD: ${{ secrets.MB_SNOWFLAKE_TEST_PASSWORD }}
# MB_SNOWFLAKE_TEST_WAREHOUSE: ${{ secrets.MB_SNOWFLAKE_TEST_WAREHOUSE }}
# MB_SNOWFLAKE_TEST_PK_USER: METABASE PK
# MB_SNOWFLAKE_TEST_PK_PRIVATE_KEY: ${{ secrets.MB_SNOWFLAKE_TEST_PK_PRIVATE_KEY }}
# MB_SNOWFLAKE_TEST_PK_PRIVATE_KEY_2: ${{ secrets.MB_SNOWFLAKE_TEST_PK_PRIVATE_KEY_2 }}
# MB_SNOWFLAKE_TEST_PK_PUBLIC_KEY: ${{ secrets.MB_SNOWFLAKE_TEST_PK_PUBLIC_KEY }}
# MB_SNOWFLAKE_TEST_PK_PUBLIC_KEY_2: ${{ secrets.MB_SNOWFLAKE_TEST_PK_PUBLIC_KEY_2 }}
# RSA Role testing:
MB_SNOWFLAKE_TEST_RSA_ROLE_TEST_DEFAULT_USER: RSA_ROLE_TEST_DEFAULT_USER
MB_SNOWFLAKE_TEST_RSA_ROLE_TEST_CUSTOM_USER: RSA_ROLE_TEST_CUSTOM_USER
MB_SNOWFLAKE_TEST_RSA_ROLE_TEST_ROLE: RSA_ROLE_TEST_ROLE
MB_SNOWFLAKE_TEST_RSA_ROLE_TEST_DB: RSA_ROLE_TEST_DB
name: Snowflake ${{ matrix.job.name }}
steps:
- uses: actions/checkout@v4
- name: Setup python-runner
if: ${{ matrix.job.needs-python-runner == true }}
uses: ./.github/actions/setup-python-runner
with:
metabase-automation-token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
- name: Test Snowflake driver
id: test-driver
uses: ./.github/actions/test-driver
with:
junit-name: 'be-tests-snowflake-ee'
test-args: ${{ matrix.job.test-args }}
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
- name: Cleanup python-runner
if: ${{ always() && matrix.job.needs-python-runner == true }}
uses: ./.github/actions/cleanup-python-runner
with:
upload-logs: ${{ steps.test-driver.outcome == 'failure' }}
logs-artifact-suffix: ${{ matrix.job.name }} ${{ matrix.version.name }}
# # RSA Role testing:
# MB_SNOWFLAKE_TEST_RSA_ROLE_TEST_DEFAULT_USER: RSA_ROLE_TEST_DEFAULT_USER
# MB_SNOWFLAKE_TEST_RSA_ROLE_TEST_CUSTOM_USER: RSA_ROLE_TEST_CUSTOM_USER
# MB_SNOWFLAKE_TEST_RSA_ROLE_TEST_ROLE: RSA_ROLE_TEST_ROLE
# MB_SNOWFLAKE_TEST_RSA_ROLE_TEST_DB: RSA_ROLE_TEST_DB
# name: Snowflake ${{ matrix.job.name }}
# steps:
# - uses: actions/checkout@v4
# - name: Setup python-runner
# if: ${{ matrix.job.needs-python-runner == true }}
# uses: ./.github/actions/setup-python-runner
# with:
# metabase-automation-token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
# - name: Test Snowflake driver
# id: test-driver
# uses: ./.github/actions/test-driver
# with:
# junit-name: 'be-tests-snowflake-ee'
# test-args: ${{ matrix.job.test-args }}
# - name: Upload Test Results
# uses: ./.github/actions/upload-test-results
# if: always()
# with:
# input-path: ./target/junit/
# output-name: ${{ github.job }}
# bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
# aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
# aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
# aws-region: ${{ vars.AWS_REGION }}
# trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
# - name: Cleanup python-runner
# if: ${{ always() && matrix.job.needs-python-runner == true }}
# uses: ./.github/actions/cleanup-python-runner
# with:
# upload-logs: ${{ steps.test-driver.outcome == 'failure' }}
# logs-artifact-suffix: ${{ matrix.job.name }} ${{ matrix.version.name }}
be-tests-sparksql-ee:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
timeout-minutes: 60
env:
CI: 'true'
@@ -1114,7 +1107,6 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Test Spark driver
id: test-driver
uses: ./.github/actions/test-driver
with:
junit-name: 'be-tests-sparksql-ee'
@@ -1123,7 +1115,7 @@ jobs:
:exclude-tags [:mb/transforms-python-test]
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -1136,7 +1128,7 @@ jobs:
be-tests-sqlite-ee:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
timeout-minutes: 60
env:
CI: 'true'
@@ -1145,7 +1137,6 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Test SQLite driver
id: test-driver
uses: ./.github/actions/test-driver
with:
junit-name: 'be-tests-sqlite-ee'
@@ -1154,7 +1145,7 @@ jobs:
:exclude-tags [:mb/transforms-python-test]
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -1167,7 +1158,7 @@ jobs:
be-tests-sqlserver:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
timeout-minutes: 30
strategy:
fail-fast: false
@@ -1219,7 +1210,7 @@ jobs:
test-args: ${{ matrix.job.test-args }}
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -1239,7 +1230,7 @@ jobs:
# be-tests-starburst-ee:
# needs: determine-driver-skips
# if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
# runs-on: ubuntu-latest
# runs-on: ubuntu-22.04
# timeout-minutes: 60
# env:
# CI: 'true'
@@ -1277,7 +1268,7 @@ jobs:
login-to-ecr:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
timeout-minutes: 15
permissions:
id-token: write
@@ -1307,7 +1298,7 @@ jobs:
be-tests-vertica-ee:
needs: [determine-driver-skips, login-to-ecr]
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
timeout-minutes: 60
env:
CI: 'true'
@@ -1326,7 +1317,6 @@ jobs:
- name: Make plugins directory
run: mkdir plugins
- name: Test Vertica driver
id: test-driver
uses: ./.github/actions/test-driver
with:
junit-name: 'be-tests-vertica-ee'
@@ -1335,7 +1325,7 @@ jobs:
:exclude-tags [:mb/transforms-python-test]
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -1362,7 +1352,8 @@ jobs:
- be-tests-postgres
- be-tests-presto-jdbc-ee
- be-tests-redshift-ee
- be-tests-snowflake-ee
# dan: disabled for account reasons. https://linear.app/metabase/issue/DEV-997/reenable-snowflake-in-ci
# - be-tests-snowflake-ee
- be-tests-sparksql-ee
- be-tests-sqlite-ee
- be-tests-sqlserver

View File

@@ -0,0 +1,184 @@
# Thi workflow does the backward compatibility tests
# between the Embedding SDK package of version N and Embedding SDK Bundle of version N - 1
# N - 1 is a target of a PR
name: Embedding SDK backward-compatibility E2E Component Tests
on:
workflow_call:
inputs:
cached-embedding-sdk-dist-artifact-name:
required: true
type: string
jobs:
files-changed:
name: "SDK backward-compatibility E2E > Check which files changed"
runs-on: ubuntu-22.04
timeout-minutes: 3
outputs:
e2e_embedding_sdk: ${{ steps.changes.outputs.e2e_embedding_sdk }}
steps:
- uses: actions/checkout@v4
- name: Test which files changed
uses: dorny/paths-filter@v3.0.0
id: changes
with:
token: ${{ github.token }}
filters: .github/file-paths.yaml
decide-target-ref:
runs-on: ubuntu-latest
outputs:
target-ref: ${{ steps.get-target-ref.outputs.value }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Decide ref to checkout
id: get-target-ref
run: |
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
echo "value=${{ github.base_ref }}" >> "$GITHUB_OUTPUT"
else
echo "value=$(git rev-parse HEAD^1)" >> "$GITHUB_OUTPUT"
fi
# if this is a test on a release branch, we need to check the build requirements
get-build-requirements:
name: "SDK backward-compatibility E2E > [Backport only] Get build requirements"
needs: [decide-target-ref]
if: |
!cancelled() &&
contains(github.base_ref || github.ref, 'release-x')
runs-on: ubuntu-22.04
timeout-minutes: 10
outputs:
java_version: ${{ fromJson(steps.dependencies.outputs.result).java_version }}
node_version: ${{ fromJson(steps.dependencies.outputs.result).node_version }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.decide-target-ref.outputs.target-ref }}
sparse-checkout: release
- name: Prepare build scripts
run: cd ${{ github.workspace }}/release && yarn && yarn build
- name: Get build dependencies
uses: actions/github-script@v7
id: dependencies
with:
script: | # js
const {
getBuildRequirements,
getVersionFromReleaseBranch,
} = require('${{ github.workspace }}/release/dist/index.cjs');
const targetBranchName = '${{ github.base_ref || github.ref }}';
const version = getVersionFromReleaseBranch(targetBranchName);
const requirements = getBuildRequirements(version);
return {
java_version: requirements.java,
node_version: requirements.node,
};
e2e-tests:
name: "SDK backward-compatibility E2E > Run e2e tests"
needs: [files-changed, decide-target-ref, get-build-requirements]
if: |
!cancelled() &&
needs.files-changed.outputs.e2e_embedding_sdk == 'true'
runs-on: ubuntu-22.04
timeout-minutes: 45
env:
MB_EDITION: ee
DISPLAY: ""
# Any env starting with `CYPRESS_` will be available to all Cypress tests via `Cypress.env()`
# Example: you can get `CYPRESS_FOO` with `Cypress.env("FOO")`
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ secrets.MB_ALL_FEATURES_TOKEN }}
CYPRESS_MB_STARTER_CLOUD_TOKEN: ${{ secrets.MB_STARTER_CLOUD_TOKEN }}
CYPRESS_MB_PRO_CLOUD_TOKEN: ${{ secrets.MB_PRO_CLOUD_TOKEN }}
CYPRESS_MB_PRO_SELF_HOSTED_TOKEN: ${{ secrets.MB_PRO_SELF_HOSTED_TOKEN }}
CYPRESS_PULL_REQUEST_ID: ${{ github.event.pull_request.number || '' }}
COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title || github.event.head_commit.message || github.event.head.sha }}
TZ: US/Pacific # to make node match the instance tz
CYPRESS_CI: true
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.decide-target-ref.outputs.target-ref }}
- name: Prepare back-end environment
uses: ./.github/actions/prepare-backend
with:
java-version: "${{ needs.get-build-requirements.outputs.java_version || 21 }}"
- name: Prepare front-end environment
uses: ./.github/actions/prepare-frontend
with:
node-version: "${{ needs.get-build-requirements.outputs.node_version }}"
- name: Build uberjar with ./bin/build.sh
run: ./bin/build.sh
- name: Retrieve Embedding SDK dist artifact
uses: actions/download-artifact@v4
with:
name: ${{ inputs.cached-embedding-sdk-dist-artifact-name }}
path: ${{ github.workspace }}/resources/embedding-sdk
- name: Prepare Cypress environment
id: cypress-prep
uses: ./.github/actions/prepare-cypress
- name: Run Metabase
run: node e2e/runner/run_cypress_ci.js start
- name: Make app db snapshot
run: node e2e/runner/run_cypress_ci.js snapshot --browser ${{ steps.cypress-prep.outputs.chrome-path }}
- name: Run component tests for Embedding SDK
run: |
node e2e/runner/run_cypress_ci.js component \
--browser ${{ steps.cypress-prep.outputs.chrome-path }} \
--env grepTags="-@skip-backward-compatibility",grepOmitFiltered=true
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always()
with:
input-path: ./target/junit
output-name: e2e-embedding-sdk
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
- name: Upload Cypress Artifacts upon failure
uses: actions/upload-artifact@v4
if: failure()
with:
name: cypress-recording-embedding-sdk-cross-version
path: |
./cypress
./logs/test.log
if-no-files-found: ignore
- name: Publish Summary
if: failure()
uses: actions/github-script@v7
with:
script: | #js
const {
generateReport,
parseReport,
formatSummary
} = require("./.github/scripts/handle-mochawesome-report.js");
const report = await generateReport();
const results = parseReport(report);
const summary = formatSummary(results);
await core.summary.addRaw(summary).write();

View File

@@ -0,0 +1,203 @@
name: E2E Component Tests for Embedding SDK
on:
workflow_call:
inputs:
cached-embedding-sdk-dist-artifact-name:
required: true
type: string
jobs:
files-changed:
name: "SDK e2e > Check which files changed"
runs-on: ubuntu-22.04
timeout-minutes: 3
outputs:
e2e_embedding_sdk: ${{ steps.changes.outputs.e2e_embedding_sdk }}
steps:
- uses: actions/checkout@v4
- name: Test which files changed
uses: dorny/paths-filter@v3.0.0
id: changes
with:
token: ${{ github.token }}
filters: .github/file-paths.yaml
# if this is a test on a release branch, we need to check the build requirements
get-build-requirements:
name: "SDK e2e > [Backport only] Get build requirements"
if: |
!cancelled() &&
contains(github.base_ref || github.ref, 'release-x')
runs-on: ubuntu-22.04
timeout-minutes: 10
outputs:
java_version: ${{ fromJson(steps.dependencies.outputs.result).java_version }}
node_version: ${{ fromJson(steps.dependencies.outputs.result).node_version }}
steps:
- uses: actions/checkout@v4
with:
sparse-checkout: release
- name: Prepare build scripts
run: cd ${{ github.workspace }}/release && yarn && yarn build
- name: Get build dependencies
uses: actions/github-script@v7
id: dependencies
with:
script: | # js
const {
getBuildRequirements,
getVersionFromReleaseBranch,
} = require('${{ github.workspace }}/release/dist/index.cjs');
const targetBranchName = '${{ github.base_ref || github.ref }}';
const version = getVersionFromReleaseBranch(targetBranchName);
const requirements = getBuildRequirements(version);
return {
java_version: requirements.java,
node_version: requirements.node,
};
build-uberjar:
name: "SDK e2e > Build uberjar"
needs: [files-changed, get-build-requirements]
if: |
!cancelled() &&
needs.files-changed.outputs.e2e_embedding_sdk == 'true'
runs-on: ubuntu-22.04
timeout-minutes: 25
env:
MB_EDITION: ee
INTERACTIVE: false
# make sure that builds on release branches get licenses, because we use them for releases
SKIP_LICENSES: ${{ github.event_name == 'pull_request' }}
steps:
- uses: actions/checkout@v4
- name: Prepare front-end environment
uses: ./.github/actions/prepare-frontend
with:
node-version: "${{ needs.get-build-requirements.outputs.node_version }}"
- name: Prepare back-end environment
uses: ./.github/actions/prepare-backend
with:
java-version: "${{ needs.get-build-requirements.outputs.java_version || 21 }}"
- name: Build uberjar with ./bin/build.sh
run: ./bin/build.sh
- name: Prepare uberjar artifact
uses: ./.github/actions/prepare-uberjar-artifact
with:
name: metabase-ee-${{ github.event.pull_request.head.sha || github.sha }}-uberjar-sdk-tests
e2e-tests:
name: "SDK e2e > Run e2e tests (React ${{ matrix.react-version }})"
needs: [get-build-requirements, build-uberjar]
if: |
!cancelled() &&
needs.build-uberjar.result == 'success'
runs-on: ubuntu-22.04
timeout-minutes: 45
strategy:
matrix:
react-version: [18, 19]
fail-fast: false
env:
MB_EDITION: ee
DISPLAY: ""
# Any env starting with `CYPRESS_` will be available to all Cypress tests via `Cypress.env()`
# Example: you can get `CYPRESS_FOO` with `Cypress.env("FOO")`
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ secrets.MB_ALL_FEATURES_TOKEN }}
CYPRESS_MB_STARTER_CLOUD_TOKEN: ${{ secrets.MB_STARTER_CLOUD_TOKEN }}
CYPRESS_MB_PRO_CLOUD_TOKEN: ${{ secrets.MB_PRO_CLOUD_TOKEN }}
CYPRESS_MB_PRO_SELF_HOSTED_TOKEN: ${{ secrets.MB_PRO_SELF_HOSTED_TOKEN }}
CYPRESS_PULL_REQUEST_ID: ${{ github.event.pull_request.number || '' }}
CYPRESS_IS_EMBEDDING_SDK: "true"
COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title || github.event.head_commit.message || github.event.head.sha }}
TZ: US/Pacific # to make node match the instance tz
CYPRESS_CI: true
steps:
- uses: actions/checkout@v4
- name: Retrieve uberjar artifact for ee
uses: actions/download-artifact@v4
with:
name: metabase-ee-${{ github.event.pull_request.head.sha || github.sha }}-uberjar-sdk-tests
- name: Prepare front-end environment
uses: ./.github/actions/prepare-frontend
- name: Prepare JDK ${{ needs.get-build-requirements.outputs.java_version || 21 }}
uses: actions/setup-java@v4
with:
java-version: ${{ needs.get-build-requirements.outputs.java_version || 21 }}
distribution: "temurin"
- name: Prepare Cypress environment
id: cypress-prep
uses: ./.github/actions/prepare-cypress
- name: Prepare back-end environment
uses: ./.github/actions/prepare-backend
- name: Retrieve Embedding SDK dist artifact
uses: actions/download-artifact@v4
with:
name: ${{ inputs.cached-embedding-sdk-dist-artifact-name }}
path: ${{ github.workspace }}/resources/embedding-sdk
- name: Run Metabase
run: node e2e/runner/run_cypress_ci.js start
- name: Make app db snapshot
run: node e2e/runner/run_cypress_ci.js snapshot --browser ${{ steps.cypress-prep.outputs.chrome-path }}
# Has to be run after the app db snapshot is made, as it is not compatible with React 19
- name: Change React version to ${{ matrix.react-version }}
run: ./bin/embedding-sdk/change-react-version.bash ${{ matrix.react-version }}
- name: Run component tests for Embedding SDK
run: node e2e/runner/run_cypress_ci.js component --browser ${{ steps.cypress-prep.outputs.chrome-path }}
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always()
with:
input-path: ./target/junit
output-name: e2e-embedding-sdk-react-${{ matrix.react-version }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
- name: "SDK e2e > Upload Cypress Artifacts upon failure"
uses: actions/upload-artifact@v4
if: failure()
with:
name: cypress-recording-embedding-sdk-react-${{ matrix.react-version }}
path: |
./cypress
./logs/test.log
if-no-files-found: ignore
- name: "SDK e2e > Publish Summary"
if: failure()
uses: actions/github-script@v7
with:
script: | #js
const {
generateReport,
parseReport,
formatSummary
} = require("./.github/scripts/handle-mochawesome-report.js");
const report = await generateReport();
const results = parseReport(report);
const summary = formatSummary(results);
await core.summary.addRaw(summary).write();

171
.github/workflows/e2e-cross-version.yml vendored Normal file
View File

@@ -0,0 +1,171 @@
name: E2E Cross-version Tests
on:
schedule:
- cron: '0 9 * * 1-5'
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
jobs:
files-changed:
name: Check which files changed
# don't run this workflow on a cron for forks
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
runs-on: ubuntu-22.04
timeout-minutes: 3
outputs:
e2e_cross_version: ${{ steps.changes.outputs.e2e_cross_version }}
steps:
- uses: actions/checkout@v4
- name: Test which files changed
uses: dorny/paths-filter@v3.0.0
id: changes
with:
token: ${{ github.token }}
filters: .github/file-paths.yaml
cross-version-testing:
needs: files-changed
if: ${{ needs.files-changed.outputs.e2e_cross_version == 'true' || github.event_name == 'schedule'}}
runs-on: ubuntu-22.04
timeout-minutes: 20
strategy:
matrix:
version: [
# Major OSS upgrade
{ source: v0.42.2, target: v0.48.7 },
# OSS upgrade
{ source: v0.41.3.1, target: v0.42.2 },
# EE upgrade
{ source: v1.43.4, target: v1.44.0 },
# EE downgrade
{ source: v1.42.0, target: v1.41.3.1 },
# Cross-edition upgrade
{ source: v0.41.3.1, target: v1.42.2 }
]
fail-fast: false
env:
CROSS_VERSION_SOURCE: ${{ matrix.version.source }}
CROSS_VERSION_TARGET: ${{ matrix.version.target }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set source Docker image to OSS
run: |
echo "DOCKER_SOURCE_IMAGE=metabase/metabase:${{ matrix.version.source }}" >> $GITHUB_ENV
if: startsWith(matrix.version.source, 'v0')
- name: Set source Docker image to EE
run: |
echo "DOCKER_SOURCE_IMAGE=metabase/metabase-enterprise:${{ matrix.version.source }}" >> $GITHUB_ENV
if: startsWith(matrix.version.source, 'v1')
- name: Prepare front-end environment
uses: ./.github/actions/prepare-frontend
- name: Start Metabase ${{ matrix.version.source }}
run: |
docker run -d \
-v $PWD/my-metabase:/metabase.db \
-p 3000:3000 \
-e CYPRESS_MB_ALL_FEATURES_TOKEN=${{ secrets.MB_ALL_FEATURES_TOKEN }} \
-e CYPRESS_MB_STARTER_CLOUD_TOKEN=${{ secrets.MB_STARTER_CLOUD_TOKEN }} \
-e CYPRESS_MB_PRO_CLOUD_TOKEN=${{ secrets.MB_PRO_CLOUD_TOKEN }} \
-e CYPRESS_MB_PRO_SELF_HOSTED_TOKEN=${{ secrets.MB_PRO_SELF_HOSTED_TOKEN }} \
--name metabase-${{ matrix.version.source }} \
${{ env.DOCKER_SOURCE_IMAGE }}
- name: Wait for Metabase to start on port 3000
run: while ! curl -s 'http://localhost:3000/api/health' | grep '{"status":"ok"}'; do sleep 1; done
- name: Prepare Cypress environment
id: cypress-prep
uses: ./.github/actions/prepare-cypress
- name: Run Cypress on the source
run: |
yarn cypress run \
--browser ${{ steps.cypress-prep.outputs.chrome-path }} \
--config-file e2e/test/scenarios/cross-version/source/shared/cross-version-source.config.js \
--spec e2e/test/scenarios/cross-version/source/**/*.cy.spec.js
- name: Stop Metabase ${{ matrix.version.source }}
run: docker stop metabase-${{ matrix.version.source }}
- name: Set target Docker image to OSS
run: |
echo "DOCKER_TARGET_IMAGE=metabase/metabase:${{ matrix.version.source }}" >> $GITHUB_ENV
if: startsWith(matrix.version.source, 'v0')
- name: Set target Docker image to EE
run: |
echo "DOCKER_TARGET_IMAGE=metabase/metabase-enterprise:${{ matrix.version.source }}" >> $GITHUB_ENV
if: startsWith(matrix.version.source, 'v1')
- name: Start Metabase ${{ matrix.version.target }}
run: |
docker run -d \
-v $PWD/my-metabase:/metabase.db \
-p 3001:3000 \
-e CYPRESS_MB_ALL_FEATURES_TOKEN=${{ secrets.MB_ALL_FEATURES_TOKEN }} \
-e CYPRESS_MB_STARTER_CLOUD_TOKEN=${{ secrets.MB_STARTER_CLOUD_TOKEN }} \
-e CYPRESS_MB_PRO_CLOUD_TOKEN=${{ secrets.MB_PRO_CLOUD_TOKEN }} \
-e CYPRESS_MB_PRO_SELF_HOSTED_TOKEN=${{ secrets.MB_PRO_SELF_HOSTED_TOKEN }} \
--name metabase-${{ matrix.version.target }} \
${{ env.DOCKER_TARGET_IMAGE }}
- name: Wait for Metabase to start on port 3001
run: while ! curl -s 'http://localhost:3001/api/health' | grep '{"status":"ok"}'; do sleep 1; done
- name: Run Cypress on the target
run: |
yarn cypress run \
--browser ${{ steps.cypress-prep.outputs.chrome-path }} \
--config-file e2e/test/scenarios/cross-version/target/shared/cross-version-target.config.js \
--spec e2e/test/scenarios/cross-version/target/**/*.cy.spec.js
- name: Upload Cypress Artifacts upon failure
uses: actions/upload-artifact@v4
if: failure()
with:
name: cypress-artifacts-${{ matrix.version.source}}-to-${{ matrix.version.target }}
path: |
./cypress
if-no-files-found: ignore
notify-on-failure:
needs: cross-version-testing
if: ${{ always() && contains(needs.*.result, 'failure') && github.event_name == 'schedule' }}
name: Cross-version test failure
runs-on: ubuntu-22.04
timeout-minutes: 5
steps:
- uses: actions/setup-node@v4
with:
node-version: lts/Jod # 22.x.x
- run: npm install @slack/web-api
- name: notify slack of failure
uses: actions/github-script@v7
with:
script: | # js
const FAILED_RUN_URL = "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}";
const REF_NAME = "${{ github.ref_name }}";
// notify slack of failure
const { WebClient } = require('@slack/web-api');
const slack = new WebClient('${{ secrets.SLACK_BOT_TOKEN }}');
await slack.chat.postMessage({
channel: 'engineering-ci',
text: 'Cross-version Tests Have Failed',
blocks: [
{
"type": "header",
"text": {
"type": "plain_text",
"text": `:broken_heart: Cross-version tests are failing on ${REF_NAME}`,
"emoji": true,
}
},
],
attachments: [{
color: "#BF40BF",
blocks: [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": `See <${FAILED_RUN_URL}|failing run details>.`
}
},
]
}]
});

View File

@@ -1,14 +1,78 @@
name: "[NOT REQUIRED] SDK Compatibility tests"
on:
workflow_call:
push:
branches:
- "master"
- "release-**"
pull_request:
types: [opened, synchronize, reopened]
branches:
- "master"
- "release-**"
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
jobs:
files-changed:
name: Check which files changed
runs-on: ubuntu-22.04
timeout-minutes: 3
outputs:
e2e_embedding_sdk_compatibility: ${{ steps.changes.outputs.e2e_embedding_sdk_compatibility }}
steps:
- uses: actions/checkout@v4
- name: Test which files changed
uses: dorny/paths-filter@v3.0.0
id: changes
with:
token: ${{ github.token }}
filters: .github/file-paths.yaml
# if this is a test on a release branch, we need to check the build requirements
get-build-requirements:
if: |
!cancelled() &&
contains(github.base_ref || github.ref, 'release-x')
runs-on: ubuntu-22.04
timeout-minutes: 10
outputs:
java_version: ${{ fromJson(steps.dependencies.outputs.result).java_version }}
node_version: ${{ fromJson(steps.dependencies.outputs.result).node_version }}
steps:
- uses: actions/checkout@v4
with:
sparse-checkout: release
- name: Prepare build scripts
run: cd ${{ github.workspace }}/release && yarn && yarn build
- name: Get build dependencies
uses: actions/github-script@v7
id: dependencies
with:
script: | # js
const {
getBuildRequirements,
getVersionFromReleaseBranch,
} = require('${{ github.workspace }}/release/dist/index.cjs');
const targetBranchName = '${{ github.base_ref || github.ref }}';
const version = getVersionFromReleaseBranch(targetBranchName);
const requirements = getBuildRequirements(version);
return {
java_version: requirements.java,
node_version: requirements.node,
};
get-sample-apps-data:
if: |
!cancelled()
runs-on: ubuntu-22.04
timeout-minutes: 10
env:
# we must support the last two stable releases
# we must support last two stable releases
SUPPORTED_RELEASES_COUNT: 2
TEST_SUITES: |
[
@@ -81,6 +145,51 @@ jobs:
matrix: []
};
build:
needs: [files-changed, get-build-requirements, get-sample-apps-data]
if: |
!cancelled() &&
needs.files-changed.outputs.e2e_embedding_sdk_compatibility == 'true' &&
fromJSON(needs.get-sample-apps-data.outputs.matrix)[0] != null
runs-on: ubuntu-22.04
timeout-minutes: 25
env:
MB_EDITION: ee
INTERACTIVE: false
# make sure that builds on release branches get licenses, because we use them for releases
SKIP_LICENSES: ${{ github.event_name == 'pull_request' }}
steps:
- uses: actions/checkout@v4
- name: Prepare front-end environment
uses: ./.github/actions/prepare-frontend
with:
node-version: "${{ needs.get-build-requirements.outputs.node_version }}"
- name: Prepare back-end environment
uses: ./.github/actions/prepare-backend
with:
java-version: "${{ needs.get-build-requirements.outputs.java_version || 21 }}"
- name: Build uberjar with ./bin/build.sh
run: ./bin/build.sh
- name: Prepare uberjar artifact
uses: ./.github/actions/prepare-uberjar-artifact
with:
name: metabase-ee-${{ github.event.pull_request.head.sha || github.sha }}-uberjar
- name: Build Embedding SDK package
run: |
# Remove the matchers to not report false positives on every PR
# unrelated to embedding (EMB-765)
echo "::remove-matcher owner=tsc::"
yarn build-embedding-sdk-package
- name: Prepare Embedding SDK artifact
uses: actions/upload-artifact@v4
with:
name: embedding-sdk-${{ github.event.pull_request.head.sha || github.sha }}
path: resources/embedding-sdk
# In order to check compatibility between Sample Apps and Embedding SDK, the job for each sample app:
# - pulls a Sample App
# - starts it
@@ -88,13 +197,13 @@ jobs:
# The job is not required, though if it's failing better to be sure that it's not caused by a breaking change.
# For more details see `Sample Apps compatibility with Embedding SDK tests` in `enterprise/frontend/src/embedding-sdk-package/dev.md`
e2e-sample-apps-tests:
needs: [get-sample-apps-data]
needs: [get-build-requirements, get-sample-apps-data, build]
strategy:
matrix:
test_suite: ${{ fromJSON(needs.get-sample-apps-data.outputs.matrix) }}
environment: ["production", "development"]
if: |
!cancelled() && needs.get-sample-apps-data.result == 'success'
!cancelled() && needs.build.result == 'success'
runs-on: ubuntu-22.04
timeout-minutes: 45
name: e2e-sample-app-${{ matrix.test_suite }}-tests-${{ matrix.environment }}-bundle
@@ -104,9 +213,8 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number || '' }}
HASH: ${{ github.event.pull_request.head.sha || github.sha }}-${{ github.run_attempt }}
MB_RUN_MODE: e2e
METASTORE_DEV_SERVER_URL: ${{ secrets.METASTORE_DEV_SERVER_URL }}
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ secrets.STAGING_MB_ALL_FEATURES_TOKEN }}
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ secrets.MB_ALL_FEATURES_TOKEN }}
CYPRESS_IS_EMBEDDING_SDK: "true"
TZ: US/Pacific # to make node match the instance tz
CYPRESS_CI: true
SAMPLE_APP_BRANCH_NAME: ${{ needs.get-sample-apps-data.outputs.branch_name }}
@@ -131,11 +239,9 @@ jobs:
echo "sample_app_name=${test_suite%-e2e}" >> "$GITHUB_OUTPUT"
- name: Retrieve uberjar artifact for ee
uses: ./.github/actions/fetch-artifact
uses: actions/download-artifact@v4
with:
name: metabase-ee-${{ github.event.pull_request.head.sha || github.sha }}-uberjar
extract-path: "target/uberjar"
output-name: "metabase.jar"
- name: Retrieve Embedding SDK dist artifact
uses: actions/download-artifact@v4
@@ -191,9 +297,7 @@ jobs:
id: run-e2e-tests
if: ${{ steps.check-sample-app-branch.outputs.sample_app_branch_exists == 'true' }}
continue-on-error: true
env:
CYPRESS_BROWSER: ${{ steps.cypress-prep.outputs.chrome-path }}
run: node e2e/runner/run_cypress_ci.js ${{ matrix.test_suite }}
run: node e2e/runner/run_cypress_ci.js ${{ matrix.test_suite }} --browser ${{ steps.cypress-prep.outputs.chrome-path }}
- name: Upload Cypress Artifacts upon failure
uses: actions/upload-artifact@v4
@@ -217,6 +321,7 @@ jobs:
# The job is not required, though if it's failing better to be sure that it's not caused by a breaking change.
# For more details see `Embedding SDK integration tests with Host Apps` in `enterprise/frontend/src/embedding-sdk-package/dev.md`
e2e-host-apps-tests:
needs: [get-build-requirements, build]
strategy:
matrix:
test_suite:
@@ -227,6 +332,8 @@ jobs:
"angular-20-host-app-e2e",
]
environment: ["production", "development"]
if: |
!cancelled() && needs.build.result == 'success'
runs-on: ubuntu-22.04
timeout-minutes: 45
name: e2e-host-app-${{ matrix.test_suite }}-tests-${{ matrix.environment }}-bundle
@@ -236,9 +343,8 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number || '' }}
HASH: ${{ github.event.pull_request.head.sha || github.sha }}-${{ github.run_attempt }}
MB_RUN_MODE: e2e
METASTORE_DEV_SERVER_URL: ${{ secrets.METASTORE_DEV_SERVER_URL }}
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ secrets.STAGING_MB_ALL_FEATURES_TOKEN }}
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ secrets.MB_ALL_FEATURES_TOKEN }}
CYPRESS_IS_EMBEDDING_SDK: "true"
TZ: US/Pacific # to make node match the instance tz
CYPRESS_CI: true
HOST_APP_ENVIRONMENT: ${{ matrix.environment }}
@@ -284,9 +390,7 @@ jobs:
- name: Run e2e tests for Host App
id: run-e2e-tests
continue-on-error: true
env:
CYPRESS_BROWSER: ${{ steps.cypress-prep.outputs.chrome-path }}
run: node e2e/runner/run_cypress_ci.js ${{ matrix.test_suite }}
run: node e2e/runner/run_cypress_ci.js ${{ matrix.test_suite }} --browser ${{ steps.cypress-prep.outputs.chrome-path }}
- name: Upload Cypress Artifacts upon failure
uses: actions/upload-artifact@v4

View File

@@ -37,26 +37,8 @@ on:
default: true
jobs:
determine-test-type:
name: Determine test type
runs-on: ubuntu-22.04
timeout-minutes: 5
outputs:
type: ${{ steps.detect.outputs.type }}
steps:
- name: Detect test type from spec path
id: detect
run: |
SPEC='${{ inputs.spec }}'
if [[ "$SPEC" == e2e/test-component/scenarios/embedding-sdk/* ]]; then
echo "type=component" >> $GITHUB_OUTPUT
else
echo "type=e2e" >> $GITHUB_OUTPUT
fi
workflow-summary:
name: Stress test inputs
needs: [determine-test-type]
runs-on: ubuntu-22.04
timeout-minutes: 5
steps:
@@ -66,7 +48,6 @@ jobs:
echo '' >> $GITHUB_STEP_SUMMARY
echo '- `branch`: ${{ github.ref_name }}' >> $GITHUB_STEP_SUMMARY
echo '- `spec`: ${{ inputs.spec }}' >> $GITHUB_STEP_SUMMARY
echo '- `test_type` (inferred): ${{ needs.determine-test-type.outputs.type }}' >> $GITHUB_STEP_SUMMARY
echo '- `burn_in`: ${{ inputs.burn_in }}' >> $GITHUB_STEP_SUMMARY
echo '- `grep`: "${{ inputs.grep }}"' >> $GITHUB_STEP_SUMMARY
echo '- `qa_db`: "${{ inputs.qa_db }}"' >> $GITHUB_STEP_SUMMARY
@@ -75,7 +56,6 @@ jobs:
echo 'triggered by: @${{ github.event.sender.login }}' >> $GITHUB_STEP_SUMMARY
stress-test-flake-fix:
needs: [determine-test-type]
runs-on: ubuntu-22.04
timeout-minutes: 60
name: Stress test E2E flake fix
@@ -83,12 +63,10 @@ jobs:
DISPLAY: ""
QA_DB_ENABLED: ${{ contains(fromJSON('["sql", "mongo"]'), inputs.qa_db) }}
CYPRESS_QA_DB_MONGO: ${{ inputs.qa_db == 'mongo' }}
MB_RUN_MODE: e2e
METASTORE_DEV_SERVER_URL: ${{ secrets.METASTORE_DEV_SERVER_URL }}
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ secrets.STAGING_MB_ALL_FEATURES_TOKEN }}
CYPRESS_MB_STARTER_CLOUD_TOKEN: ${{ secrets.STAGING_MB_STARTER_CLOUD_TOKEN }}
CYPRESS_MB_PRO_CLOUD_TOKEN: ${{ secrets.STAGING_MB_PRO_CLOUD_TOKEN }}
CYPRESS_MB_PRO_SELF_HOSTED_TOKEN: ${{ secrets.STAGING_MB_PRO_SELF_HOSTED_TOKEN }}
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ secrets.MB_ALL_FEATURES_TOKEN }}
CYPRESS_MB_STARTER_CLOUD_TOKEN: ${{ secrets.MB_STARTER_CLOUD_TOKEN }}
CYPRESS_MB_PRO_CLOUD_TOKEN: ${{ secrets.MB_PRO_CLOUD_TOKEN }}
CYPRESS_MB_PRO_SELF_HOSTED_TOKEN: ${{ secrets.MB_PRO_SELF_HOSTED_TOKEN }}
MB_SNOWPLOW_AVAILABLE: true
MB_SNOWPLOW_URL: "http://localhost:9090" # Snowplow micro
MB_EDITION: ${{ inputs.mb_edition }}
@@ -120,16 +98,6 @@ jobs:
- name: Prepare front-end environment
uses: ./.github/actions/prepare-frontend
- name: Prepare back-end environment
if: ${{ needs.determine-test-type.outputs.type == 'component' }}
uses: ./.github/actions/prepare-backend
with:
m2-cache-key: "cljs"
- name: Build Embedding SDK package
if: ${{ needs.determine-test-type.outputs.type == 'component' }}
run: yarn build-embedding-sdk-package
- name: Prepare JDK 21
uses: actions/setup-java@v4
with:
@@ -148,12 +116,12 @@ jobs:
- name: Stress-test ${{ github.event.inputs.spec }} ${{ github.event.inputs.burn_in }} times
env:
GREP: ${{ github.event.inputs.grep }}
CYPRESS_BROWSER: ${{ steps.cypress-prep.outputs.chrome-path }}
run: |
node e2e/runner/run_cypress_ci.js ${{ needs.determine-test-type.outputs.type }} \
node e2e/runner/run_cypress_ci.js e2e \
--spec '${{ github.event.inputs.spec }}' \
--env burn=${{ github.event.inputs.burn_in }} \
--config-file e2e/support/cypress-stress-test.config.js
--config-file e2e/support/cypress-stress-test.config.js \
--browser ${{ steps.cypress-prep.outputs.chrome-path }}
- name: Upload Cypress Artifacts upon failure
uses: actions/upload-artifact@v4

View File

@@ -42,14 +42,12 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HASH: ${{ github.event.pull_request.head.sha || github.sha }}-${{ github.run_attempt }}
JOB_NAME: ${{ inputs.name }}
MB_RUN_MODE: "e2e"
METASTORE_DEV_SERVER_URL: ${{ secrets.METASTORE_DEV_SERVER_URL }}
# Any env starting with `CYPRESS_` will be available to all Cypress tests via `Cypress.env()`
# Example: you can get `CYPRESS_FOO` with `Cypress.env("FOO")`
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ secrets.STAGING_MB_ALL_FEATURES_TOKEN }}
CYPRESS_MB_STARTER_CLOUD_TOKEN: ${{ secrets.STAGING_MB_STARTER_CLOUD_TOKEN }}
CYPRESS_MB_PRO_CLOUD_TOKEN: ${{ secrets.STAGING_MB_PRO_CLOUD_TOKEN }}
CYPRESS_MB_PRO_SELF_HOSTED_TOKEN: ${{ secrets.STAGING_MB_PRO_SELF_HOSTED_TOKEN }}
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ secrets.MB_ALL_FEATURES_TOKEN }}
CYPRESS_MB_STARTER_CLOUD_TOKEN: ${{ secrets.MB_STARTER_CLOUD_TOKEN }}
CYPRESS_MB_PRO_CLOUD_TOKEN: ${{ secrets.MB_PRO_CLOUD_TOKEN }}
CYPRESS_MB_PRO_SELF_HOSTED_TOKEN: ${{ secrets.MB_PRO_SELF_HOSTED_TOKEN }}
CYPRESS_PULL_REQUEST_ID: ${{ github.event.pull_request.number || '' }}
COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title || github.event.head_commit.message || github.event.head.sha }}
MB_EXPERIMENTAL_SEARCH_BLOCK_ON_QUEUE: true
@@ -59,6 +57,7 @@ jobs:
TZ: US/Pacific # to make node match the instance tz
CYPRESS_CI: true
CYPRESS_QA_DB_MONGO: ${{ inputs.name == 'mongo' }}
CYPRESS_IS_EMBEDDING_SDK: "${{ inputs.name == 'embedding-sdk' }}"
steps:
- uses: actions/checkout@v4
with:
@@ -84,6 +83,10 @@ jobs:
mysql: ${{ inputs.name != 'mongo' }}
mongo: ${{ inputs.name == 'mongo' }}
- name: Run Embedding SDK Storybook
if: inputs.name == 'embedding-sdk'
uses: ./.github/actions/run-embedding-sdk-storybook
- name: Retrieve uberjar artifact for ${{ inputs.edition }}
uses: ./.github/actions/fetch-artifact
with:
@@ -120,8 +123,6 @@ jobs:
run: node e2e/runner/run_cypress_ci.js start
- name: Generate database snapshots
env:
CYPRESS_BROWSER: ${{ steps.cypress-prep.outputs.chrome-path }}
run: node e2e/runner/run_cypress_ci.js snapshot
- name: Run auto-split Cypress tests on ${{ inputs.name }}
@@ -135,33 +136,35 @@ jobs:
# do not write cypress-split summary to github
SPLIT_SUMMARY: false
SPEC_PATH: ${{ env.specs || inputs.specs }}
CYPRESS_BROWSER: ${{ steps.cypress-prep.outputs.chrome-path }}
shell: bash
run: |
echo "Running tests with specs: $SPEC_PATH"
node e2e/runner/run_cypress_ci.js e2e \
--env grepTags="-@mongo+-@python+-@OSS+-@skip" \
--spec "$SPEC_PATH"
--env grepTags="-@mongo+-@python+-@flaky+-@OSS+-@skip",grepOmitFiltered=true \
--spec "$SPEC_PATH" \
--browser ${{ steps.cypress-prep.outputs.chrome-path }}
# Trunk Quarantine controls the final state of the run in the CI!
# Even if this step fails we let it pass, and then `trunk-io/analytics-uploader` determines
# whether to fail or to pass the whole job, based on the quarantine list.
continue-on-error: true
# Even if this step fails, we let it pass and then `trunk-io/analytics-uploader` determines whether to fail or to
# pass the whole job, based on the quarantine list.
# We only want to "continue on error" (ignore failures) when the Trunk upload is active - i.e. not on drafts!
continue-on-error: ${{ !(github.event_name == 'pull_request' && github.event.pull_request.draft == true) }}
- name: Run Tagged EE Cypress tests on ${{ inputs.name }}
if: ${{ inputs.specs }}
shell: bash
env:
SPEC_PATH: ${{ env.specs || inputs.specs }}
CYPRESS_BROWSER: ${{ steps.cypress-prep.outputs.chrome-path }}
run: |
echo "Running tests with specs: $SPEC_PATH"
node e2e/runner/run_cypress_ci.js e2e \
--env grepTags="${{inputs.tags && format('{0}+-@skip', inputs.tags) || '-@skip'}}" \
--spec "$SPEC_PATH"
--env grepTags="${{inputs.tags && format('{0}+-@skip', inputs.tags) || '-@skip'}}",grepOmitFiltered=true \
--spec "$SPEC_PATH" \
--browser ${{ steps.cypress-prep.outputs.chrome-path }}
# Trunk Quarantine controls the final state of the run in the CI!
# Even if this step fails we let it pass, and then `trunk-io/analytics-uploader` determines
# whether to fail or to pass the whole job, based on the quarantine list.
continue-on-error: true
# Even if this step fails, we let it pass and then `trunk-io/analytics-uploader` determines whether to fail or to
# pass the whole job, based on the quarantine list.
# We only want to "continue on error" (ignore failures) when the Trunk upload is active - i.e. not on drafts!
continue-on-error: ${{ !(github.event_name == 'pull_request' && github.event.pull_request.draft == true) }}
- name: Upload Test Results
uses: ./.github/actions/upload-test-results

View File

@@ -73,6 +73,20 @@ jobs:
tags: ${{ matrix.tags }}
runner: ${{ matrix.runner }}
# run this separately to make it non-blocking
flaky-e2e-tests:
name: Flaky e2e Tests
uses: ./.github/workflows/e2e-test.yml
needs:
- e2e-matrix-builder
# skip flaky test run if only e2e specs changed as an optimization
if: |
!cancelled() && !inputs.skip && needs.e2e-matrix-builder.outputs.isDefaultSpecPattern == 'true'
secrets: inherit
with:
name: Flaky
tags: "@flaky"
collect-timings:
name: Collect E2E Timings
runs-on: ubuntu-22.04
@@ -138,12 +152,14 @@ jobs:
e2e-tests-result:
needs:
- e2e-tests
- flaky-e2e-tests
runs-on: ubuntu-latest
timeout-minutes: 5
name: e2e-tests-result
if: always() && !cancelled()
env:
E2E_TESTS_RESULT: ${{ needs.e2e-tests.result }}
FLAKY_E2E_TESTS_RESULT: ${{ needs.flaky-e2e-tests.result }}
steps:
- name: Stub
if: ${{ needs.e2e-tests.result == 'skipped' }}
@@ -153,6 +169,7 @@ jobs:
if: ${{ needs.e2e-tests.result != 'skipped' }}
run: | # sh
echo "E2E Tests: ${{ needs.e2e-tests.result }}"
echo "Flaky E2E Tests: ${{ needs.flaky-e2e-tests.result }}"
if [ "${{ needs.e2e-tests.result }}" != "success" ]; then
echo "E2E Tests failed"

View File

@@ -1,250 +0,0 @@
name: Embedding SDK Component Tests
on:
workflow_call:
jobs:
sdk-component-tests:
name: "SDK component tests (React ${{ matrix.react-version }})"
runs-on: ubuntu-22.04
timeout-minutes: 45
strategy:
matrix:
react-version: [18, 19]
fail-fast: false
env:
MB_EDITION: ee
DISPLAY: ""
MB_RUN_MODE: "e2e"
METASTORE_DEV_SERVER_URL: ${{ secrets.METASTORE_DEV_SERVER_URL }}
# Any env starting with `CYPRESS_` will be available to all Cypress tests via `Cypress.env()`
# Example: you can get `CYPRESS_FOO` with `Cypress.env("FOO")`
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ secrets.STAGING_MB_ALL_FEATURES_TOKEN }}
CYPRESS_MB_STARTER_CLOUD_TOKEN: ${{ secrets.STAGING_MB_STARTER_CLOUD_TOKEN }}
CYPRESS_MB_PRO_CLOUD_TOKEN: ${{ secrets.STAGING_MB_PRO_CLOUD_TOKEN }}
CYPRESS_MB_PRO_SELF_HOSTED_TOKEN: ${{ secrets.STAGING_MB_PRO_SELF_HOSTED_TOKEN }}
COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title || github.event.head_commit.message || github.event.head.sha }}
TZ: US/Pacific # to make node match the instance tz
CYPRESS_CI: true
steps:
- uses: actions/checkout@v4
- name: Retrieve uberjar artifact for ee
uses: ./.github/actions/fetch-artifact
with:
name: metabase-ee-${{ github.event.pull_request.head.sha || github.sha }}-uberjar
extract-path: "target/uberjar"
output-name: "metabase.jar"
- name: Retrieve Embedding SDK dist artifact
uses: actions/download-artifact@v4
with:
name: embedding-sdk-${{ github.event.pull_request.head.sha || github.sha }}
path: ${{ github.workspace }}/resources/embedding-sdk
- name: Prepare front-end environment
uses: ./.github/actions/prepare-frontend
- name: Prepare JDK 21
uses: actions/setup-java@v4
with:
java-version: 21
distribution: "temurin"
- name: Prepare Cypress environment
id: cypress-prep
uses: ./.github/actions/prepare-cypress
- name: Run Metabase
run: node e2e/runner/run_cypress_ci.js start
- name: Make app db snapshot
env:
CYPRESS_BROWSER: ${{ steps.cypress-prep.outputs.chrome-path }}
run: node e2e/runner/run_cypress_ci.js snapshot
# Has to be run after the app db snapshot is made, as it is not compatible with React 19
- name: Change React version to ${{ matrix.react-version }}
run: ./bin/embedding-sdk/change-react-version.bash ${{ matrix.react-version }}
- name: Run component tests for Embedding SDK
env:
CYPRESS_BROWSER: ${{ steps.cypress-prep.outputs.chrome-path }}
run: node e2e/runner/run_cypress_ci.js component
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always()
with:
input-path: ./target/junit
output-name: e2e-embedding-sdk-react-${{ matrix.react-version }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
- name: "SDK component tests > Upload Cypress Artifacts upon failure"
uses: actions/upload-artifact@v4
if: failure()
with:
name: cypress-recording-embedding-sdk-react-${{ matrix.react-version }}
path: |
./cypress
./logs/test.log
if-no-files-found: ignore
- name: "SDK component tests > Publish Summary"
if: failure()
uses: actions/github-script@v7
with:
script: | #js
const {
generateReport,
parseReport,
formatSummary
} = require("./.github/scripts/handle-mochawesome-report.js");
const report = await generateReport();
const results = parseReport(report);
const summary = formatSummary(results);
await core.summary.addRaw(summary).write();
decide-target-ref:
runs-on: ubuntu-latest
outputs:
target-ref: ${{ steps.get-target-ref.outputs.value }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Decide ref to checkout
id: get-target-ref
run: |
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
echo "value=${{ github.base_ref }}" >> "$GITHUB_OUTPUT"
else
echo "value=$(git rev-parse HEAD^1)" >> "$GITHUB_OUTPUT"
fi
# This job ensures that the Embedding SDK package from the current commit (N) works with the
# Embedding SDK Bundle (Metabase JAR) from the previous commit (N-1).
# N - 1 is a target of a pull request, or a literal previous commit when this job is run on push in a branch.
sdk-component-tests-backward-compatibility:
name: "SDK component tests (Backward compatibility)"
needs: [decide-target-ref]
runs-on: ubuntu-22.04
timeout-minutes: 45
env:
MB_EDITION: ee
DISPLAY: ""
MB_RUN_MODE: "e2e"
METASTORE_DEV_SERVER_URL: ${{ secrets.METASTORE_DEV_SERVER_URL }}
# Any env starting with `CYPRESS_` will be available to all Cypress tests via `Cypress.env()`
# Example: you can get `CYPRESS_FOO` with `Cypress.env("FOO")`
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ secrets.STAGING_MB_ALL_FEATURES_TOKEN }}
CYPRESS_MB_STARTER_CLOUD_TOKEN: ${{ secrets.STAGING_MB_STARTER_CLOUD_TOKEN }}
CYPRESS_MB_PRO_CLOUD_TOKEN: ${{ secrets.STAGING_MB_PRO_CLOUD_TOKEN }}
CYPRESS_MB_PRO_SELF_HOSTED_TOKEN: ${{ secrets.STAGING_MB_PRO_SELF_HOSTED_TOKEN }}
COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title || github.event.head_commit.message || github.event.head.sha }}
TZ: US/Pacific # to make node match the instance tz
CYPRESS_CI: true
steps:
- name: Check out the code from the previous commit
uses: actions/checkout@v4
with:
ref: ${{ needs.decide-target-ref.outputs.target-ref }}
- name: Get the actual commit SHA from the checked out branch
id: get-commit-sha
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- name: Prepare back-end environment
uses: ./.github/actions/prepare-backend
- name: Prepare front-end environment
uses: ./.github/actions/prepare-frontend
- name: Attempt to retrieve the ee uberjar artifact from the previous commit
id: download-uberjar
uses: ./.github/actions/fetch-artifact
with:
name: metabase-ee-${{ steps.get-commit-sha.outputs.sha }}-uberjar
extract-path: "target/uberjar"
output-name: "metabase.jar"
continue-on-error: true # do not break the whole job if this step doesn't succeed
# Build the uberjar from the previous commit only if we couldn't fetch it.
# `step.foo.OUTCOME` is important because it looks at the step status *before* continue-on-error is applied.
# Do not confuse it with the `step.foo.conclusion`.
- name: Build MB uberjar from the commit N-1
if: steps.download-uberjar.outcome != 'success'
run: ./bin/build.sh
- name: Retrieve Embedding SDK dist artifact built from the current commit N
uses: actions/download-artifact@v4
with:
name: embedding-sdk-${{ github.event.pull_request.head.sha || github.sha }}
path: ${{ github.workspace }}/resources/embedding-sdk
- name: Prepare Cypress environment
id: cypress-prep
uses: ./.github/actions/prepare-cypress
- name: Run Metabase
run: node e2e/runner/run_cypress_ci.js start
- name: Make app db snapshot
env:
CYPRESS_BROWSER: ${{ steps.cypress-prep.outputs.chrome-path }}
run: node e2e/runner/run_cypress_ci.js snapshot
- name: Run component tests for Embedding SDK
env:
CYPRESS_BROWSER: ${{ steps.cypress-prep.outputs.chrome-path }}
run: |
node e2e/runner/run_cypress_ci.js component \
--env grepTags="-@skip-backward-compatibility"
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always()
with:
input-path: ./target/junit
output-name: e2e-embedding-sdk
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
- name: Upload Cypress Artifacts upon failure
uses: actions/upload-artifact@v4
if: failure()
with:
name: cypress-recording-embedding-sdk-cross-version
path: |
./cypress
./logs/test.log
if-no-files-found: ignore
- name: Publish Summary
if: failure()
uses: actions/github-script@v7
with:
script: | #js
const {
generateReport,
parseReport,
formatSummary
} = require("./.github/scripts/handle-mochawesome-report.js");
const report = await generateReport();
const results = parseReport(report);
const summary = formatSummary(results);
await core.summary.addRaw(summary).write();

View File

@@ -1,31 +0,0 @@
name: Embedding SDK Package Build
on:
workflow_call:
workflow_dispatch:
concurrency:
group: embedding-sdk-package-${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
jobs:
build-sdk-package:
runs-on: ubuntu-22.04
timeout-minutes: 25
steps:
- uses: actions/checkout@v4
- name: Prepare front-end environment
uses: ./.github/actions/prepare-frontend
- name: Prepare back-end environment
uses: ./.github/actions/prepare-backend
- name: Build Embedding SDK package
run: |
# Remove the matchers to not report false positives on every PR
# unrelated to embedding (EMB-765)
echo "::remove-matcher owner=tsc::"
yarn build-embedding-sdk-package
- name: Prepare Embedding SDK artifact
uses: actions/upload-artifact@v4
with:
name: embedding-sdk-${{ github.event.pull_request.head.sha || github.sha }}
path: resources/embedding-sdk

View File

@@ -2,35 +2,61 @@ name: SDK
on:
workflow_call:
inputs:
cli-snippets-type-check:
description: "Whether to run cli snippets type check."
required: true
type: boolean
docs-snippets-type-check:
description: "Whether to run docs snippets type check."
required: true
type: boolean
documentation-validation:
description: "Whether to run documentation generation validation check."
required: true
type: boolean
component-tests:
description: "Whether to run component tests."
required: true
type: boolean
host-sample-apps-tests:
description: "Whether to run host and sample apps compatibility tests."
required: true
type: boolean
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
jobs:
files-changed:
name: Check which files changed
runs-on: ubuntu-22.04
timeout-minutes: 3
outputs:
e2e_embedding_sdk: ${{ steps.changes.outputs.e2e_embedding_sdk }}
frontend_embedding_sdk_sources: ${{ steps.changes.outputs.frontend_embedding_sdk_sources }}
embedding_documentation: ${{ steps.changes.outputs.embedding_documentation }}
steps:
- uses: actions/checkout@v4
- name: Test which files changed
uses: dorny/paths-filter@v3.0.0
id: changes
with:
token: ${{ github.token }}
filters: .github/file-paths.yaml
build:
needs: [files-changed]
if: |
!cancelled() &&
(needs.files-changed.outputs.e2e_embedding_sdk == 'true' ||
needs.files-changed.outputs.frontend_embedding_sdk_sources == 'true' ||
needs.files-changed.outputs.embedding_documentation == 'true')
runs-on: ubuntu-22.04
timeout-minutes: 25
steps:
- uses: actions/checkout@v4
- name: Prepare front-end environment
uses: ./.github/actions/prepare-frontend
- name: Prepare back-end environment
uses: ./.github/actions/prepare-backend
- name: Build Embedding SDK package
run: |
# Remove the matchers to not report false positives on every PR
# unrelated to embedding (EMB-765)
echo "::remove-matcher owner=tsc::"
yarn build-embedding-sdk-package
- name: Prepare Embedding SDK artifact
uses: actions/upload-artifact@v4
with:
name: embedding-sdk-${{ github.event.pull_request.head.sha || github.sha }}
path: resources/embedding-sdk
embedding-sdk-cli-snippets-type-check:
if: inputs.cli-snippets-type-check
needs: [files-changed, build]
if: |
needs.files-changed.outputs.frontend_embedding_sdk_sources == 'true' &&
needs.build.result == 'success'
runs-on: ubuntu-22.04
timeout-minutes: 20
steps:
@@ -46,7 +72,10 @@ jobs:
run: yarn run embedding-sdk:cli-snippets:type-check
embedding-sdk-docs-snippets-type-check:
if: inputs.docs-snippets-type-check
needs: [files-changed, build]
if: |
needs.files-changed.outputs.embedding_documentation == 'true' &&
needs.build.result == 'success'
runs-on: ubuntu-22.04
timeout-minutes: 20
steps:
@@ -62,7 +91,10 @@ jobs:
run: yarn run embedding-sdk:docs-snippets:type-check
embedding-sdk-api-documentation-generation-validation:
if: inputs.documentation-validation
needs: [files-changed, build]
if: |
needs.files-changed.outputs.frontend_embedding_sdk_sources == 'true' &&
needs.build.result == 'success'
runs-on: ubuntu-22.04
timeout-minutes: 20
steps:
@@ -108,24 +140,29 @@ jobs:
run: exit 1
# See `Component e2e tests` in `enterprise/frontend/src/embedding-sdk-package/dev.md`
embedding-sdk-component-tests:
name: "SDK component tests"
if: inputs.component-tests
uses: ./.github/workflows/embedding-sdk-component-tests.yml
sdk-e2e-tests:
name: "SDK e2e tests"
uses: ./.github/workflows/e2e-component-tests-embedding-sdk.yml
needs: [build]
secrets: inherit
with:
cached-embedding-sdk-dist-artifact-name: embedding-sdk-${{ github.event.pull_request.head.sha || github.sha }}
not-required-embedding-sdk-apps-compatibility-tests:
name: "SDK e2e tests for compatibility with (host/sample) apps"
if: inputs.host-sample-apps-tests
uses: ./.github/workflows/embedding-sdk-host-sample-apps-tests.yml
e2e-component-tests-embedding-sdk-backward-compatibility:
name: "SDK e2e backward compatibility tests"
uses: ./.github/workflows/e2e-component-tests-embedding-sdk-backward-compatibility.yml
needs: [build]
secrets: inherit
with:
cached-embedding-sdk-dist-artifact-name: embedding-sdk-${{ github.event.pull_request.head.sha || github.sha }}
embedding-sdk-tests-result:
needs:
- embedding-sdk-cli-snippets-type-check
- embedding-sdk-docs-snippets-type-check
- embedding-sdk-api-documentation-generation-validation
- embedding-sdk-component-tests
- sdk-e2e-tests
- e2e-component-tests-embedding-sdk-backward-compatibility
if: always() && !cancelled()
runs-on: ubuntu-latest
timeout-minutes: 5

View File

@@ -35,16 +35,13 @@ jobs:
uses: ./.github/actions/prepare-frontend
- name: Prepare back-end environment
uses: ./.github/actions/prepare-backend
- name: Get Eslint cache key
uses: ./.github/actions/get-cache-keys
id: get-cache-keys
- name: Restore ESLint cache
uses: actions/cache/restore@v4
uses: actions/cache@v4
with:
path: ${{ github.workspace}}/.eslintcache
key: ${{ steps.get-cache-keys.outputs.eslint-cache-key }}
path: .eslintcache
key: eslintcache-${{ hashFiles('yarn.lock', '.eslintrc.js') }}
restore-keys: |
eslintcache-
- name: Compile CLJS
run: yarn build-pure:cljs
- name: Find CSS variables that are used but never defined
@@ -91,9 +88,8 @@ jobs:
uses: ./.github/actions/prepare-backend
- name: Run frontend unit tests
run: yarn run test-unit --silent --shard=${{ matrix.shard }}/${{ env.SHARDS }}
# Trunk Quarantine controls the final state of the run in the CI!
# Even if this step fails we let it pass, and then `trunk-io/analytics-uploader` determines
# whether to fail or to pass the whole job, based on the quarantine list.
# We want Trunk to still receive test reports even if this job fails.
# It will filter out the quarantined tests, and will control the job exit code based on the analysis.
continue-on-error: true
- name: Upload Test Results
uses: ./.github/actions/upload-test-results

View File

@@ -1,227 +0,0 @@
name: Minimal Tests
on:
pull_request:
types: [opened, synchronize, reopened, labeled]
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
jobs:
# Only run if the PR has the minimal-tests label
check-label:
runs-on: ubuntu-latest
timeout-minutes: 1
outputs:
should-run: ${{ steps.check.outputs.result }}
steps:
- name: Check for minimal-tests label
id: check
run: |
if [[ "${{ contains(github.event.pull_request.labels.*.name, 'minimal-tests') }}" == "true" ]]; then
echo "result=true" >> $GITHUB_OUTPUT
else
echo "result=false" >> $GITHUB_OUTPUT
fi
# Postgres app-db tests - single version, single job
postgres-app-db-test:
needs: check-label
if: needs.check-label.outputs.should-run == 'true'
runs-on: ubuntu-22.04
timeout-minutes: 60
env:
CI: 'true'
DRIVERS: postgres
MB_DB_TYPE: postgres
MB_DB_PORT: 5432
MB_DB_HOST: localhost
MB_DB_DBNAME: mb_test
MB_DB_USER: mb_test
MB_POSTGRESQL_TEST_USER: mb_test
services:
postgres:
image: postgres:latest
ports:
- "5432:5432"
env:
POSTGRES_USER: mb_test
POSTGRES_DB: mb_test
POSTGRES_HOST_AUTH_METHOD: trust
name: Postgres Latest EE App DB Tests (Part 1)
steps:
- uses: actions/checkout@v4
- name: Test Postgres App DB
uses: ./.github/actions/test-driver
with:
build-static-viz: true
junit-name: 'minimal-be-tests-postgres-ee'
test-args: >-
:only '["test" "enterprise/backend/test"]'
:exclude-tags '[:mb/driver-tests :mb/transforms-python-test]'
:partition/total 2
:partition/index 0
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
# Postgres driver tests
postgres-driver-test:
needs: check-label
if: needs.check-label.outputs.should-run == 'true'
runs-on: ubuntu-latest
timeout-minutes: 60
env:
CI: 'true'
DRIVERS: postgres
MB_DB_TYPE: postgres
MB_DB_PORT: 5432
MB_DB_HOST: localhost
MB_DB_DBNAME: mb_test
MB_DB_USER: mb_test
MB_POSTGRESQL_TEST_USER: mb_test
services:
postgres:
image: postgres:latest
ports:
- "5432:5432"
env:
POSTGRES_USER: mb_test
POSTGRES_DB: mb_test
POSTGRES_HOST_AUTH_METHOD: trust
name: Postgres Latest Driver Tests
steps:
- uses: actions/checkout@v4
- name: Test Postgres Driver
uses: ./.github/actions/test-driver
with:
junit-name: 'minimal-be-tests-postgres-driver-ee'
test-args: >-
:only-tags [:mb/driver-tests]
:exclude-tags [:mb/transforms-python-test]
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
# Frontend unit tests
frontend-unit-tests:
needs: check-label
if: needs.check-label.outputs.should-run == 'true'
runs-on: ubuntu-22.04
timeout-minutes: 30
env:
JEST_JUNIT_OUTPUT_DIR: ./target/junit
JEST_JUNIT_OUTPUT_NAME: test-report-frontend-unit.xml
name: Frontend Unit Tests
steps:
- uses: actions/checkout@v4
- name: Prepare front-end environment
uses: ./.github/actions/prepare-frontend
- name: Prepare back-end environment
uses: ./.github/actions/prepare-backend
- name: Run frontend unit tests
run: yarn run test-unit --silent
continue-on-error: true
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always()
with:
input-path: ./target/junit
output-name: frontend-unit
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
# Summary job
minimal-tests-result:
needs:
- postgres-app-db-test
- postgres-driver-test
- frontend-unit-tests
runs-on: ubuntu-latest
timeout-minutes: 5
name: minimal-tests-result
if: always() && !cancelled()
steps:
- name: Check job status
uses: actions/github-script@v7
env:
needs: ${{ toJson(needs) }}
with:
script: | # js
const needs = JSON.parse(process.env.needs);
const jobs = Object.entries(needs).map(
([jobName, jobValues]) => ({
name: jobName,
result: jobValues.result
}));
// Check if all jobs either succeeded or were skipped
// Skipped jobs are acceptable (they were intentionally skipped when the label is absent)
const allPassedOrSkipped = jobs.every(job =>
job.result === 'success' || job.result === 'skipped'
);
// Check if any jobs actually failed (not skipped, not cancelled)
const anyFailed = jobs.some(job =>
job.result === 'failure'
);
if (allPassedOrSkipped && !anyFailed) {
console.log("");
console.log(" _------. ");
console.log(" / , \_ ");
console.log(" / / /{}\ |o\_ ");
console.log(" / \ `--' /-' \ ");
console.log(" | \ \ | ");
console.log(" | |`-, | ");
console.log(" / /__/)/ ");
console.log(" | | ");
console.log("");
console.log("All minimal tests have passed. Fast feedback achieved!");
process.exit(0);
}
// otherwise, something failed
console.log("");
console.log(" .::::::::::. .::::::::::. ");
console.log(" .::::''''''::::. .::::''''''::::. ");
console.log(" .:::' `::::.... ....::::' `:::. ");
console.log(" .::' `:::::::| |:::::::' `::. ");
console.log(" .::| |::::::|_ ___ __|::::::| |::. ");
console.log(" `--' |::::::|_()__()_|::::::| `--' ");
console.log(" ::: |::-o::| |::o-::| ::: ");
console.log(" `::. .|::::::| |::::::|. .::' ");
console.log(" `:::. .::\-----' `-----/::. .:::' ");
console.log(" `::::......::::' `::::......::::' ");
console.log(" `::::::::::' `::::::::::' ");
console.log("");
console.log("Minimal tests have failed.");
jobs.forEach((job) => {
if (job.result !== 'success' && job.result !== 'skipped') {
console.log(`${job.name} - ${job.result}`);
}
});
process.exit(1);

View File

@@ -10,15 +10,6 @@ concurrency:
jobs:
pr-env:
if: ${{ (github.event.action == 'closed' && contains(github.event.pull_request.labels.*.name, 'PR-Env') && !contains(github.event.pull_request.labels.*.name, 'PR-Env-self-hosting')) || github.event.label.name == 'PR-Env' }}
if: ${{ github.event.action == 'closed' && contains(github.event.pull_request.labels.*.name, 'PR-Env') || github.event.label.name == 'PR-Env' }}
uses: ./.github/workflows/pr-env-destroy.yml
secrets: inherit
with:
self_hosting: false
pr-env-self-hosting:
if: ${{ (github.event.action == 'closed' && contains(github.event.pull_request.labels.*.name, 'PR-Env-self-hosting')) || github.event.label.name == 'PR-Env-self-hosting' }}
uses: ./.github/workflows/pr-env-destroy.yml
secrets: inherit
with:
self_hosting: true

View File

@@ -3,12 +3,6 @@ run-name: Destroying Dynamic PR Environment for ${{ github.ref_name }} by @${{ g
on:
workflow_call:
inputs:
self_hosting:
description: 'Self-hosting stack'
required: false
type: boolean
default: false
jobs:
destroy_pr:
@@ -60,20 +54,9 @@ jobs:
namespace: default
user: oidc-token
- name: Destroy PR Review ENV
if: ${{ !inputs.self_hosting }}
run: |
kubectl delete metabase -n hosting-pr${{ github.event.number }} hosting-pr${{ github.event.number }}
kubectl delete ns hosting-pr${{ github.event.number }}
- name: Setup Helm
if: ${{ inputs.self_hosting }}
uses: azure/setup-helm@v4
with:
version: 'latest'
- name: Destroy PR Review ENV (self-hosting)
if: ${{ inputs.self_hosting }}
run: |
helm uninstall hosting-pr${{ github.event.number }} --namespace hosting-pr${{ github.event.number }} || true
kubectl delete ns hosting-pr${{ github.event.number }} || true
- name: Setup psql client
run: |
sudo apt-get update

View File

@@ -10,7 +10,7 @@ concurrency:
jobs:
uberjar:
if: ${{ github.event.label.name == 'PR-Env' || github.event.label.name == 'PR-Env-self-hosting' }}
if: ${{ github.event.label.name == 'PR-Env' }}
uses: ./.github/workflows/uberjar.yml
secrets: inherit
@@ -19,13 +19,3 @@ jobs:
if: ${{ github.event.label.name == 'PR-Env' }}
uses: ./.github/workflows/pr-env.yml
secrets: inherit
with:
self_hosting: false
pr-env-self-hosting:
needs: [uberjar]
if: ${{ github.event.label.name == 'PR-Env-self-hosting' }}
uses: ./.github/workflows/pr-env.yml
secrets: inherit
with:
self_hosting: true

View File

@@ -3,12 +3,6 @@ run-name: Building Dynamic PR Environment for ${{ github.ref_name }} by @${{ git
on:
workflow_call:
inputs:
self_hosting:
description: 'Deploy self-hosting stack'
required: false
type: boolean
default: false
jobs:
build:
runs-on: ubuntu-latest
@@ -119,10 +113,8 @@ jobs:
| grep -q 1 \
|| psql -c "CREATE DATABASE hosting_pr${{ github.event.number }}"
- name: Download Deployment YAML template
if: ${{ !inputs.self_hosting }}
run: aws s3 cp s3://metabase-pr-env/metabase.yml.tmpl ./metabase.yml.tmpl
- name: Render Deployment YAML
if: ${{ !inputs.self_hosting }}
uses: nowactions/envsubst@v1
with:
input: ./metabase.yml.tmpl
@@ -133,52 +125,8 @@ jobs:
MB_PREMIUM_EMBEDDING_TOKEN: ${{ secrets.PR_ENV_MB_PREMIUM_EMBEDDING_TOKEN }}
SHA: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Deploy PR Review ENV
if: ${{ !inputs.self_hosting }}
run: |
kubectl apply -f ./metabase.yml
- name: Download Helm values template (self-hosting)
if: ${{ inputs.self_hosting }}
run: aws s3 cp s3://metabase-pr-env/values.yaml.tmpl ./values.yaml.tmpl
- name: Render Helm values (self-hosting)
if: ${{ inputs.self_hosting }}
uses: nowactions/envsubst@v1
with:
input: ./values.yaml.tmpl
output: ./values.yaml
env:
IMAGE_TAG: pr${{ github.event.number }}
PR_NUMBER: ${{ github.event.number }}
MB_PREMIUM_EMBEDDING_TOKEN: ${{ secrets.PR_ENV_MB_PREMIUM_EMBEDDING_TOKEN }}
SHA: ${{ github.event.pull_request.head.sha || github.sha }}
QUAY_USERNAME: ${{ secrets.QUAY_USERNAME }}
QUAY_PASSWORD: ${{ secrets.QUAY_PASSWORD }}
OPEN_AI_API_KEY: ${{ secrets.OPEN_AI_API_KEY }}
DB_HOST: ${{ secrets.PR_ENV_DB_HOST }}
DB_USERNAME: ${{ secrets.PR_ENV_DB_USER }}
DB_PASSWORD: ${{ secrets.PR_ENV_DB_PASSWORD }}
CONFIG: ${{ secrets.PR_ENV_CONFIG }}
- name: Setup Helm
if: ${{ inputs.self_hosting }}
uses: azure/setup-helm@v4
with:
version: 'latest'
- name: Login to Quay.io
if: ${{ inputs.self_hosting }}
run: |
helm registry login quay.io \
--username ${{ secrets.QUAY_USERNAME }} \
--password ${{ secrets.QUAY_PASSWORD }}
- name: Deploy PR Review ENV (self-hosting)
if: ${{ inputs.self_hosting }}
run: |
helm upgrade --install hosting-pr${{ github.event.number }} \
oci://quay.io/enterprisemetabase/chart-metabase-stack \
--namespace hosting-pr${{ github.event.number }} \
--create-namespace \
--values ./values.yaml \
--atomic \
--timeout 10m
preview_links:
runs-on: ubuntu-latest
needs: [deploy_pr]
@@ -191,4 +139,3 @@ jobs:
✅ Preview:
https://pr${{ github.event.number }}.coredev.metabase.com
🗒️ [login instructions](https://www.notion.so/metabase/Ephemeral-PR-Environments-c74a3710c87a460dbf4fc0007a001458)
📦 Deployment type: ${{ inputs.self_hosting && 'PR-Env-self-hosting' || 'PR-Env' }}

View File

@@ -24,39 +24,6 @@ on:
required: true
jobs:
test-mode-config:
runs-on: ubuntu-22.04
timeout-minutes: 5
outputs:
run_mode: ${{ fromJson(steps.config.outputs.result).runMode }}
dev_server: ${{ fromJson(steps.config.outputs.result).metastoreDevServer }}
steps:
- uses: actions/checkout@v4
with:
sparse-checkout: release
- name: Prepare build scripts
run: cd ${{ github.workspace }}/release && yarn && yarn build
# note: this block can be removed after we stop needing to be able to test versions before v55
- name: Get test mode config
id: config
uses: actions/github-script@v7
with:
script: | # js
const { getMajorVersion } = require('${{ github.workspace }}/release/dist/index.cjs');
// Versions before v55 used a single token for testing
// with v55 and later we use enterprise tokens that mirror actual Metabase plans
const MULTIPLE_TEST_TOKEN_THRESHOLD = 55;
const version = '${{ inputs.version }}';
const majorVersion = Number(getMajorVersion(version));
const oldTestToken = majorVersion < MULTIPLE_TEST_TOKEN_THRESHOLD;
return ({
runMode: oldTestToken ? 'prod' : 'e2e',
metastoreDevServer: oldTestToken ? '' : '${{ vars.METASTORE_DEV_SERVER_URL }}'
});
release-artifact:
runs-on: ubuntu-22.04
timeout-minutes: 10
@@ -129,19 +96,17 @@ jobs:
run-sanity-check:
runs-on: ubuntu-22.04
timeout-minutes: 30
needs: [test-mode-config, release-artifact]
needs: [release-artifact]
strategy:
matrix:
edition: [oss, ee]
env:
MB_EDITION: ${{ matrix.edition }}
DISPLAY: ""
MB_RUN_MODE: ${{ needs.test-mode-config.outputs.run_mode }}
METASTORE_DEV_SERVER_URL: ${{ needs.test-mode-config.outputs.dev_server }}
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ secrets.STAGING_MB_ALL_FEATURES_TOKEN }}
CYPRESS_MB_STARTER_CLOUD_TOKEN: ${{ secrets.STAGING_MB_STARTER_CLOUD_TOKEN }}
CYPRESS_MB_PRO_CLOUD_TOKEN: ${{ secrets.STAGING_MB_PRO_CLOUD_TOKEN }}
CYPRESS_MB_PRO_SELF_HOSTED_TOKEN: ${{ secrets.STAGING_MB_PRO_SELF_HOSTED_TOKEN }}
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ secrets.MB_ALL_FEATURES_TOKEN }}
CYPRESS_MB_STARTER_CLOUD_TOKEN: ${{ secrets.MB_STARTER_CLOUD_TOKEN }}
CYPRESS_MB_PRO_CLOUD_TOKEN: ${{ secrets.MB_PRO_CLOUD_TOKEN }}
CYPRESS_MB_PRO_SELF_HOSTED_TOKEN: ${{ secrets.MB_PRO_SELF_HOSTED_TOKEN }}
# these are needed for backwards compatibility with release branches earlier than v55, since
# this workflow always runs from master, but we run tests from the release branch
CYPRESS_ALL_FEATURES_TOKEN: ${{ secrets.MB_ALL_FEATURES_TOKEN }}
@@ -194,21 +159,19 @@ jobs:
- name: Run a few important OSS Cypress tests as sanity check
if: ${{ matrix.edition == 'oss' }}
env:
CYPRESS_BROWSER: ${{ steps.cypress-prep.outputs.chrome-path }}
run: |
node e2e/runner/run_cypress_ci.js e2e \
--env grepTags="@smoke+-@EE" \
--spec './e2e/test/scenarios/**/*.cy.spec.(js|ts)'
--env grepTags="@smoke+-@EE",grepOmitFiltered=true \
--spec './e2e/test/scenarios/**/*.cy.spec.(js|ts)' \
--browser ${{ steps.cypress-prep.outputs.chrome-path }}
- name: Run a few important EE Cypress tests as sanity check
if: ${{ matrix.edition == 'ee' }}
env:
CYPRESS_BROWSER: ${{ steps.cypress-prep.outputs.chrome-path }}
run: |
node e2e/runner/run_cypress_ci.js e2e \
--env grepTags="@smoke+-@OSS" \
--spec './e2e/test/scenarios/**/*.cy.spec.(js|ts)'
--env grepTags="@smoke+-@OSS",grepOmitFiltered=true \
--spec './e2e/test/scenarios/**/*.cy.spec.(js|ts)' \
--browser ${{ steps.cypress-prep.outputs.chrome-path }}
- name: Upload Cypress Artifacts upon failure
uses: actions/upload-artifact@v4

View File

@@ -345,9 +345,8 @@ jobs:
GH_TOKEN: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
- name: Edit PR adding useful information (using Metabase bot app)
# metabase/embedding team is added automatically as code owner of updated `package.template.json` file
run: |
gh pr edit --add-reviewer albertoperdomo --add-label no-backport
gh pr edit --add-reviewer @metabase/embedding,albertoperdomo --add-label no-backport
env:
GH_TOKEN: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}

View File

@@ -47,7 +47,7 @@ jobs:
with:
script: | # js
const {
getMajorVersionFromRef,
getVersionFromReleaseBranch,
getMajorVersion,
} = require('${{ github.workspace }}/release/dist/index.cjs');
@@ -60,15 +60,14 @@ jobs:
}
// otherwise, get the version from the release branch name
const ref = context.payload.ref;
const majorVersion = getMajorVersionFromRef(ref);
const branch = context.payload.ref;
const version = getVersionFromReleaseBranch(branch);
const majorVersion = getMajorVersion(version);
console.log({ ref, majorVersion });
console.log({ branch, version, majorVersion });
core.exportVariable('VERSION', majorVersion);
- name: generate release Log
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: cd release && tsx ./src/release-log-run.ts $VERSION > v$VERSION.html
- name: generate release channel log
run: cd release && tsx ./src/release-channel-log.ts > channels.html

View File

@@ -8,11 +8,6 @@ on:
description: 'Major Metabase version (e.g. 45, 52, 68)'
type: number
required: true
override_sha:
description: DANGEROUSLY allow releasing from this commit sha, regardless of the state of CI (can be empty)
type: string
required: false
default: ''
schedule:
- cron: '0 21 * * 1-5' # every weekday at 4 pm EST/5 pm EDT
@@ -42,7 +37,7 @@ jobs:
const lastRelease = currentRelease - 1;
const AUTO_RELEASE_VERSIONS = [lastRelease, currentRelease];
async function releasePatchFor(majorVersion, overrideSha) {
async function releasePatchFor(majorVersion) {
const nextPatch = await getNextPatchVersion({
github,
owner: context.repo.owner,
@@ -50,7 +45,7 @@ jobs:
majorVersion,
});
const releaseCommit = overrideSha || await getLatestGreenCommit({
const goodCommit = await getLatestGreenCommit({
github,
owner: context.repo.owner,
repo: context.repo.repo,
@@ -61,27 +56,26 @@ jobs:
github,
owner: context.repo.owner,
repo: context.repo.repo,
ref: releaseCommit,
ref: goodCommit,
majorVersion: Number(majorVersion),
});
console.log(
{ nextPatch, releaseCommit, hasBeenReleased }
);
if (nextPatch && releaseCommit && !hasBeenReleased) {
if (nextPatch && goodCommit && !hasBeenReleased) {
await github.rest.actions.createWorkflowDispatch({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'tag-for-release.yml',
ref: 'refs/heads/master',
inputs: {
commit: releaseCommit,
commit: goodCommit,
version: nextPatch,
auto: true,
}
}).catch(console.error);
});
} else {
console.log(
{ nextPatch, goodCommit, hasBeenReleased }
);
console.error(`No new patch version or no green commit found for v${majorVersion}`);
}
}
@@ -94,15 +88,7 @@ jobs:
throw new Error(`Invalid version number: ${inputVersion}`);
}
// only allow overriding the commit SHA for workflow_dispatch events with a provided sha
const overrideSha = String(context.payload?.inputs?.override_sha ?? '').trim();
const isValidOverride = overrideSha.length === 40;
if (!isValidOverride && overrideSha.length > 0) {
throw new Error(`Invalid override SHA provided: ${overrideSha}`);
}
await releasePatchFor(inputVersion, isValidOverride ? overrideSha : undefined);
await releasePatchFor(inputVersion);
} else { // scheduled release of AUTO_RELEASE_VERSIONS
await Promise.all(AUTO_RELEASE_VERSIONS.map(version => releasePatchFor(version)));
await Promise.all(AUTO_RELEASE_VERSIONS.map(releasePatchFor));
}

View File

@@ -189,16 +189,14 @@ jobs:
- name: Upload to s3 ${{ inputs.tag_name }} path
run: | # sh
aws s3 cp \
s3://${{ vars.AWS_S3_DOWNLOADS_BUCKET }}/${{ steps.source_path.outputs.result }}/ \
s3://${{ vars.AWS_S3_DOWNLOADS_BUCKET }}/${{ steps.upload_path.outputs.result }}/ \
--recursive \
--include "*"
s3://${{ vars.AWS_S3_DOWNLOADS_BUCKET }}/${{ steps.source_path.outputs.result }}/metabase.jar \
s3://${{ vars.AWS_S3_DOWNLOADS_BUCKET }}/${{ steps.upload_path.outputs.result }}/metabase.jar
- name: Create cloudfront invalidation
run: |
aws cloudfront create-invalidation \
--distribution-id ${{ vars.AWS_CLOUDFRONT_DOWNLOADS_ID }} \
--paths "/${{ steps.upload_path.outputs.result }}/*"
--paths /${{ steps.upload_path.outputs.result }}/metabase.jar
- name: Download the jars
run: | # sh

View File

@@ -211,15 +211,13 @@ jobs:
return version_path;
- name: Upload to S3
run: |
aws s3 cp ./metabase.jar s3://${{ vars.AWS_S3_DOWNLOADS_BUCKET }}/${{ steps.version_path.outputs.result }}/metabase.jar
aws s3 cp ./SHA256.sum s3://${{ vars.AWS_S3_DOWNLOADS_BUCKET }}/${{ steps.version_path.outputs.result }}/metabase.sha256
run: aws s3 cp ./metabase.jar s3://${{ vars.AWS_S3_DOWNLOADS_BUCKET }}/${{ steps.version_path.outputs.result }}/metabase.jar
- name: Create cloudfront invalidation
run: |
aws cloudfront create-invalidation \
--distribution-id ${{ vars.AWS_CLOUDFRONT_DOWNLOADS_ID }} \
--paths "/${{ steps.version_path.outputs.result }}/*"
--paths /${{ steps.version_path.outputs.result }}/metabase.jar
verify-s3-download:
runs-on: ubuntu-22.04

View File

@@ -12,7 +12,7 @@ on:
jobs:
files-changed:
if: ${{ !contains(github.event.pull_request.labels.*.name,'ci skip') && !contains(github.event.pull_request.labels.*.name,'minimal-tests') }}
if: ${{ !contains(github.event.pull_request.labels.*.name,'ci skip') }}
name: Check which files changed
runs-on: ubuntu-22.04
timeout-minutes: 5
@@ -25,11 +25,6 @@ jobs:
frontend_all: ${{ steps.changes.outputs.frontend_all }}
frontend_sources: ${{ steps.changes.outputs.frontend_sources }}
documentation: ${{ steps.changes.outputs.documentation }}
frontend_embedding_sdk_sources: ${{ steps.changes.outputs.frontend_embedding_sdk_sources }}
embedding_documentation: ${{ steps.changes.outputs.embedding_documentation }}
embedding_sdk_components: ${{ steps.changes.outputs.embedding_sdk_components }}
embedding_sdk_host_sample_apps: ${{ steps.changes.outputs.embedding_sdk_host_sample_apps }}
embedding_sdk_ci: ${{ steps.changes.outputs.embedding_sdk_ci }}
steps:
- uses: actions/checkout@v4
- name: Test which files changed
@@ -41,19 +36,49 @@ jobs:
list-files: csv
filters: .github/file-paths.yaml
static-viz-files-changed:
name: Check whether static-viz files changed
if: ${{ !contains(github.event.pull_request.labels.*.name,'ci skip') }}
runs-on: ubuntu-22.04
timeout-minutes: 10
outputs:
static_viz: ${{ steps.static_viz.outputs.static_viz }}
steps:
- uses: actions/checkout@v4
- name: Prepare front-end environment
uses: ./.github/actions/prepare-frontend
- name: Prepare back-end environment
uses: ./.github/actions/prepare-backend
- name: Build static-viz frontend
run: yarn build-static-viz
env:
MB_EDITION: ee
- name: Upload Static Viz Bundle Artifact
uses: actions/upload-artifact@v4
with:
name: static-viz-bundle-${{ github.sha }}
path: resources/frontend_client/app/dist
- name: Check for static viz changes
uses: dorny/paths-filter@v3.0.0
id: static_viz
with:
token: ${{ github.token }}
filters: .github/static-viz-sources.yaml
uberjar:
needs: [files-changed]
needs: [files-changed, static-viz-files-changed]
if: ${{ !cancelled() && !(needs.files-changed.outputs.documentation == 'true' && needs.files-changed.outputs.backend_all == 'false' && needs.files-changed.outputs.frontend_all == 'false' && needs.files-changed.outputs.e2e_all == 'false') }}
uses: ./.github/workflows/uberjar.yml
secrets: inherit
backend-tests:
if: ${{ !cancelled() && needs.files-changed.result == 'success' }}
needs: [files-changed]
needs: [files-changed, static-viz-files-changed]
uses: ./.github/workflows/backend.yml
secrets: inherit
with:
skip: ${{ needs.files-changed.outputs.backend_all != 'true' }}
skip: ${{ needs.files-changed.outputs.backend_all != 'true' && needs.static-viz-files-changed.outputs.static_viz != 'true' }}
semantic-search-tests:
needs: files-changed
@@ -97,51 +122,18 @@ jobs:
# list of changed specs files, then only specs are changed and we can run only those specs
specs: ${{ needs.files-changed.outputs.e2e_all_files == needs.files-changed.outputs.e2e_specs_files && needs.files-changed.outputs.e2e_specs_files || '' }}
embedding-sdk-package:
needs: [files-changed]
if: |
!cancelled() &&
(needs.files-changed.outputs.embedding_sdk_components == 'true' ||
needs.files-changed.outputs.frontend_embedding_sdk_sources == 'true' ||
needs.files-changed.outputs.embedding_documentation == 'true' ||
needs.files-changed.outputs.embedding_sdk_ci == 'true')
uses: ./.github/workflows/embedding-sdk-package-build.yml
secrets: inherit
sdk-tests:
needs: [uberjar, embedding-sdk-package, files-changed]
if: |
needs.embedding-sdk-package.result == 'success' &&
needs.uberjar.result == 'success'
needs: files-changed
uses: ./.github/workflows/embedding-sdk.yml
secrets: inherit
with:
cli-snippets-type-check: ${{ needs.files-changed.outputs.frontend_embedding_sdk_sources == 'true' }}
docs-snippets-type-check: ${{ needs.files-changed.outputs.embedding_documentation == 'true' }}
documentation-validation: ${{ needs.files-changed.outputs.frontend_embedding_sdk_sources == 'true' }}
component-tests: ${{ needs.files-changed.outputs.embedding_sdk_components == 'true' }}
host-sample-apps-tests: ${{ needs.files-changed.outputs.embedding_sdk_host_sample_apps == 'true' }}
pr-env:
needs: [uberjar]
if: |
!cancelled() &&
contains(github.event.pull_request.labels.*.name, 'PR-Env') &&
!contains(github.event.pull_request.labels.*.name, 'PR-Env-self-hosting')
contains(github.event.pull_request.labels.*.name, 'PR-Env')
uses: ./.github/workflows/pr-env.yml
secrets: inherit
with:
self_hosting: false
pr-env-self-hosting:
needs: [uberjar]
if: |
!cancelled() &&
contains(github.event.pull_request.labels.*.name, 'PR-Env-self-hosting')
uses: ./.github/workflows/pr-env.yml
secrets: inherit
with:
self_hosting: true
containerize:
needs: [uberjar]

View File

@@ -111,7 +111,7 @@ jobs:
uses: actions/github-script@v7
id: release_branch
with:
result-encoding: string
result_encoding: string
script: | # js
const { getReleaseBranch } = require('${{ github.workspace }}/release/dist/index.cjs');
const version = '${{ inputs.version }}';

1
.gitignore vendored
View File

@@ -68,6 +68,7 @@ cypress.env.json
/resources/frontend_client/embed-sdk.html
/resources/frontend_client/index.html
/resources/frontend_client/public.html
/resources/frontend_client/app/assets/pyodide/
/resources/embedding-sdk
/resources/i18n/*.edn
/resources/instance_analytics.zip

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 931 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 212 KiB

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

View File

@@ -1,8 +1,5 @@
accept = ["200..=204", "429"]
exclude = ["\\.html(#.*)?$"]
exclude_path = ["docs/util"]
exclude_path = ["docs/embedding/sdk"]
no_progress = true
include_fragments = true
verbose = "error"
include_verbatim = false
cache = false
verbose = "error"

Some files were not shown because too many files have changed in this diff Show More