Compare commits
7 Commits
openapi-sp
...
workspaces
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0dd7d78c07 | ||
|
|
33daf1d822 | ||
|
|
9faad636cb | ||
|
|
b201dca6cf | ||
|
|
f1982e56e3 | ||
|
|
439c3f3be5 | ||
|
|
37449925fa |
@@ -1,12 +0,0 @@
|
||||
Generate a CI failure report for PR $PR_NUM_OR_URL (or current branch if no argument given).
|
||||
|
||||
Run `./bin/mage ci-report $PR_NUM_OR_URL` to generate the report, then:
|
||||
|
||||
1. **Summarize the failures** - What tests failed and in which jobs?
|
||||
2. **Categorize** - Are these real failures, flaky tests, or infrastructure issues?
|
||||
3. **Recommend next steps**:
|
||||
- If flaky: suggest rerunning with `gh run rerun <run-id> --failed -R metabase/metabase`
|
||||
- If real: identify the root cause and suggest fixes
|
||||
- If infrastructure: note the issue type (timeout, OOM, network)
|
||||
|
||||
Focus on actionable insights. If all checks are passing, confirm that briefly.
|
||||
@@ -1 +0,0 @@
|
||||
Run mutation testing for $ARGUMENTS
|
||||
@@ -1,86 +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`
|
||||
- **Run all tests for a module:** `./bin/mage run-tests test/metabase/notification` Because the module lives in that directory.
|
||||
|
||||
Note: the `./bin/mage run-tests` command accepts multiple args, so you can pass
|
||||
`./bin/mage run-tests namespace/test-name namespace/other-test namespace/third-test`
|
||||
to run 3 tests, or
|
||||
`./bin/mage run-tests test/metabase/module1 test/metabase/module2` to run 2 modules.
|
||||
|
||||
## 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
|
||||
|
||||
> **Note:** If you have `clojure-mcp` tools available (check for tools like `clojure_eval`),
|
||||
> **always prefer those over `./bin/mage -repl`**. The MCP tools provide better integration,
|
||||
> richer feedback, and avoid shell escaping issues. Only use `./bin/mage -repl` as a fallback
|
||||
> when clojure-mcp is not available.
|
||||
|
||||
- **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
|
||||
@@ -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 YYYY-MM-DD) -- 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 non-comment 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,21 +0,0 @@
|
||||
## Linting and Formatting
|
||||
|
||||
- **Lint:** `bun run lint-eslint-pure`
|
||||
- Run ESLint on the codebase
|
||||
- **Format:** `bun run prettier`
|
||||
- Format code using Prettier
|
||||
- **Type Check:** `bun run type-check-pure`
|
||||
- Run TypeScript type checking
|
||||
|
||||
## Testing
|
||||
|
||||
### JavaScript/TypeScript Tests
|
||||
|
||||
- **Test a specific file:** `bun run test-unit-keep-cljs path/to/file.unit.spec.js`
|
||||
- **Test by pattern:** `bun run test-unit-keep-cljs -t "pattern"`
|
||||
- Runs tests matching the given pattern
|
||||
|
||||
### ClojureScript Tests
|
||||
|
||||
- **Test ClojureScript:** `bun run test-cljs`
|
||||
- Run ClojureScript tests
|
||||
@@ -1,420 +0,0 @@
|
||||
---
|
||||
name: add-malli-schemas
|
||||
description: Efficiently add Malli schemas to API endpoints in the Metabase codebase with proper patterns, validation timing, and error handling
|
||||
---
|
||||
|
||||
# Add Malli Schemas to API Endpoints
|
||||
|
||||
This skill helps you efficiently and uniformly add Malli schemas to API endpoints in the Metabase codebase.
|
||||
|
||||
## Reference Files (Best Examples)
|
||||
|
||||
- `src/metabase/warehouses/api.clj` - Most comprehensive schemas, custom error messages
|
||||
- `src/metabase/api_keys/api.clj` - Excellent response schemas
|
||||
- `src/metabase/collections/api.clj` - Great named schema patterns
|
||||
- `src/metabase/timeline/api/timeline.clj` - Clean, simple examples
|
||||
|
||||
## Quick Checklist
|
||||
|
||||
When adding Malli schemas to an endpoint:
|
||||
|
||||
- [ ] Route params have schemas
|
||||
- [ ] Query params have schemas with `:optional true` and `:default` where appropriate
|
||||
- [ ] Request body has a schema (for POST/PUT)
|
||||
- [ ] Response schema is defined (using `:-` after route string)
|
||||
- [ ] Use existing schema types from `ms` namespace when possible
|
||||
- [ ] Consider creating named schemas for reusable or complex types
|
||||
- [ ] Add contextual error messages for validation failures
|
||||
|
||||
## Basic Structure
|
||||
|
||||
### Complete Endpoint Example
|
||||
|
||||
```clojure
|
||||
(mr/def ::Color [:enum "red" "blue" "green"])
|
||||
|
||||
(mr/def ::ResponseSchema
|
||||
[:map
|
||||
[:id pos-int?]
|
||||
[:name string?]
|
||||
[:color ::Color]
|
||||
[:created_at ms/TemporalString]])
|
||||
|
||||
(api.macros/defendpoint :post "/:name" :- ::ResponseSchema
|
||||
"Create a resource with a given name."
|
||||
[;; Route Params:
|
||||
{:keys [name]} :- [:map [:name ms/NonBlankString]]
|
||||
;; Query Params:
|
||||
{:keys [include archived]} :- [:map
|
||||
[:include {:optional true} [:maybe [:= "details"]]]
|
||||
[:archived {:default false} [:maybe ms/BooleanValue]]]
|
||||
;; Body Params:
|
||||
{:keys [color]} :- [:map [:color ::Color]]
|
||||
]
|
||||
;; endpoint implementation, ex:
|
||||
{:id 99
|
||||
:name (str "mr or mrs " name)
|
||||
:color ({"red" "blue" "blue" "green" "green" "red"} color)
|
||||
:created_at (t/format (t/formatter "yyyy-MM-dd'T'HH:mm:ssXXX") (t/zoned-date-time))}
|
||||
)
|
||||
```
|
||||
|
||||
## Common Schema Patterns
|
||||
|
||||
1. Route Params (the 5 in `api/user/id/5`)
|
||||
2. Query Params (the sort+asc pair in `api/users?sort=asc`)
|
||||
3. Body Params (the contents of a request body. Almost always decoded from json into edn)
|
||||
4. The Raw Request map
|
||||
|
||||
Of the 4 arguments, deprioritize usage of the raw request unless necessary.
|
||||
|
||||
### Route Params
|
||||
|
||||
Always required, typically just a map with an ID:
|
||||
|
||||
```clojure
|
||||
[{:keys [id]} :- [:map [:id ms/PositiveInt]]]
|
||||
```
|
||||
|
||||
For multiple route params:
|
||||
|
||||
```clojure
|
||||
[{:keys [id field-id]} :- [:map
|
||||
[:id ms/PositiveInt]
|
||||
[:field-id ms/PositiveInt]]]
|
||||
```
|
||||
|
||||
### Query Params
|
||||
|
||||
Add properties for `{:optional true ...}` and `:default` values:
|
||||
|
||||
```clojure
|
||||
{:keys [archived include limit offset]} :- [:map
|
||||
[:archived {:default false} [:maybe ms/BooleanValue]]
|
||||
[:include {:optional true} [:maybe [:= "tables"]]]
|
||||
[:limit {:optional true} [:maybe ms/PositiveInt]]
|
||||
[:offset {:optional true} [:maybe ms/PositiveInt]]]
|
||||
```
|
||||
|
||||
### Request Body (POST/PUT)
|
||||
|
||||
```clojure
|
||||
{:keys [name description parent_id]} :- [:map
|
||||
[:name ms/NonBlankString]
|
||||
[:description {:optional true} [:maybe ms/NonBlankString]]
|
||||
[:parent_id {:optional true} [:maybe ms/PositiveInt]]]
|
||||
```
|
||||
|
||||
### Response Schemas
|
||||
|
||||
#### Simple inline response:
|
||||
|
||||
```clojure
|
||||
(api.macros/defendpoint :get "/:id" :- [:map
|
||||
[:id pos-int?]
|
||||
[:name string?]]
|
||||
"Get a thing"
|
||||
...)
|
||||
```
|
||||
|
||||
#### Named schema for reuse:
|
||||
|
||||
```clojure
|
||||
(mr/def ::Thing
|
||||
[:map
|
||||
[:id pos-int?]
|
||||
[:name string?]
|
||||
[:description [:maybe string?]]])
|
||||
|
||||
(api.macros/defendpoint :get "/:id" :- ::Thing
|
||||
"Get a thing"
|
||||
...)
|
||||
|
||||
(api.macros/defendpoint :get "/" :- [:sequential ::Thing]
|
||||
"Get all things"
|
||||
...)
|
||||
```
|
||||
|
||||
## Common Schema Types
|
||||
|
||||
### From `metabase.util.malli.schema` (aliased as `ms`)
|
||||
|
||||
Prefer the schemas in the ms/* namespace, since they work better with our api infrastructure.
|
||||
|
||||
For example use `ms/PositiveInt` instead of `pos-int?`.
|
||||
|
||||
```clojure
|
||||
ms/PositiveInt ;; Positive integer
|
||||
ms/NonBlankString ;; Non-empty string
|
||||
ms/BooleanValue ;; String "true"/"false" or boolean
|
||||
ms/MaybeBooleanValue ;; BooleanValue or nil
|
||||
ms/TemporalString ;; ISO-8601 date/time string (for REQUEST params only!)
|
||||
ms/Map ;; Any map
|
||||
ms/JSONString ;; JSON-encoded string
|
||||
ms/PositiveNum ;; Positive number
|
||||
ms/IntGreaterThanOrEqualToZero ;; 0 or positive
|
||||
```
|
||||
|
||||
**IMPORTANT:** For response schemas, use `:any` for temporal fields, not `ms/TemporalString`!
|
||||
Response schemas validate BEFORE JSON serialization, so they see Java Time objects.
|
||||
|
||||
### Built-in Malli Types
|
||||
|
||||
```clojure
|
||||
:string ;; Any string
|
||||
:boolean ;; true/false
|
||||
:int ;; Any integer
|
||||
:keyword ;; Clojure keyword
|
||||
pos-int? ;; Positive integer predicate
|
||||
[:maybe X] ;; X or nil
|
||||
[:enum "a" "b" "c"] ;; One of these values
|
||||
[:or X Y] ;; Schema that satisfies X or Y
|
||||
[:and X Y] ;; Schema that satisfies X and Y
|
||||
[:sequential X] ;; Sequential of Xs
|
||||
[:set X] ;; Set of Xs
|
||||
[:map-of K V] ;; Map with keys w/ schema K and values w/ schema V
|
||||
[:tuple X Y Z] ;; Fixed-length tuple of schemas X Y Z
|
||||
```
|
||||
|
||||
Avoid using sequence schemas unless completely necessary.
|
||||
|
||||
## Step-by-Step: Adding Schemas to an Endpoint
|
||||
|
||||
### Example: Adding return schema to `GET /api/field/:id/related`
|
||||
|
||||
**Before:**
|
||||
```clojure
|
||||
(api.macros/defendpoint :get "/:id/related"
|
||||
"Return related entities."
|
||||
[{:keys [id]} :- [:map [:id ms/PositiveInt]]]
|
||||
(-> (t2/select-one :model/Field :id id) api/read-check xrays/related))
|
||||
```
|
||||
|
||||
**Step 1:** Check what the function returns (look at `xrays/related`)
|
||||
|
||||
**Step 2:** Define response schema based on return type:
|
||||
|
||||
```clojure
|
||||
(mr/def ::RelatedEntity
|
||||
[:map
|
||||
[:tables [:sequential [:map [:id pos-int?] [:name string?]]]]
|
||||
[:fields [:sequential [:map [:id pos-int?] [:name string?]]]]])
|
||||
```
|
||||
|
||||
**Step 3:** Add response schema to endpoint:
|
||||
|
||||
```clojure
|
||||
(api.macros/defendpoint :get "/:id/related" :- ::RelatedEntity
|
||||
"Return related entities."
|
||||
[{:keys [id]} :- [:map [:id ms/PositiveInt]]]
|
||||
(-> (t2/select-one :model/Field :id id) api/read-check xrays/related))
|
||||
```
|
||||
|
||||
## Advanced Patterns
|
||||
|
||||
### Custom Error Messages
|
||||
|
||||
```clojure
|
||||
(def DBEngineString
|
||||
"Schema for a valid database engine name."
|
||||
(mu/with-api-error-message
|
||||
[:and
|
||||
ms/NonBlankString
|
||||
[:fn
|
||||
{:error/message "Valid database engine"}
|
||||
#(u/ignore-exceptions (driver/the-driver %))]]
|
||||
(deferred-tru "value must be a valid database engine.")))
|
||||
```
|
||||
|
||||
### Enum with Documentation
|
||||
|
||||
```clojure
|
||||
(def PinnedState
|
||||
(into [:enum {:error/message "pinned state must be 'all', 'is_pinned', or 'is_not_pinned'"}]
|
||||
#{"all" "is_pinned" "is_not_pinned"}))
|
||||
```
|
||||
|
||||
### Complex Nested Response
|
||||
|
||||
```clojure
|
||||
(mr/def ::DashboardQuestionCandidate
|
||||
[:map
|
||||
[:id ms/PositiveInt]
|
||||
[:name ms/NonBlankString]
|
||||
[:description [:maybe string?]]
|
||||
[:sole_dashboard_info
|
||||
[:map
|
||||
[:id ms/PositiveInt]
|
||||
[:name ms/NonBlankString]
|
||||
[:description [:maybe string?]]]]])
|
||||
|
||||
(mr/def ::DashboardQuestionCandidatesResponse
|
||||
[:map
|
||||
[:data [:sequential ::DashboardQuestionCandidate]]
|
||||
[:total ms/PositiveInt]])
|
||||
```
|
||||
|
||||
### Paginated Response Pattern
|
||||
|
||||
```clojure
|
||||
(mr/def ::PaginatedResponse
|
||||
[:map
|
||||
[:data [:sequential ::Item]]
|
||||
[:total integer?]
|
||||
[:limit {:optional true} [:maybe integer?]]
|
||||
[:offset {:optional true} [:maybe integer?]]])
|
||||
```
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
### Don't: Forget `:maybe` for nullable fields
|
||||
|
||||
```clojure
|
||||
[:description ms/NonBlankString] ;; WRONG - fails if nil
|
||||
[:description [:maybe ms/NonBlankString]] ;; RIGHT - allows nil
|
||||
```
|
||||
|
||||
### Don't: Forget `:optional true` for optional query params
|
||||
|
||||
```clojure
|
||||
[:limit ms/PositiveInt] ;; WRONG - required but shouldn't be
|
||||
[:limit {:optional true} [:maybe ms/PositiveInt]] ;; RIGHT
|
||||
```
|
||||
|
||||
### Don't: Forget `:default` values for known params
|
||||
|
||||
```clojure
|
||||
[:limit ms/PositiveInt] ;; WRONG - required but shouldn't be
|
||||
[:limit {:optional true :default 0} [:maybe ms/PositiveInt]] ;; RIGHT
|
||||
```
|
||||
|
||||
|
||||
### Don't: Mix up route params, query params, and body
|
||||
|
||||
```clojure
|
||||
;; WRONG - all in one map
|
||||
[{:keys [id name archived]} :- [:map ...]]
|
||||
|
||||
;; RIGHT - separate destructuring
|
||||
[{:keys [id]} :- [:map [:id ms/PositiveInt]]
|
||||
{:keys [archived]} :- [:map [:archived {:default false} ms/BooleanValue]]
|
||||
{:keys [name]} :- [:map [:name ms/NonBlankString]]]
|
||||
```
|
||||
|
||||
### Don't: Use `ms/TemporalString` for Java Time objects in response schemas
|
||||
|
||||
```clojure
|
||||
;; WRONG - Java Time objects aren't strings yet
|
||||
[:date_joined ms/TemporalString]
|
||||
|
||||
;; RIGHT - schemas validate BEFORE JSON serialization
|
||||
[:date_joined :any] ;; Java Time object, serialized to string by middleware
|
||||
[:last_login [:maybe :any]] ;; Java Time object or nil
|
||||
```
|
||||
|
||||
**Why:** Response schemas validate the internal Clojure data structures BEFORE they are serialized to JSON. Java Time objects like `OffsetDateTime` get converted to ISO-8601 strings by the JSON middleware, so the schema needs to accept the raw Java objects.
|
||||
|
||||
### Don't: Use `[:sequential X]` when the data is actually a set
|
||||
|
||||
```clojure
|
||||
;; WRONG - group_ids is actually a set
|
||||
[:group_ids {:optional true} [:sequential pos-int?]]
|
||||
|
||||
;; RIGHT - matches the actual data structure
|
||||
[:group_ids {:optional true} [:maybe [:set pos-int?]]]
|
||||
```
|
||||
|
||||
**Why:** Toucan hydration methods often return sets. The JSON middleware will serialize sets to arrays, but the schema validates before serialization.
|
||||
|
||||
### Don't: Create anonymous schemas for reused structures
|
||||
|
||||
Use `mr/def` for schemas used in multiple places:
|
||||
|
||||
```clojure
|
||||
(mr/def ::User
|
||||
[:map
|
||||
[:id pos-int?]
|
||||
[:email string?]
|
||||
[:name string?]])
|
||||
```
|
||||
|
||||
## Finding Return Types
|
||||
|
||||
1. **Look at the function being called**
|
||||
|
||||
```clojure
|
||||
(api.macros/defendpoint :get "/:id"
|
||||
[{:keys [id]}]
|
||||
(t2/select-one :model/Field :id id)) ;; Returns a Field instance
|
||||
```
|
||||
|
||||
2. **Check Toucan models for structure**
|
||||
|
||||
Look in `src/metabase/*/models/*.clj` for model definitions.
|
||||
|
||||
3. **Use clojure-mcp or REPL to inspect**
|
||||
|
||||
```bash
|
||||
./bin/mage -repl '(require '\''metabase.xrays.core) (doc metabase.xrays.core/related)'
|
||||
```
|
||||
|
||||
4. **Check tests**
|
||||
|
||||
Tests often show the expected response structure.
|
||||
|
||||
## Understanding Schema Validation Timing
|
||||
|
||||
**CRITICAL CONCEPT:** Schemas validate at different points in the request/response lifecycle:
|
||||
|
||||
### Request Parameter Schemas (Query/Body/Route)
|
||||
- Validate AFTER JSON parsing
|
||||
- Data is already deserialized (strings, numbers, booleans)
|
||||
- Use `ms/TemporalString` for date/time inputs
|
||||
- Use `ms/BooleanValue` for boolean query params
|
||||
|
||||
### Response Schemas
|
||||
- Validate BEFORE JSON serialization
|
||||
- Data is still in Clojure format (Java Time objects, sets, keywords)
|
||||
- Use `:any` for Java Time objects
|
||||
- Use `[:set X]` for sets
|
||||
- Use `[:enum :keyword]` for keyword enums
|
||||
|
||||
### Serialization Flow
|
||||
|
||||
```
|
||||
Request: JSON string → Parse → Coerce → Handler
|
||||
Response: Handler → Schema Check → Encode → Serialize → JSON string
|
||||
```
|
||||
|
||||
## Workflow Summary
|
||||
|
||||
1. **Read the endpoint** - understand what it does
|
||||
2. **Identify params** - route, query, body
|
||||
3. **Add parameter schemas** - use existing types from `ms`
|
||||
4. **Determine return type** - check the implementation
|
||||
5. **Define response schema** - inline or named with `mr/def`
|
||||
6. **Test** - ensure the endpoint works and validates correctly
|
||||
|
||||
## Testing Your Schemas
|
||||
|
||||
After adding schemas, verify:
|
||||
|
||||
1. **Valid requests work** - test with correct data
|
||||
2. **Invalid requests fail gracefully** - test with wrong types
|
||||
3. **Optional params work** - test with/without optional params
|
||||
4. **Error messages are clear** - check validation error responses
|
||||
|
||||
## Tips
|
||||
|
||||
- **Start simple** - begin with basic types, refine later
|
||||
- **Reuse schemas** - if you see the same structure twice, make it a named schema
|
||||
- **Be specific** - use `ms/PositiveInt` instead of `pos-int?`
|
||||
- **Document intent** - add docstrings to named schemas
|
||||
- **Follow conventions** - look at similar endpoints in the same namespace
|
||||
- **Check the actual data** - use REPL to inspect what's actually returned before serialization
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [Malli Documentation](https://github.com/metosin/malli)
|
||||
- Metabase Malli utilities: `src/metabase/util/malli/schema.clj`
|
||||
- Metabase schema registry: `src/metabase/util/malli/registry.clj`
|
||||
@@ -1,504 +0,0 @@
|
||||
---
|
||||
name: analytics-events
|
||||
description: Add product analytics events to track user interactions in the Metabase frontend
|
||||
allowed-tools: Read, Write, Edit, Grep, Glob
|
||||
---
|
||||
|
||||
# Frontend Analytics Events Skill
|
||||
|
||||
This skill helps you add product analytics (Snowplow) events to track user interactions in the Metabase frontend codebase.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
Analytics events in Metabase use Snowplow with typed event schemas. All events must be defined in TypeScript types before use.
|
||||
|
||||
**Key Files:**
|
||||
- `frontend/src/metabase-types/analytics/event.ts` - Event type definitions
|
||||
- `frontend/src/metabase-types/analytics/schema.ts` - Schema registry
|
||||
- `frontend/src/metabase/lib/analytics.ts` - Core tracking functions
|
||||
- Feature-specific `analytics.ts` files - Tracking function wrappers
|
||||
|
||||
## Quick Checklist
|
||||
|
||||
When adding a new analytics event:
|
||||
|
||||
- [ ] Define event type in `frontend/src/metabase-types/analytics/event.ts`
|
||||
- [ ] Add event to appropriate union type (e.g., `DataStudioEvent`, `SimpleEvent`)
|
||||
- [ ] Create tracking function in feature's `analytics.ts` file
|
||||
- [ ] Import and call tracking function at the interaction point
|
||||
- [ ] Use `trackSimpleEvent()` for basic events (most common)
|
||||
|
||||
## Event Schema Types
|
||||
|
||||
### 1. Simple Events (Most Common)
|
||||
|
||||
Use `SimpleEventSchema` for straightforward tracking. It supports these standard fields:
|
||||
|
||||
```typescript
|
||||
type SimpleEventSchema = {
|
||||
event: string; // Required: Event name (snake_case)
|
||||
target_id?: number | null; // Optional: ID of affected entity
|
||||
triggered_from?: string | null; // Optional: UI location/context
|
||||
duration_ms?: number | null; // Optional: Duration in milliseconds
|
||||
result?: string | null; // Optional: Outcome (e.g., "success", "failure")
|
||||
event_detail?: string | null; // Optional: Additional detail/variant
|
||||
};
|
||||
```
|
||||
|
||||
**When to use:** 90% of events fit this schema. Use for clicks, opens, closes, creates, deletes, etc.
|
||||
|
||||
### 2. Custom Schemas (legacy, no events are being added)
|
||||
|
||||
Consider adding new event schema only in very special cases.
|
||||
|
||||
**Examples:** `DashboardEventSchema`, `CleanupEventSchema`, `QuestionEventSchema`
|
||||
|
||||
## Step-by-Step: Adding a Simple Event
|
||||
|
||||
### Example: Track when a user applies filters in a table picker
|
||||
|
||||
#### Step 1: Define Event Types
|
||||
|
||||
Add event type definitions to `frontend/src/metabase-types/analytics/event.ts`:
|
||||
|
||||
```typescript
|
||||
export type DataStudioTablePickerFiltersAppliedEvent = ValidateEvent<{
|
||||
event: "data_studio_table_picker_filters_applied";
|
||||
}>;
|
||||
|
||||
export type DataStudioTablePickerFiltersClearedEvent = ValidateEvent<{
|
||||
event: "data_studio_table_picker_filters_cleared";
|
||||
}>;
|
||||
```
|
||||
|
||||
#### Step 2: Add to Union Type
|
||||
|
||||
Find or create the appropriate union type and add your events:
|
||||
|
||||
```typescript
|
||||
export type DataStudioEvent =
|
||||
| DataStudioLibraryCreatedEvent
|
||||
| DataStudioTablePublishedEvent
|
||||
| DataStudioGlossaryCreatedEvent
|
||||
| DataStudioGlossaryEditedEvent
|
||||
| DataStudioGlossaryDeletedEvent
|
||||
| DataStudioTablePickerFiltersAppliedEvent // <- Add here
|
||||
| DataStudioTablePickerFiltersClearedEvent; // <- Add here
|
||||
```
|
||||
|
||||
#### Step 3: Create Tracking Functions
|
||||
|
||||
In your feature's `analytics.ts` file (e.g., `enterprise/frontend/src/metabase-enterprise/data-studio/analytics.ts`):
|
||||
|
||||
```typescript
|
||||
import { trackSimpleEvent } from "metabase/lib/analytics";
|
||||
|
||||
export const trackDataStudioTablePickerFiltersApplied = () => {
|
||||
trackSimpleEvent({
|
||||
event: "data_studio_table_picker_filters_applied",
|
||||
});
|
||||
};
|
||||
|
||||
export const trackDataStudioTablePickerFiltersCleared = () => {
|
||||
trackSimpleEvent({
|
||||
event: "data_studio_table_picker_filters_cleared",
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
#### Step 4: Use in Components
|
||||
|
||||
Import and call the tracking function at the interaction point:
|
||||
|
||||
```typescript
|
||||
import {
|
||||
trackDataStudioTablePickerFiltersApplied,
|
||||
trackDataStudioTablePickerFiltersCleared,
|
||||
} from "metabase-enterprise/data-studio/analytics";
|
||||
|
||||
function FilterPopover({ filters, onSubmit }) {
|
||||
const handleReset = () => {
|
||||
trackDataStudioTablePickerFiltersCleared(); // <- Track here
|
||||
onSubmit(emptyFilters);
|
||||
};
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
trackDataStudioTablePickerFiltersApplied(); // <- Track here
|
||||
onSubmit(form);
|
||||
}}
|
||||
>
|
||||
{/* form content */}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Using SimpleEventSchema Fields
|
||||
|
||||
### Example: Event with target_id
|
||||
|
||||
```typescript
|
||||
// Type definition
|
||||
export type DataStudioLibraryCreatedEvent = ValidateEvent<{
|
||||
event: "data_studio_library_created";
|
||||
target_id: number | null;
|
||||
}>;
|
||||
|
||||
// Tracking function
|
||||
export const trackDataStudioLibraryCreated = (id: CollectionId) => {
|
||||
trackSimpleEvent({
|
||||
event: "data_studio_library_created",
|
||||
target_id: Number(id),
|
||||
});
|
||||
};
|
||||
|
||||
// Usage
|
||||
trackDataStudioLibraryCreated(newLibrary.id);
|
||||
```
|
||||
|
||||
### Example: Event with triggered_from
|
||||
|
||||
```typescript
|
||||
// Type definition
|
||||
export type NewButtonClickedEvent = ValidateEvent<{
|
||||
event: "new_button_clicked";
|
||||
triggered_from: "app-bar" | "empty-collection";
|
||||
}>;
|
||||
|
||||
// Tracking function
|
||||
export const trackNewButtonClicked = (location: "app-bar" | "empty-collection") => {
|
||||
trackSimpleEvent({
|
||||
event: "new_button_clicked",
|
||||
triggered_from: location,
|
||||
});
|
||||
};
|
||||
|
||||
// Usage
|
||||
<Button onClick={() => {
|
||||
trackNewButtonClicked("app-bar");
|
||||
handleCreate();
|
||||
}}>
|
||||
New
|
||||
</Button>
|
||||
```
|
||||
|
||||
### Example: Event with event_detail
|
||||
|
||||
```typescript
|
||||
// Type definition
|
||||
export type MetadataEditEvent = ValidateEvent<{
|
||||
event: "metadata_edited";
|
||||
event_detail: "type_casting" | "semantic_type_change" | "visibility_change";
|
||||
triggered_from: "admin" | "data_studio";
|
||||
}>;
|
||||
|
||||
// Tracking function
|
||||
export const trackMetadataChange = (
|
||||
detail: "type_casting" | "semantic_type_change" | "visibility_change",
|
||||
location: "admin" | "data_studio"
|
||||
) => {
|
||||
trackSimpleEvent({
|
||||
event: "metadata_edited",
|
||||
event_detail: detail,
|
||||
triggered_from: location,
|
||||
});
|
||||
};
|
||||
|
||||
// Usage
|
||||
trackMetadataChange("semantic_type_change", "data_studio");
|
||||
```
|
||||
|
||||
### Example: Event with result and duration
|
||||
|
||||
```typescript
|
||||
// Type definition
|
||||
export type MoveToTrashEvent = ValidateEvent<{
|
||||
event: "moved-to-trash";
|
||||
target_id: number | null;
|
||||
triggered_from: "collection" | "detail_page" | "cleanup_modal";
|
||||
duration_ms: number | null;
|
||||
result: "success" | "failure";
|
||||
event_detail: "question" | "model" | "metric" | "dashboard";
|
||||
}>;
|
||||
|
||||
// Tracking function
|
||||
export const trackMoveToTrash = (params: {
|
||||
targetId: number | null;
|
||||
triggeredFrom: "collection" | "detail_page" | "cleanup_modal";
|
||||
durationMs: number | null;
|
||||
result: "success" | "failure";
|
||||
itemType: "question" | "model" | "metric" | "dashboard";
|
||||
}) => {
|
||||
trackSimpleEvent({
|
||||
event: "moved-to-trash",
|
||||
target_id: params.targetId,
|
||||
triggered_from: params.triggeredFrom,
|
||||
duration_ms: params.durationMs,
|
||||
result: params.result,
|
||||
event_detail: params.itemType,
|
||||
});
|
||||
};
|
||||
|
||||
// Usage with timing
|
||||
const startTime = Date.now();
|
||||
try {
|
||||
await moveToTrash(item);
|
||||
trackMoveToTrash({
|
||||
targetId: item.id,
|
||||
triggeredFrom: "collection",
|
||||
durationMs: Date.now() - startTime,
|
||||
result: "success",
|
||||
itemType: "question",
|
||||
});
|
||||
} catch (error) {
|
||||
trackMoveToTrash({
|
||||
targetId: item.id,
|
||||
triggeredFrom: "collection",
|
||||
durationMs: Date.now() - startTime,
|
||||
result: "failure",
|
||||
itemType: "question",
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
### Event Names (snake_case)
|
||||
|
||||
```typescript
|
||||
// Good
|
||||
"data_studio_library_created"
|
||||
"table_picker_filters_applied"
|
||||
"metabot_chat_opened"
|
||||
|
||||
// Bad
|
||||
"DataStudioLibraryCreated" // Wrong case
|
||||
"tablePickerFiltersApplied" // Wrong case
|
||||
"filters-applied" // Use underscore, not hyphen
|
||||
```
|
||||
|
||||
### Event Type Names (PascalCase with "Event" suffix)
|
||||
|
||||
```typescript
|
||||
// Good
|
||||
DataStudioLibraryCreatedEvent
|
||||
TablePickerFiltersAppliedEvent
|
||||
MetabotChatOpenedEvent
|
||||
|
||||
// Bad
|
||||
dataStudioLibraryCreated // Wrong case
|
||||
DataStudioLibraryCreated // Missing "Event" suffix
|
||||
```
|
||||
|
||||
### Tracking Function Names (camelCase with "track" prefix)
|
||||
|
||||
```typescript
|
||||
// Good
|
||||
trackDataStudioLibraryCreated
|
||||
trackTablePickerFiltersApplied
|
||||
trackMetabotChatOpened
|
||||
|
||||
// Bad
|
||||
DataStudioLibraryCreated // Missing "track" prefix
|
||||
track_library_created // Wrong case
|
||||
logLibraryCreated // Use "track" prefix
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Pattern 1: Feature-Specific Union Types
|
||||
|
||||
Group related events together:
|
||||
|
||||
```typescript
|
||||
export type DataStudioEvent =
|
||||
| DataStudioLibraryCreatedEvent
|
||||
| DataStudioTablePublishedEvent
|
||||
| DataStudioGlossaryCreatedEvent;
|
||||
|
||||
export type MetabotEvent =
|
||||
| MetabotChatOpenedEvent
|
||||
| MetabotRequestSentEvent
|
||||
| MetabotFixQueryClickedEvent;
|
||||
|
||||
// Then add to SimpleEvent union
|
||||
export type SimpleEvent =
|
||||
| /* other events */
|
||||
| DataStudioEvent
|
||||
| MetabotEvent
|
||||
| /* more events */;
|
||||
```
|
||||
|
||||
### Pattern 2: Conditional Tracking
|
||||
|
||||
Track different events based on user action:
|
||||
|
||||
```typescript
|
||||
const handleSave = async () => {
|
||||
if (isNewItem) {
|
||||
await createItem(data);
|
||||
trackItemCreated(newItem.id);
|
||||
} else {
|
||||
await updateItem(id, data);
|
||||
trackItemUpdated(id);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
### Don't: Add custom fields to SimpleEvent
|
||||
|
||||
```typescript
|
||||
// WRONG - SimpleEvent doesn't support custom fields
|
||||
export const trackFiltersApplied = (filters: FilterState) => {
|
||||
trackSimpleEvent({
|
||||
event: "filters_applied",
|
||||
data_layer: filters.dataLayer, // ❌ Not in SimpleEventSchema
|
||||
data_source: filters.dataSource, // ❌ Not in SimpleEventSchema
|
||||
with_owner: filters.hasOwner, // ❌ Not in SimpleEventSchema
|
||||
});
|
||||
};
|
||||
|
||||
// RIGHT - Use only standard SimpleEventSchema fields
|
||||
export const trackFiltersApplied = () => {
|
||||
trackSimpleEvent({
|
||||
event: "filters_applied",
|
||||
});
|
||||
};
|
||||
|
||||
// Or use event_detail for a single variant
|
||||
export const trackFilterApplied = (filterType: string) => {
|
||||
trackSimpleEvent({
|
||||
event: "filter_applied",
|
||||
event_detail: filterType, // ✓ "data_layer", "data_source", etc.
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
### Don't: Forget to add event to union type
|
||||
|
||||
```typescript
|
||||
// Define the event
|
||||
export type NewFeatureClickedEvent = ValidateEvent<{
|
||||
event: "new_feature_clicked";
|
||||
}>;
|
||||
|
||||
// ❌ WRONG - Forgot to add to SimpleEvent union
|
||||
// Event won't be recognized by TypeScript
|
||||
|
||||
// ✓ RIGHT - Add to appropriate union
|
||||
export type SimpleEvent =
|
||||
| /* other events */
|
||||
| NewFeatureClickedEvent;
|
||||
```
|
||||
|
||||
### Don't: Mix up event name formats
|
||||
|
||||
```typescript
|
||||
// WRONG
|
||||
event: "dataStudioLibraryCreated" // camelCase
|
||||
event: "data-studio-library-created" // kebab-case
|
||||
event: "Data_Studio_Library_Created" // Mixed case
|
||||
|
||||
// RIGHT
|
||||
event: "data_studio_library_created" // snake_case
|
||||
```
|
||||
|
||||
### Don't: Track PII or sensitive data
|
||||
|
||||
```typescript
|
||||
// WRONG - Don't track user emails, names, or sensitive data
|
||||
trackSimpleEvent({
|
||||
event: "user_logged_in",
|
||||
event_detail: user.email, // ❌ PII
|
||||
});
|
||||
|
||||
// RIGHT - Track non-sensitive identifiers only
|
||||
trackSimpleEvent({
|
||||
event: "user_logged_in",
|
||||
target_id: user.id, // ✓ Just the ID
|
||||
});
|
||||
```
|
||||
|
||||
### Don't: Forget to track both success and failure
|
||||
|
||||
```typescript
|
||||
// WRONG - Only tracking success
|
||||
try {
|
||||
await saveData();
|
||||
trackDataSaved();
|
||||
} catch (error) {
|
||||
// ❌ No tracking for failure case
|
||||
}
|
||||
|
||||
// RIGHT - Track both outcomes
|
||||
try {
|
||||
await saveData();
|
||||
trackDataSaved({ result: "success" });
|
||||
} catch (error) {
|
||||
trackDataSaved({ result: "failure" });
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Analytics Events
|
||||
|
||||
While developing, you can verify events are firing:
|
||||
|
||||
1. **Check browser console** - When `SNOWPLOW_ENABLED=true` in dev, events are logged
|
||||
2. **Use shouldLogAnalytics** - Set in `metabase/env` to see all analytics in console
|
||||
3. **Check Snowplow debugger** - Browser extension for Snowplow events
|
||||
|
||||
Example console output:
|
||||
|
||||
```
|
||||
[SNOWPLOW EVENT | event sent:true], data_studio_table_picker_filters_applied
|
||||
```
|
||||
|
||||
## File Organization
|
||||
|
||||
### Where to put tracking functions:
|
||||
|
||||
```
|
||||
Feature-specific analytics functions:
|
||||
frontend/src/metabase/{feature}/analytics.ts
|
||||
enterprise/frontend/src/metabase-enterprise/{feature}/analytics.ts
|
||||
|
||||
Event type definitions (all in one place):
|
||||
frontend/src/metabase-types/analytics/event.ts
|
||||
|
||||
Core tracking utilities:
|
||||
frontend/src/metabase/lib/analytics.ts
|
||||
```
|
||||
|
||||
## Real-World Examples
|
||||
|
||||
See these files for reference:
|
||||
|
||||
- **Simple events**: `enterprise/frontend/src/metabase-enterprise/data-studio/analytics.ts`
|
||||
- **Events with variants**: `frontend/src/metabase/dashboard/analytics.ts`
|
||||
- **Complex events**: `frontend/src/metabase/query_builder/analytics.js`
|
||||
- **Event type examples**: `frontend/src/metabase-types/analytics/event.ts`
|
||||
|
||||
## Workflow Summary
|
||||
|
||||
1. **Identify the user interaction** to track
|
||||
2. **Decide on event name** (snake_case, descriptive)
|
||||
3. **Define event type** in `event.ts` using `ValidateEvent`
|
||||
4. **Add to union type** (create feature union if needed)
|
||||
5. **Create tracking function** in feature's `analytics.ts`
|
||||
6. **Import and call** at the interaction point
|
||||
7. **Test** that events fire correctly
|
||||
|
||||
## Tips
|
||||
|
||||
- **Be specific** - `filters_applied` is better than `action_performed`
|
||||
- **Use past tense** - `library_created` not `create_library`
|
||||
- **Group related events** - Create feature-specific event union types
|
||||
- **Track meaningful actions** - Not every click needs tracking
|
||||
- **Consider the data** - What would you want to analyze later?
|
||||
- **Stay consistent** - Follow existing naming patterns in the codebase
|
||||
- **Document context** - Use `triggered_from` to track where the action happened
|
||||
@@ -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 2025-01-01) -- description`
|
||||
|
||||
### Code Organization
|
||||
|
||||
- [ ] Everything `^:private` unless used elsewhere
|
||||
- [ ] No `declare` when avoidable (public functions near end)
|
||||
- [ ] Functions under 20 lines when possible
|
||||
- [ ] No blank, non-comment 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 2025-01-01) -- 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`.
|
||||
@@ -1,69 +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
|
||||
|
||||
## Tool Preference
|
||||
|
||||
When `clojure-mcp` tools are available (e.g., `clojure_eval`, `clojure_edit`), **always use them**
|
||||
instead of shell commands like `./bin/mage -repl`. The MCP tools provide:
|
||||
- Direct REPL integration without shell escaping issues
|
||||
- Better error messages and feedback
|
||||
- Structural Clojure editing that prevents syntax errors
|
||||
|
||||
Only fall back to `./bin/mage` commands when clojure-mcp is not available.
|
||||
|
||||
@./../_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
|
||||
@@ -1,163 +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:**
|
||||
|
||||
- [ ] 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 |
|
||||
| ----------------------------- | --------------------------------------------- |
|
||||
| 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: Formal tone**
|
||||
> Line 15: This could be more conversational. Consider: "You can't..." instead of "You cannot..."
|
||||
|
||||
> **Issue 2: 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: Formal tone**
|
||||
|
||||
This could be more conversational. Consider: "You can't..." instead of "You cannot..."
|
||||
```
|
||||
|
||||
**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).
|
||||
@@ -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: `bun run 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) |
|
||||
@@ -1,114 +0,0 @@
|
||||
---
|
||||
name: mutation-testing
|
||||
description: Run mutation testing on a Clojure namespace, generate tests to kill surviving mutations, and open draft PRs with Linear issue tracking.
|
||||
---
|
||||
|
||||
# Mutation Testing Skill
|
||||
|
||||
This skill runs mutation testing end-to-end: generates a coverage report, groups functions, shells out to `claude -p` to write tests, verifies mutations are killed, and creates draft PRs with Linear issues.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A running nREPL connected to the Metabase dev environment
|
||||
- `LINEAR_API_KEY` environment variable set with a valid Linear personal API key
|
||||
- `gh` CLI authenticated with GitHub
|
||||
- `claude` CLI available on PATH
|
||||
|
||||
## Reference Files
|
||||
|
||||
- `dev/src/dev/coverage.clj` — mutation testing engine (generates reports, runs mutations)
|
||||
- `dev/src/dev/mutation_testing.clj` — orchestration, Linear API, PR helpers
|
||||
|
||||
## Invocation
|
||||
|
||||
The argument is a Clojure namespace, optionally followed by a base branch:
|
||||
|
||||
```
|
||||
/mutation-testing metabase.lib.order-by
|
||||
/mutation-testing metabase.lib.order-by --base-branch release-x.52.x
|
||||
/mutation-testing metabase.lib.order-by --project-id abc-123
|
||||
/mutation-testing metabase.lib.order-by --base-branch release-x.52.x --project-id abc-123
|
||||
```
|
||||
|
||||
- `--base-branch`: defaults to the value in config (set via `set-config!`), or `"master"` if not configured.
|
||||
- `--project-id`: if provided, issues are added to this existing Linear project instead of creating a new one.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Parse arguments
|
||||
|
||||
Parse `$ARGUMENTS` to extract:
|
||||
- **namespace** (required) — the first positional argument
|
||||
- **`--base-branch`** (optional) — if present, the next argument is the base branch name
|
||||
- **`--project-id`** (optional) — if present, the next argument is an existing Linear project ID
|
||||
|
||||
### 2. Load and configure
|
||||
|
||||
```clojure
|
||||
(require '[dev.mutation-testing :as mut-test] :reload)
|
||||
(require '[dev.coverage :as cov] :reload)
|
||||
```
|
||||
|
||||
If this is the first invocation (or the REPL was restarted), set up the Linear team:
|
||||
|
||||
```clojure
|
||||
(mut-test/list-teams!)
|
||||
(mut-test/set-config! {:team-id "<id>"})
|
||||
```
|
||||
|
||||
### 3. Run
|
||||
|
||||
```clojure
|
||||
(mut-test/run! '<target-ns>)
|
||||
;; Or with options:
|
||||
(mut-test/run! '<target-ns> {:base-branch "release-x.52.x"})
|
||||
(mut-test/run! '<target-ns> {:project-id "abc-123"})
|
||||
(mut-test/run! '<target-ns> {:base-branch "release-x.52.x" :project-id "abc-123"})
|
||||
```
|
||||
|
||||
Pass the opts map with `:base-branch` and/or `:project-id` if those flags were provided in the arguments.
|
||||
|
||||
This will:
|
||||
1. Generate a baseline mutation testing report
|
||||
2. Create a Linear project for the namespace
|
||||
3. Group functions by coverage relationships
|
||||
4. For each group: create branch → invoke Claude to write tests → verify → retry if needed → commit & push → create Linear issue → create draft PR
|
||||
5. Print a summary with PR links
|
||||
|
||||
### 4. Handle failures
|
||||
|
||||
If a group fails mid-processing, `run!` catches the error and continues to the next group. To retry a single group manually:
|
||||
|
||||
```clojure
|
||||
;; Get the parsed namespace info
|
||||
(def parsed (mut-test/parse-namespace '<target-ns>))
|
||||
|
||||
;; Run coverage to get the data
|
||||
(def coverage-results (cov/test-namespace (:target-ns parsed) [(:test-ns parsed)]))
|
||||
|
||||
;; Group and find the one you want
|
||||
(def groups (mut-test/group-functions coverage-results))
|
||||
|
||||
;; Process just that group
|
||||
(mut-test/process-group! parsed (nth groups <index>))
|
||||
```
|
||||
|
||||
### 5. Verify the prompt (dry run)
|
||||
|
||||
To inspect what Claude will see without invoking it:
|
||||
|
||||
```clojure
|
||||
(def parsed (mut-test/parse-namespace '<target-ns>))
|
||||
(def coverage-results (cov/test-namespace (:target-ns parsed) [(:test-ns parsed)]))
|
||||
(def groups (mut-test/group-functions coverage-results))
|
||||
(println (mut-test/build-test-prompt
|
||||
(merge (select-keys parsed [:target-ns :test-ns :source-path :test-path])
|
||||
(select-keys (first groups) [:fn-names :mutations]))))
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
- **Team ID**: Set once per REPL session via `(mut-test/list-teams!)` and `(mut-test/set-config! {:team-id "..."})`
|
||||
- **Project ID**: Set automatically when `run!` calls `create-project-for-namespace!`
|
||||
- **Base Branch**: Defaults to `"master"`. Override via `(mut-test/set-config! {:base-branch "release-x.52.x"})` or pass `{:base-branch "..."}` as the second arg to `run!`
|
||||
- **Project ID**: If set (via `set-config!` or `run!` opts), `run!` reuses the existing Linear project instead of creating a new one
|
||||
@@ -1,20 +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:
|
||||
|
||||
- Readability and maintainability
|
||||
- Appropriate test coverage
|
||||
- Compliance with project coding standards and conventions
|
||||
- Code quality and best practices
|
||||
- Type safety and proper TypeScript usage
|
||||
- React best practices (when applicable)
|
||||
@@ -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
|
||||
@@ -1,7 +1,7 @@
|
||||
;; -*- comment-column: 100; -*-
|
||||
{:config-paths ["macros" "src" "config"]
|
||||
:linters
|
||||
{;;
|
||||
{ ;;
|
||||
;; enabled optional linters (these are disabled by default)
|
||||
;;
|
||||
|
||||
@@ -50,15 +50,13 @@
|
||||
;; disabled linters
|
||||
;;
|
||||
|
||||
:redundant-ignore {:level :off} ; lots of false positives, see https://github.com/clj-kondo/clj-kondo/issues/2414
|
||||
:redundant-ignore {:level :off} ; lots of false positives, see https://github.com/clj-kondo/clj-kondo/issues/2414
|
||||
:unexpected-recur {:level :off} ; TODO (cam): I think we just need to tell it how to handle MBQL match and we can enable this?
|
||||
|
||||
;;
|
||||
;; globally-enabled custom linters
|
||||
;;
|
||||
:metabase/check-defmulti-arglists {:level :warning}
|
||||
:metabase/check-def-check-not-uppercase-name {:level :warning}
|
||||
:metabase/check-def-no-underscores {:level :warning}
|
||||
:metabase/check-me-humanize {:level :warning}
|
||||
:metabase/defsetting-must-specify-export {:level :warning}
|
||||
:metabase/defsetting-namespace {:level :warning}
|
||||
@@ -67,9 +65,8 @@
|
||||
: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/no-jsqlparser-imports {:level :warning}
|
||||
:metabase/require-shape-checker {:level :warning}
|
||||
:metabase/tests-must-live-in-known-modules {: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}
|
||||
@@ -180,11 +177,6 @@
|
||||
metabase.notification.models/create-notification!
|
||||
metabase.pulse.send/send-pulse!
|
||||
metabase-enterprise.transforms-python.python-runner-test/execute!
|
||||
;; Workspace isolation functions are safe in parallel tests because they operate on
|
||||
;; isolated, test-specific workspaces and databases that don't affect other tests
|
||||
metabase-enterprise.workspaces.isolation/destroy-workspace-isolation!
|
||||
metabase-enterprise.workspaces.isolation/ensure-database-isolation!
|
||||
metabase-enterprise.workspaces.test-util/create-ready-ws!
|
||||
;; Safe in tests, where we typically use a private, temporary index per thread.
|
||||
metabase.search.core/reindex!
|
||||
metabase.util/run-count!
|
||||
@@ -198,7 +190,7 @@
|
||||
:metabase/disallow-hardcoded-driver-names-in-tests
|
||||
;; don't include `:h2` since that will probably lead to a mountain of tests where we are specifically testing
|
||||
;; middleware or compilation behavior with just H2 since it's the default.
|
||||
{:drivers #{;; core drivers
|
||||
{:drivers #{ ;; core drivers
|
||||
#_:h2
|
||||
:postgres
|
||||
:mysql
|
||||
@@ -226,10 +218,7 @@
|
||||
;; misc config for built-in linters
|
||||
;;
|
||||
|
||||
:invalid-arity {:skip-args [metabase.lib.util.match/match
|
||||
metabase.lib.util.match/match-lite
|
||||
metabase.lib.util.match/match-many
|
||||
metabase.lib.util.match/replace-lite]} ; TODO (cam): can we fix these?
|
||||
:invalid-arity {:skip-args [metabase.lib.util.match/match]} ; TODO (cam): can we fix these?
|
||||
:refer-all {:level :warning, :exclude [clojure.test]}
|
||||
:unused-referred-var {:exclude {compojure.core [GET DELETE POST PUT]}}
|
||||
|
||||
@@ -240,6 +229,9 @@
|
||||
{:exclude
|
||||
[(build.uberjar/with-duration-ms)
|
||||
(cljs.test/is [=? malli=])
|
||||
(clojure.core.logic/fresh)
|
||||
(clojure.core.logic/matcha)
|
||||
(clojure.core.logic/run)
|
||||
(clojure.test/is [=? malli= qv=])
|
||||
(clojure.tools.macro/macrolet)
|
||||
instaparse.core/transform
|
||||
@@ -248,17 +240,13 @@
|
||||
(metabase.lib.util.match/match)
|
||||
(metabase.lib.util.match/match-one)
|
||||
(metabase.lib.util.match/match-lite)
|
||||
(metabase.lib.util.match/match-many)
|
||||
(metabase.lib.util.match/match-lite-recursive)
|
||||
(metabase.lib.util.match/replace)
|
||||
(metabase.lib.util.match/replace-in)
|
||||
(metabase.lib.util.match/replace-lite)
|
||||
(metabase.driver-api.core/match)
|
||||
(metabase.driver-api.core/match-one)
|
||||
(metabase.driver-api.core/match-lite)
|
||||
(metabase.driver-api.core/match-many)
|
||||
(metabase.driver-api.core/replace)
|
||||
(metabase.driver-api.core/replace-in)
|
||||
(metabase.driver-api.core/replace-lite)
|
||||
(metabase.query-processor.middleware.cache-backend.interface/with-cached-results)
|
||||
(metabase.util.regex/rx [opt])
|
||||
(metabase.util/prog1 [<>])
|
||||
@@ -310,14 +298,9 @@
|
||||
metabase.lib.convert/->legacy-MBQL {:message "Conversion to legacy MBQL is discouraged, please use Lib to manipulate MBQL queries"}
|
||||
metabase.lib.core/->legacy-MBQL {:message "Conversion to legacy MBQL is discouraged, please use Lib to manipulate MBQL queries"}
|
||||
metabase.lib.equality/find-column-indexes-for-refs {:message "Use lib.equality/closest-matches-in-metadata or lib.equality/find-closest-matches-for-refs instead of lib.equality/find-column-indexes-for-refs"}
|
||||
metabase.lib.util.match/match {:message "Use lib.util.match/match-many instead of lib.util.match/match."}
|
||||
metabase.lib.util.match/match-one {:message "Use lib.util.match/match-lite instead of lib.util.match/match-one."}
|
||||
metabase.lib.util.match/replace {:message "Use lib.util.match/replace-lite instead of lib.util.match/replace."}
|
||||
metabase-enterprise.transforms.interface/execute! {:message "Use metabase-enterprise.transforms.execute/execute! instead of metabase-enterprise.transforms.interface/execute!"}
|
||||
next.jdbc/active-tx? {:message "Use metabase.app-db.core/in-transaction? to see if we are in a transaction"}
|
||||
toucan2.core/debug {:message "Don't commit code using t2/debug"}
|
||||
toucan2.tools.debug/debug {:message "Don't commit code using t2/debug"}
|
||||
}
|
||||
toucan2.tools.debug/debug {:message "Don't commit code using t2/debug"}}
|
||||
|
||||
:discouraged-namespace
|
||||
{camel-snake-kebab.core {:message "CSK is not Turkish-safe, use the versions in metabase.util instead."}
|
||||
@@ -427,6 +410,7 @@
|
||||
:lint-as
|
||||
{cljs.cache/defcache clojure.core/deftype
|
||||
clojure.core.cache/defcache clojure.core/deftype
|
||||
clojure.core.logic/defne clj-kondo.lint-as/def-catch-all
|
||||
clojure.test.check.clojure-test/defspec clojure.test/deftest
|
||||
clojure.test.check.properties/for-all clojure.core/let
|
||||
clojurewerkz.quartzite.jobs/defjob clojure.core/defn
|
||||
@@ -436,10 +420,11 @@
|
||||
metabase-enterprise.content-translation.routes-test/with-new-secret-key! clojure.core/fn
|
||||
metabase-enterprise.serialization.test-util/with-dbs clojure.core/fn
|
||||
metabase-enterprise.serialization.test-util/with-random-dump-dir clojure.core/let
|
||||
metabase-enterprise.workspaces.test-util/with-resources! clojure.core/let
|
||||
metabase-enterprise.workspaces.test-util/with-workspaces! clojure.core/let
|
||||
metabase-enterprise.transforms.api-test/with-transform-tags clojure.core/let
|
||||
metabase-enterprise.transforms.api-test/with-worker-runs clojure.core/let
|
||||
metabase-enterprise.transforms.test-util/with-transform-cleanup! clojure.core/let
|
||||
metabase-enterprise.transforms.execute/with-transform-lifecycle clojure.core/let
|
||||
metabase.actions.test-util/with-actions clojure.core/let
|
||||
metabase.transforms.test-util/with-transform-cleanup! clojure.core/let
|
||||
metabase.api.common/let-404 clojure.core/let
|
||||
metabase.app-db.custom-migrations.util/with-temp-scheduler! clojure.core/fn
|
||||
metabase.app-db.custom-migrations/define-migration clj-kondo.lint-as/def-catch-all
|
||||
@@ -467,8 +452,8 @@
|
||||
metabase.premium-features.settings/define-premium-feature clojure.core/def
|
||||
metabase.pulse.dashboard-subscription-test/with-dashboard-sub-for-card clojure.core/let
|
||||
metabase.pulse.send-test/with-pulse-for-card clojure.core/let
|
||||
metabase.query-processor.pipeline-queries-test/pmbql-query clojure.core/->
|
||||
metabase.query-processor.pipeline-queries-test/run-pmbql-query clojure.core/->
|
||||
metabase.query-processor-test.pipeline-queries-test/pmbql-query clojure.core/->
|
||||
metabase.query-processor-test.pipeline-queries-test/run-pmbql-query clojure.core/->
|
||||
metabase.query-processor.error-type/deferror clojure.core/def
|
||||
metabase.query-processor.middleware.cache.impl/with-reducible-deserialized-results clojure.core/let
|
||||
metabase.query-processor.middleware.process-userland-query-test/with-query-execution! clojure.core/let
|
||||
@@ -527,16 +512,7 @@
|
||||
{cljs.test/deftest hooks.clojure.test/deftest
|
||||
cljs.test/is hooks.clojure.test/is
|
||||
cljs.test/use-fixtures hooks.clojure.test/use-fixtures
|
||||
clojure.core/def hooks.clojure.core.def/lint-def
|
||||
clojure.core/definterface hooks.clojure.core.def/lint-def
|
||||
clojure.core/defmacro hooks.clojure.core.defmacro/lint-defmacro
|
||||
clojure.core/defmulti hooks.clojure.core.defmulti/lint-defmulti
|
||||
clojure.core/defn hooks.clojure.core.defn/lint-defn
|
||||
clojure.core/defn- hooks.clojure.core.defn/lint-defn
|
||||
clojure.core/defonce hooks.clojure.core.def/lint-def
|
||||
clojure.core/defprotocol hooks.clojure.core.def/lint-def
|
||||
clojure.core/defrecord hooks.clojure.core.def/lint-def
|
||||
clojure.core/deftype hooks.clojure.core.def/lint-def
|
||||
clojure.core/ns hooks.clojure.core.ns/lint-ns
|
||||
clojure.core/require hooks.clojure.core.require/lint-require
|
||||
clojure.core/requiring-resolve hooks.clojure.core.require/lint-requiring-resolve
|
||||
@@ -548,10 +524,8 @@
|
||||
metabase-enterprise.advanced-permissions.models.permissions.application-permissions-test/with-new-group-and-current-graph hooks.common/with-two-top-level-bindings
|
||||
metabase-enterprise.audit-app.pages-test/with-temp-objects hooks.common/with-one-binding
|
||||
metabase-enterprise.cache.config-test/with-temp-persist-models hooks.common/with-seven-bindings
|
||||
metabase-enterprise.dependencies.api-test/with-dependents-test! hooks.common/with-two-bindings
|
||||
metabase-enterprise.metabot-v3.tools.dependencies-test/with-dependent-transforms! hooks.common/with-two-bindings
|
||||
metabase-enterprise.semantic-search.db.connection/with-migrate-tx hooks.common/let-one-with-optional-value
|
||||
metabase-enterprise.serialization.api-test/with-serialization-test-data! hooks.common/with-three-bindings
|
||||
metabase-enterprise.serialization.test-util/with-temp-dpc hooks.toucan2.tools.with-temp/with-temp
|
||||
metabase-enterprise.transforms-python.drivers-test/with-test-table hooks.common/with-two-bindings
|
||||
metabase.api.common.internal-test/with-jetty-server hooks.common/with-one-binding
|
||||
@@ -569,7 +543,7 @@
|
||||
metabase.dashboards-rest.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.expressions-test/calculate-bird-scarcity
|
||||
metabase.driver.bigquery-cloud-sdk-test/calculate-bird-scarcity hooks.metabase.query-processor-test.expressions-test/calculate-bird-scarcity
|
||||
metabase.driver.mongo-test/with-describe-table-for-sample hooks.metabase.driver.mongo-test/with-describe-table-for-sample
|
||||
metabase.driver.oracle-test/with-temp-user hooks.common/let-one-with-optional-value
|
||||
metabase.driver/register! hooks.metabase.driver/register!
|
||||
@@ -606,11 +580,11 @@
|
||||
metabase.pulse.models.pulse-test/with-dashboard-subscription-in-collection! hooks.common/with-four-bindings
|
||||
metabase.pulse.models.pulse-test/with-pulse-in-collection! hooks.common/with-four-bindings
|
||||
metabase.pulse.test-util/checkins-query-card hooks.metabase.test.data/$ids
|
||||
metabase.queries-rest.api.card-test/with-card-param-values-fixtures hooks.common/let-one-with-optional-value
|
||||
metabase.queries-rest.api.card-test/with-temp-native-card hooks.common/with-two-bindings
|
||||
metabase.queries-rest.api.card-test/with-temp-native-card-with-params hooks.common/with-two-bindings
|
||||
metabase.query-processor.expressions-test/calculate-bird-scarcity hooks.metabase.query-processor.expressions-test/calculate-bird-scarcity
|
||||
metabase.query-processor.filter-test/count-with-filter-clause hooks.metabase.test.data/$ids
|
||||
metabase.queries.api.card-test/with-card-param-values-fixtures hooks.common/let-one-with-optional-value
|
||||
metabase.queries.api.card-test/with-temp-native-card hooks.common/with-two-bindings
|
||||
metabase.queries.api.card-test/with-temp-native-card-with-params hooks.common/with-two-bindings
|
||||
metabase.query-processor-test.expressions-test/calculate-bird-scarcity hooks.metabase.query-processor-test.expressions-test/calculate-bird-scarcity
|
||||
metabase.query-processor-test.filter-test/count-with-filter-clause hooks.metabase.test.data/$ids
|
||||
metabase.query-processor.middleware.cache-test/with-mock-cache! hooks.common/with-two-bindings
|
||||
metabase.request.core/with-current-request hooks.common/with-used-first-arg
|
||||
metabase.request.core/with-current-user hooks.common/with-used-first-arg
|
||||
@@ -709,7 +683,7 @@
|
||||
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-rest.api.card-test/with-ordered-items macros.metabase.queries-rest.api.card-test/with-ordered-items
|
||||
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
|
||||
@@ -778,32 +752,33 @@
|
||||
;; `metabase-enterprise.sandbox.query-processor*-test`
|
||||
;;
|
||||
{:pattern "^metabase(?:((?:-enterprise\\.sandbox)?\\.query-processor)|(?:\\.driver\\.sql(?!server)(?!ite))).*-test$"
|
||||
:name general-qp-and-driver-test-namespaces}
|
||||
|
||||
{:pattern "(?:^mage.*)"
|
||||
:name mage-namespaces}]
|
||||
:name general-qp-and-driver-test-namespaces}]
|
||||
|
||||
:config-in-ns
|
||||
{test-namespaces
|
||||
{:linters
|
||||
{:inline-def {:level :off}
|
||||
:missing-docstring {:level :off}
|
||||
:private-call {:level :off}
|
||||
:metabase/defsetting-must-specify-export {:level :off}
|
||||
:metabase/defsetting-namespace {:level :off}
|
||||
:metabase/test-helpers-use-non-thread-safe-functions {:level :warning} ; more config in `.clj-kondo/config/test-helpers/config.edn
|
||||
{:inline-def {:level :off}
|
||||
:missing-docstring {:level :off}
|
||||
:private-call {:level :off}
|
||||
:metabase/defsetting-must-specify-export {:level :off}
|
||||
:metabase/defsetting-namespace {:level :off}
|
||||
|
||||
:discouraged-var
|
||||
{metabase.driver/database-supports? {:level :off}
|
||||
metabase.lib.convert/->legacy-MBQL {:level :off}
|
||||
metabase.lib.core/->legacy-MBQL {:level :off}}}}
|
||||
metabase.lib.core/->legacy-MBQL {:level :off}}}
|
||||
|
||||
:hooks
|
||||
{:analyze-call
|
||||
{clojure.core/defmacro hooks.clojure.core.defn/non-thread-safe-form-should-end-with-exclamation
|
||||
clojure.core/defn hooks.clojure.core.defn/non-thread-safe-form-should-end-with-exclamation
|
||||
clojure.core/defn- hooks.clojure.core.defn/non-thread-safe-form-should-end-with-exclamation}}}
|
||||
|
||||
source-namespaces
|
||||
{:linters
|
||||
{:metabase/discourage-millis-duration {:level :warning}
|
||||
:metabase/validate-defendpoint-has-response-schema {:level :warning}
|
||||
:metabase/validate-escaped-single-quotes-in-i18n {:level :warning}
|
||||
:metabase/validate-string-or-str-args-to-i18n {:level :warning}
|
||||
{:metabase/validate-escaped-single-quotes-in-i18n {:level :warning}
|
||||
:metabase/validate-string-or-str-args-to-i18n {:level :warning}
|
||||
:metabase/discourage-millis-duration {:level :warning}
|
||||
|
||||
:discouraged-var
|
||||
{clojure.core/with-redefs {:message "Don't use with-redefs outside of tests"}
|
||||
@@ -932,7 +907,6 @@
|
||||
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/get-in {:message "Use metabase.util.performance/get-in"}
|
||||
clojure.core/select-keys {:message "Use metabase.util.performance/select-keys"}
|
||||
clojure.core/some {:message "Use metabase.util.performance/some"}
|
||||
clojure.core/update-keys {:message "Use metabase.util.performance/update-keys"}
|
||||
@@ -973,11 +947,4 @@
|
||||
toucan2.tools.with-temp/with-temp {:message "Use lib.tu/mock-metadata-provider in QP tests. Exceptions: tests for code that doesn't use MPs or models not supported by MPs"}}
|
||||
|
||||
:metabase/disallow-hardcoded-driver-names-in-tests
|
||||
{:level :warning}}}
|
||||
|
||||
mage-namespaces
|
||||
{:linters
|
||||
{:discouraged-java-method
|
||||
{java.lang.System {exit
|
||||
{:level :error
|
||||
:message "Don't use System/exit directly. Instead use mage.util/exit and let mage handle exiting the process"}}}}}}}
|
||||
{:level :warning}}}}}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
||||
;; TODO -- seems silly to maintain different blacklists and whitelists here than we use for the deftest `^:parallel`
|
||||
;; checker...
|
||||
{:explicitly-safe
|
||||
#{;; these toucan methods might actually set global values if it's used outside of a transaction,
|
||||
#{ ;; these toucan methods might actually set global values if it's used outside of a transaction,
|
||||
;; but since mt/with-temp runs in a transaction, so we'll ignore them in this case.
|
||||
toucan2.core/delete!
|
||||
toucan2.core/update!
|
||||
|
||||
13
.clj-kondo/macros/metabase/queries/api/card_test.clj
Normal file
13
.clj-kondo/macros/metabase/queries/api/card_test.clj
Normal file
@@ -0,0 +1,13 @@
|
||||
(ns macros.metabase.queries.api.card-test)
|
||||
|
||||
(defmacro with-ordered-items
|
||||
[collection model-and-name-syms & body]
|
||||
`(let ~(into []
|
||||
(comp (partition-all 2)
|
||||
(mapcat (fn [[model binding]]
|
||||
[binding model])))
|
||||
model-and-name-syms)
|
||||
~collection
|
||||
~@(->> (partition-all 2 model-and-name-syms)
|
||||
(map second))
|
||||
~@body))
|
||||
@@ -1,13 +0,0 @@
|
||||
(ns macros.metabase.queries-rest.api.card-test)
|
||||
|
||||
(defmacro with-ordered-items
|
||||
[collection model-and-name-syms & body]
|
||||
`(let ~(into []
|
||||
(comp (partition-all 2)
|
||||
(mapcat (fn [[model binding]]
|
||||
[binding model])))
|
||||
model-and-name-syms)
|
||||
~collection
|
||||
~@(->> (partition-all 2 model-and-name-syms)
|
||||
(map second))
|
||||
~@body))
|
||||
@@ -0,0 +1,9 @@
|
||||
(ns macros.metabase.server.statistics-handler-test)
|
||||
|
||||
(defmacro with-server
|
||||
[_ _ [connector chan-start-handle chan-finish-handle chan-done-request] & body]
|
||||
`(let [~connector nil
|
||||
~chan-start-handle nil
|
||||
~chan-finish-handle nil
|
||||
~chan-done-request nil]
|
||||
~@body))
|
||||
@@ -1,38 +0,0 @@
|
||||
(ns hooks.clojure.core
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[clojure.string :as str]))
|
||||
|
||||
(defn- all-uppercase? [symb]
|
||||
(let [s (str symb)]
|
||||
(= s #_{:clj-kondo/ignore [:discouraged-var]} (str/upper-case s))))
|
||||
|
||||
(defn- has-underscores? [symb]
|
||||
(str/includes? (str symb) "_"))
|
||||
|
||||
(defn- check-symbol-is-kebab-case [symbol-node]
|
||||
(let [symb (hooks/sexpr symbol-node)]
|
||||
(when (or (all-uppercase? symb)
|
||||
(has-underscores? symb))
|
||||
(hooks/reg-finding! (assoc (meta symbol-node)
|
||||
:message "Use kebab-case for function and variable names; don't use special notation for constants."
|
||||
:type :metabase/check-def-kebab-case)))))
|
||||
|
||||
(defn lint-def* [{:keys [node]}]
|
||||
(let [[_def & args] (:children node)
|
||||
name-symbol (some (fn [arg]
|
||||
(when (and (hooks/token-node? arg)
|
||||
(symbol? (hooks/sexpr arg)))
|
||||
arg))
|
||||
args)]
|
||||
(check-symbol-is-kebab-case name-symbol)))
|
||||
|
||||
(defn lint-def [x]
|
||||
(lint-def* x)
|
||||
x)
|
||||
|
||||
(comment
|
||||
(defn x []
|
||||
(let [node (-> "(def ^:private TYPE->MODEL {\"document\" :model/Document})"
|
||||
hooks/parse-string)]
|
||||
(lint-def {:node node}))))
|
||||
@@ -1,52 +0,0 @@
|
||||
(ns hooks.clojure.core.def
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[clojure.string :as str]))
|
||||
|
||||
(defn- uppercase-name?
|
||||
"Whether the symbol is clearly an uppercase name like `TYPE-CONSTANTS` or `SOME_CONSTANT` or `TYPE->MODEL`."
|
||||
[s]
|
||||
(and (re-find #"[A-Z0-9]+(?:[-_]>?[A-Z0-9]+)+" s)
|
||||
;; ignore stuff like `->SCREAMING_SNAKE_CASE`... this is ok
|
||||
(not (str/includes? s "SCREAMING_SNAKE_CASE"))))
|
||||
|
||||
(defn- has-underscores? [s]
|
||||
(and (str/includes? s "_")
|
||||
;; ignore stuff like `->snake_case` and `->SCREAMING_SNAKE_CASE`
|
||||
(not (str/includes? s "snake_case"))
|
||||
(not (str/includes? s "SCREAMING_SNAKE_CASE"))))
|
||||
|
||||
(defn- check-symbol-is-kebab-case [symbol-node]
|
||||
(let [symb (hooks/sexpr symbol-node)
|
||||
s (str symb)]
|
||||
(cond
|
||||
(uppercase-name? s)
|
||||
(hooks/reg-finding!
|
||||
(assoc (meta symbol-node)
|
||||
:message "Use lower-case names for functions and variables; don't use special notation for constants. [:metabase/check-def-check-not-all-uppercase]"
|
||||
:type :metabase/check-def-check-not-uppercase-name))
|
||||
|
||||
(has-underscores? s)
|
||||
(hooks/reg-finding!
|
||||
(assoc (meta symbol-node)
|
||||
:message "Use kebab-case names for functions and variables. [:metabase/check-def-no-underscores]"
|
||||
:type :metabase/check-def-no-underscores)))))
|
||||
|
||||
(defn lint-def* [{:keys [node]}]
|
||||
(let [[_def & args] (:children node)
|
||||
name-symbol (some (fn [arg]
|
||||
(when (and (hooks/token-node? arg)
|
||||
(symbol? (hooks/sexpr arg)))
|
||||
arg))
|
||||
args)]
|
||||
(check-symbol-is-kebab-case name-symbol)))
|
||||
|
||||
(defn lint-def [x]
|
||||
(lint-def* x)
|
||||
x)
|
||||
|
||||
(comment
|
||||
(defn x []
|
||||
(let [node (-> "(def ^:private TYPE->MODEL {\"document\" :model/Document})"
|
||||
hooks/parse-string)]
|
||||
(lint-def {:node node}))))
|
||||
@@ -1,7 +0,0 @@
|
||||
(ns hooks.clojure.core.defmacro
|
||||
(:require
|
||||
[hooks.clojure.core.def]))
|
||||
|
||||
(defn lint-defmacro [x]
|
||||
(hooks.clojure.core.def/lint-def* x)
|
||||
x)
|
||||
@@ -2,7 +2,6 @@
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[clojure.string :as str]
|
||||
[hooks.clojure.core.def]
|
||||
[hooks.common]))
|
||||
|
||||
(defn- check-arglists [report-node arglists]
|
||||
@@ -50,5 +49,4 @@
|
||||
|
||||
(defn lint-defmulti [x]
|
||||
(defmulti-check-for-arglists-metadata (:node x))
|
||||
(hooks.clojure.core.def/lint-def* x)
|
||||
x)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[clojure.string :as str]
|
||||
[hooks.clojure.core.def]
|
||||
[hooks.common]))
|
||||
|
||||
(defn- end-with-exclamation?
|
||||
@@ -51,11 +50,6 @@
|
||||
(non-thread-safe-form-should-end-with-exclamation* node config))
|
||||
{:node node})
|
||||
|
||||
(defn lint-defn [x]
|
||||
(non-thread-safe-form-should-end-with-exclamation x)
|
||||
(hooks.clojure.core.def/lint-def* x)
|
||||
x)
|
||||
|
||||
(comment
|
||||
(require '[clj-kondo.core :as clj-kondo])
|
||||
(def form (str '(defmacro a
|
||||
|
||||
@@ -99,48 +99,12 @@
|
||||
:message "Metabase namespaces should have the form metabase[-enterprise].<module>.* [:metabase/namespace-name]"
|
||||
:type :metabase/namespace-name)))))))
|
||||
|
||||
(defn- ns-form-node->import-node [ns-form-node]
|
||||
(some (fn [node]
|
||||
(when (and (hooks/list-node? node)
|
||||
(let [first-child (first (:children node))]
|
||||
(and (hooks/keyword-node? first-child)
|
||||
(= (hooks/sexpr first-child) :import))))
|
||||
node))
|
||||
(:children ns-form-node)))
|
||||
|
||||
(defn- jsqlparser-import?
|
||||
"Check if a symbol or string represents a jsqlparser import."
|
||||
[s]
|
||||
(str/starts-with? (str s) "net.sf.jsqlparser"))
|
||||
|
||||
(defn- lint-jsqlparser-imports [ns-form-node]
|
||||
(when-let [import-node (ns-form-node->import-node ns-form-node)]
|
||||
(doseq [node (rest (:children import-node))
|
||||
:when (not (contains? (hooks.common/ignored-linters node) :metabase/no-jsqlparser-imports))]
|
||||
(cond
|
||||
;; Handle (net.sf.jsqlparser.foo Bar Baz) form
|
||||
(hooks/list-node? node)
|
||||
(let [package-node (first (:children node))
|
||||
package-name (when package-node (str (hooks/sexpr package-node)))]
|
||||
(when (jsqlparser-import? package-name)
|
||||
(hooks/reg-finding! (assoc (meta node)
|
||||
:message "Don't import net.sf.jsqlparser classes directly. This code should be agnostic to the parsing library. [:metabase/no-jsqlparser-imports]"
|
||||
:type :metabase/no-jsqlparser-imports))))
|
||||
|
||||
;; Handle net.sf.jsqlparser.foo.Bar form (single class import)
|
||||
(hooks/token-node? node)
|
||||
(when (jsqlparser-import? (hooks/sexpr node))
|
||||
(hooks/reg-finding! (assoc (meta node)
|
||||
:message "Don't import net.sf.jsqlparser classes directly. This code should be agnostic to the parsing library. [:metabase/no-jsqlparser-imports]"
|
||||
:type :metabase/no-jsqlparser-imports)))))))
|
||||
|
||||
(defn lint-ns [x]
|
||||
(doto (:node x)
|
||||
lint-require-shapes
|
||||
lint-requires-on-new-lines
|
||||
(lint-modules (modules/config x))
|
||||
lint-namespace-name
|
||||
lint-jsqlparser-imports)
|
||||
lint-namespace-name)
|
||||
x)
|
||||
|
||||
(comment
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[clojure.string :as str]
|
||||
[hooks.common]
|
||||
[hooks.common.modules :as modules]))
|
||||
[hooks.common]))
|
||||
|
||||
(defn- warn-about-disallowed-parallel-forms [form config]
|
||||
(letfn [(error! [form message]
|
||||
@@ -103,39 +102,7 @@
|
||||
(:children node))))]
|
||||
(walk node)))
|
||||
|
||||
(defn- test-namespace-lives-outside-of-module-system? [ns-symb]
|
||||
(some (fn [prefix]
|
||||
(str/starts-with? (name ns-symb) prefix))
|
||||
["build-drivers."
|
||||
"build."
|
||||
"i18n." ; bin/i18n
|
||||
"lint-migrations-file-test"
|
||||
"main-test" ; bin/release-list
|
||||
"metabase.deps-edn-test"
|
||||
"metabase.driver."
|
||||
"metabase.test."
|
||||
"metabase.test.data."
|
||||
"metabuild-common."])) ; utils for build scripts
|
||||
|
||||
(let [warned-namespaces (atom #{})]
|
||||
(defn deftest-check-in-valid-module
|
||||
"Check whether a `deftest` lives in a known module -- important so our system to only run a subset of tests based on
|
||||
which modules have changes picks them up correctly. (DEV-1125)"
|
||||
[{:keys [node], ns-symb :ns, :as input}]
|
||||
(when (and ns-symb
|
||||
(not (test-namespace-lives-outside-of-module-system? ns-symb))
|
||||
;; only warn once per namespace
|
||||
(not (contains? @warned-namespaces ns-symb)))
|
||||
(swap! warned-namespaces conj ns-symb)
|
||||
(let [known-modules (set (keys (:metabase/modules (modules/config input))))
|
||||
current-module (modules/module ns-symb)]
|
||||
(when (or (not current-module)
|
||||
(not (contains? known-modules current-module)))
|
||||
(hooks/reg-finding! (assoc (meta node)
|
||||
:message (format "All tests must live in a known module; %s is not a known module" (pr-str current-module))
|
||||
:type :metabase/tests-must-live-in-known-modules)))))))
|
||||
|
||||
(defn deftest [{:keys [node cljc lang config], :as input}]
|
||||
(defn deftest [{:keys [node cljc lang config]}]
|
||||
;; run [[deftest-check-parallel]] only once... if this is a `.cljc` file only run it for the `:clj` analysis, no point
|
||||
;; in running it twice.
|
||||
(when (or (not cljc)
|
||||
@@ -143,8 +110,7 @@
|
||||
(deftest-check-parallel node (get-in config [:linters :metabase/validate-deftest])))
|
||||
(deftest-check-not-horrifically-long node (get-in config [:linters :metabase/i-like-making-cams-eyes-bleed-with-horrifically-long-tests]))
|
||||
(deftest-check-no-driver-keywords node (get-in config [:linters :metabase/disallow-hardcoded-driver-names-in-tests]))
|
||||
(deftest-check-in-valid-module input)
|
||||
input)
|
||||
{:node node})
|
||||
|
||||
;;; this is a hacky way to determine whether these namespaces are required in the `ns` form or not... basically `:ns`
|
||||
;;; will come back as `nil` if they are not.
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
(ns hooks.common.modules
|
||||
(:require
|
||||
[clojure.string :as str]))
|
||||
(ns hooks.common.modules)
|
||||
|
||||
(defn ignored-namespace? [config ns-symb]
|
||||
(some
|
||||
@@ -19,19 +17,14 @@
|
||||
(module 'metabase-enterprise.whatever.core) => enterprise/whatever"
|
||||
[ns-symb]
|
||||
{:pre [(simple-symbol? ns-symb)]}
|
||||
;; treat something like `metabase.driver-test` (for a module that hasn't fully been updated to use `.core`
|
||||
;; namespaces) as being in the `driver` module
|
||||
(let [ns-symb (if (str/ends-with? (name ns-symb) "-test")
|
||||
(symbol (str/replace (name ns-symb) #"-test$" ""))
|
||||
ns-symb)]
|
||||
(or (some->> (re-find #"^metabase-enterprise\.([^.]+)" (str ns-symb))
|
||||
second
|
||||
(symbol "enterprise"))
|
||||
(some-> (re-find #"^metabase\.([^.]+)" (str ns-symb))
|
||||
second
|
||||
symbol))))
|
||||
(or (some->> (re-find #"^metabase-enterprise\.([^.]+)" (str ns-symb))
|
||||
second
|
||||
(symbol "enterprise"))
|
||||
(some-> (re-find #"^metabase\.([^.]+)" (str ns-symb))
|
||||
second
|
||||
symbol)))
|
||||
|
||||
(defn- module-api-namespaces
|
||||
(defn module-api-namespaces
|
||||
"Set of API namespace symbols for a given module. `:any` means you can use anything, there are no API namespaces for
|
||||
this module (yet). If unspecified, the default is just the `<module>.core` namespace."
|
||||
[config module]
|
||||
@@ -51,12 +44,6 @@
|
||||
(symbol (str ns-prefix ".core"))
|
||||
(symbol (str ns-prefix ".init"))}))))
|
||||
|
||||
(defn- module-friends
|
||||
[config module]
|
||||
"Set of modules that are `:friends` of `module`, i.e. allowed to use *any* namespace from the module, not just the
|
||||
designated [[module-api-namespaces]]."
|
||||
(set (get-in config [:metabase/modules module :friends])))
|
||||
|
||||
(defn allowed-modules
|
||||
"Set of namespace symbols that `module` is allowed to use. `:any` means it's allowed to use anything."
|
||||
[config module]
|
||||
@@ -67,22 +54,11 @@
|
||||
(or (= allowed-modules :any)
|
||||
(contains? (set allowed-modules) required-module))))
|
||||
|
||||
(defn- allowed-module-namespace? [config current-module ns-symb]
|
||||
(defn allowed-module-namespace? [config ns-symb]
|
||||
(let [module (module ns-symb)
|
||||
module-api-namespaces (module-api-namespaces config module)
|
||||
module-friends (module-friends config module)]
|
||||
module-api-namespaces (module-api-namespaces config module)]
|
||||
(or (empty? module-api-namespaces)
|
||||
(contains? module-api-namespaces ns-symb)
|
||||
(contains? module-friends current-module))))
|
||||
|
||||
(defn- rest-module? [module]
|
||||
(str/ends-with? module "-rest"))
|
||||
|
||||
(defn- routes-module? [module]
|
||||
(str/ends-with? module "-routes"))
|
||||
|
||||
(defn- core-module? [module]
|
||||
(str/ends-with? module "core"))
|
||||
(contains? module-api-namespaces ns-symb))))
|
||||
|
||||
(defn usage-error
|
||||
"Find usage errors when a `required-namespace` is required in the `current-module`. Returns a string describing the
|
||||
@@ -98,19 +74,8 @@
|
||||
current-module
|
||||
current-module)
|
||||
|
||||
(not (allowed-module-namespace? config current-module required-namespace))
|
||||
(not (allowed-module-namespace? config required-namespace))
|
||||
(format "Namespace %s is not an allowed external API namespace for the %s module. [:metabase/modules %s :api]"
|
||||
required-namespace
|
||||
required-module
|
||||
required-module)
|
||||
|
||||
;; (for now) rest modules are allowed to use one another; `routes` is ok because it collects routes together
|
||||
;; and `core` is ok because [[metabase.core.init]] might need to init some of the `-routes` modules'
|
||||
;; namespaces
|
||||
(and (not ((some-fn rest-module? routes-module? core-module?) current-module))
|
||||
(rest-module? required-module))
|
||||
(format "Do not use -rest modules (%s) in non-rest modules (%s) -- move things from %s to %s if needed"
|
||||
required-module
|
||||
current-module
|
||||
required-module
|
||||
(symbol (str/replace required-module #"-rest$" "")))))))
|
||||
required-module)))))
|
||||
|
||||
@@ -90,10 +90,6 @@
|
||||
:else
|
||||
(recur (conj bindings x) (conj schemas nil) (list* y z more)))))]
|
||||
(validate-route! route)
|
||||
(when-not result-schema
|
||||
(api/reg-finding! (assoc (meta node)
|
||||
:message "All REST API endpoints should have a detailed response schema"
|
||||
:type :metabase/validate-defendpoint-has-response-schema)))
|
||||
(let [[_route-param-schema query-param-schema _body-param-schema] schemas]
|
||||
(when query-param-schema
|
||||
(validate-query-params! query-param-schema)))
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
(ns hooks.metabase.query-processor.expressions-test
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[hooks.metabase.test.data :as data]))
|
||||
|
||||
(defn calculate-bird-scarcity
|
||||
[{{[_ formula filter-clause] :children} :node}]
|
||||
(let [formula* (data/replace-$id-special-tokens formula)
|
||||
filter-clause* (or (some-> filter-clause data/replace-$id-special-tokens)
|
||||
(hooks/token-node 'nil))
|
||||
node* (hooks/list-node
|
||||
(list
|
||||
(hooks/token-node 'do)
|
||||
formula*
|
||||
filter-clause*))]
|
||||
{:node node*}))
|
||||
@@ -0,0 +1,16 @@
|
||||
(ns hooks.metabase.query-processor-test.expressions-test
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[hooks.metabase.test.data :as data]))
|
||||
|
||||
(defn calculate-bird-scarcity
|
||||
[{{[_ formula filter-clause] :children} :node}]
|
||||
(let [formula* (data/replace-$id-special-tokens formula)
|
||||
filter-clause* (or (some-> filter-clause data/replace-$id-special-tokens)
|
||||
(hooks/token-node 'nil))
|
||||
node* (hooks/list-node
|
||||
(list
|
||||
(hooks/token-node 'do)
|
||||
formula*
|
||||
filter-clause*))]
|
||||
{:node node*}))
|
||||
@@ -97,6 +97,7 @@
|
||||
metabot-get-prompt-templates-url
|
||||
metabot-prompt-generator-token-limit
|
||||
multi-setting-read-only
|
||||
notebook-native-preview-shown
|
||||
notebook-native-preview-sidebar-width
|
||||
notification-link-base-url
|
||||
num-metabot-choices
|
||||
@@ -118,10 +119,10 @@
|
||||
reset-token-ttl-hours
|
||||
retired-setting
|
||||
retry-initial-interval
|
||||
retry-max-retries
|
||||
retry-max-attempts
|
||||
retry-max-interval-millis
|
||||
retry-multiplier
|
||||
retry-jitter-factor
|
||||
retry-randomization-factor
|
||||
saml-application-name
|
||||
saml-attribute-email
|
||||
saml-attribute-firstname
|
||||
|
||||
@@ -43,17 +43,6 @@
|
||||
'{:metabase/modules {api {:uses #{search}}
|
||||
search {:api #{metabase.search.core}}}}))))
|
||||
|
||||
(deftest ^:parallel module-checker-friends-test
|
||||
(is (= []
|
||||
(lint-modules
|
||||
'(ns metabase.search-rest.api
|
||||
(:require
|
||||
[metabase.search.config :as search.config]
|
||||
[metabase.search.core :as search]))
|
||||
'{:metabase/modules {search {:api #{metabase.search.core}
|
||||
:friends #{search-rest}}
|
||||
search-rest {:uses #{search}}}}))))
|
||||
|
||||
(deftest ^:parallel module-checker-api-namespaces-ignore-test
|
||||
(is (empty? (lint-modules
|
||||
'(ns metabase.api.search
|
||||
|
||||
31
.cljfmt.edn
31
.cljfmt.edn
@@ -43,6 +43,9 @@
|
||||
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]]
|
||||
@@ -90,7 +93,6 @@
|
||||
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-enterprise.workspaces.models.workspace-log/track! [[: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]]
|
||||
@@ -120,10 +122,7 @@
|
||||
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/match-lite [[:inner 0]]
|
||||
metabase.driver-api.core/match-many [[:inner 0]]
|
||||
metabase.driver-api.core/replace [[:inner 0]]
|
||||
metabase.driver-api.core/replace-lite [[: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]]
|
||||
@@ -137,7 +136,6 @@
|
||||
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.driver/with-swapped-connection-details [[:block 2]]
|
||||
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]]
|
||||
@@ -159,9 +157,8 @@
|
||||
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-many [[:inner 0]]
|
||||
metabase.lib.util.match/match-lite-recursive [[:inner 0]]
|
||||
metabase.lib.util.match/replace [[:inner 0]]
|
||||
metabase.lib.util.match/replace-lite [[: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]]
|
||||
@@ -179,23 +176,21 @@
|
||||
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-rest.api.card-test/with-cards-in-readable-collection [[:block 1]]
|
||||
metabase.queries-rest.api.card-test/with-cards-in-writeable-collection [[:block 1]]
|
||||
metabase.queries-rest.api.card-test/with-ordered-items [[:inner 0]]
|
||||
metabase.queries-rest.api.card-test/with-temp-native-card [[:block 1]]
|
||||
metabase.queries-rest.api.card-test/with-temp-native-card-with-params [[:block 1]]
|
||||
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.pipeline-queries-test/pmbql-query [[:block 1]]
|
||||
metabase.query-processor.pipeline-queries-test/run-pmbql-query [[: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.query-processor.timeseries-test.util/test-timeseries-drivers [[:block 0]]
|
||||
metabase.query-processor.timeseries-test/run-mbql-query [[:inner 0]]
|
||||
metabase.request.core/as-admin [[:block 0]]
|
||||
metabase.request.core/with-current-request [[:inner 0]]
|
||||
metabase.request.core/with-current-user [[:inner 0]]
|
||||
@@ -283,9 +278,12 @@
|
||||
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]]
|
||||
@@ -306,7 +304,6 @@
|
||||
metabase.util.queue-test/await-test-while [[:block 1]]
|
||||
metabase.util/auto-retry [[:block 1]]
|
||||
metabase.util/case-enum [[:block 1]]
|
||||
metabase.util/for-map [[:block 1]]
|
||||
metabase.util/ignore-exceptions [[:block 0]]
|
||||
metabase.util/or-with [[:block 1]]
|
||||
metabase.util/profile [[:block 1]]
|
||||
|
||||
@@ -1,35 +1,23 @@
|
||||
Brite
|
||||
afterAll
|
||||
ags
|
||||
altho
|
||||
Brite
|
||||
cancelation
|
||||
cancelations
|
||||
caracteres
|
||||
copyin
|
||||
datas
|
||||
edn
|
||||
eduction
|
||||
hasTable
|
||||
hasTables
|
||||
horizontalX
|
||||
horizontalY
|
||||
juxt
|
||||
methodd
|
||||
nd
|
||||
notIn
|
||||
optionA
|
||||
ot
|
||||
pass-thru
|
||||
requestor
|
||||
re-use
|
||||
re-used
|
||||
ser
|
||||
serieses
|
||||
sortings
|
||||
titel
|
||||
trs
|
||||
trsn
|
||||
tru
|
||||
trun
|
||||
unparseable
|
||||
withs
|
||||
|
||||
23
.codespellrc
23
.codespellrc
@@ -1,24 +1,3 @@
|
||||
[codespell]
|
||||
skip =
|
||||
*.csv,
|
||||
*.edn,
|
||||
*.geojson,
|
||||
*.json,
|
||||
*.tsv,
|
||||
*.unit.spec.*,
|
||||
*.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/query_builder/components/NativeQueryEditor/CodeMirrorEditor/language.ts,
|
||||
frontend/src/metabase/static-viz/constants/char-sizes.ts,
|
||||
frontend/src/metabase/visualizations/lib/mapping_codes.ts,
|
||||
frontend/*/mocks/*,
|
||||
modules/drivers/mongo/src/metabase/driver/mongo/operators.clj,
|
||||
src/metabase/analyze/classifiers/name.clj,
|
||||
src/metabase/util/currency.cljc,
|
||||
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
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
FROM mcr.microsoft.com/vscode/devcontainers/java:21
|
||||
|
||||
# Set up nodesource, install node, bun, fontconfig for static viz, rlwrap for dev ergonomics
|
||||
# Set up nodesource, install node, yarn, fontconfig for static viz, rlwrap for dev ergonomics
|
||||
|
||||
RUN ( curl -fsSL https://deb.nodesource.com/setup_22.x | bash ) \
|
||||
&& export DEBIAN_FRONTEND=noninteractive \
|
||||
&& apt-get update && apt-get -y install --no-install-recommends nodejs rlwrap fontconfig \
|
||||
&& npm install -g bun
|
||||
&& apt-get update && apt-get -y install --no-install-recommends nodejs yarn rlwrap fontconfig
|
||||
|
||||
# install Clojure
|
||||
RUN curl https://download.clojure.org/install/linux-install-1.12.0.1488.sh | bash
|
||||
|
||||
@@ -61,7 +61,6 @@
|
||||
(eval . (put-clojure-indent 'u/prog1 1))
|
||||
(eval . (put-clojure-indent 'u/select-keys-when '(:form)))
|
||||
(eval . (put-clojure-indent 'with-meta '(:form)))
|
||||
(eval . (put-clojure-indent 'ws.log/track! 2))
|
||||
;; these ones have to be done with `define-clojure-indent' for now because of upstream bug
|
||||
;; https://github.com/clojure-emacs/clojure-mode/issues/600 once that's resolved we should use `put-clojure-indent'
|
||||
;; instead. Please don't add new entries unless they don't work with `put-clojure-indent'
|
||||
|
||||
163
.env.example
163
.env.example
@@ -1,163 +0,0 @@
|
||||
# ==============================================================================
|
||||
# 0. METABASE DEVELOPMENT ENVIRONMENT
|
||||
# ==============================================================================
|
||||
#
|
||||
# Central configuration for local Metabase development.
|
||||
# Copy to .env and uncomment/modify values as needed.
|
||||
#
|
||||
# Usage:
|
||||
# op inject --in-file .env.example --out-file .env (populate using CLI)
|
||||
# cp .env.example .env (populate manually)
|
||||
#
|
||||
# Hint: Auto-load environment variables with mise or direnv:
|
||||
# mise - mise.toml: [settings] env_file = ".env" (https://mise.jdx.dev)
|
||||
# direnv - Create .envrc with: dotenv .env (https://direnv.net)
|
||||
#
|
||||
# Full Metabase environment configuration reference:
|
||||
# https://www.metabase.com/docs/latest/configuring-metabase/environment-variables
|
||||
#
|
||||
# ⚠️ Make sure the same var is not exported multiple times in different contexts.
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# 1. GLOBAL CONTEXT
|
||||
# ==============================================================================
|
||||
#
|
||||
# Shared credentials and tokens used across multiple dev workflows.
|
||||
|
||||
# GitHub - For gh CLI, API access, and CI integrations
|
||||
# https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens
|
||||
# GITHUB_TOKEN=your-PAT
|
||||
|
||||
# AWS - For S3, Athena, Redshift, and other AWS services
|
||||
# https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html
|
||||
# AWS_ACCESS_KEY_ID=your-access-key
|
||||
# AWS_SECRET_ACCESS_KEY=your-secret-key
|
||||
# AWS_REGION=us-east-1
|
||||
|
||||
# Docker Hub - Avoid rate limits when pulling images
|
||||
# https://docs.docker.com/security/for-developers/access-tokens/
|
||||
# DOCKER_USERNAME=your-username
|
||||
# DOCKER_PASSWORD=your-access-token
|
||||
|
||||
# ==============================================================================
|
||||
# 2. LOCAL METABASE DEVELOPMENT
|
||||
# ==============================================================================
|
||||
#
|
||||
# Settings for running Metabase locally. Most have sensible defaults—only
|
||||
# configure what you need.
|
||||
|
||||
# 2.1 Application Database
|
||||
# ------------------------------------------------------------------------------
|
||||
# Where Metabase stores its own data (dashboards, users, settings, etc.).
|
||||
# https://www.metabase.com/docs/latest/installation-and-operation/configuring-application-database
|
||||
|
||||
# Option A: Individual connection parameters
|
||||
# MB_DB_TYPE="postgres"
|
||||
# MB_DB_HOST="localhost"
|
||||
# MB_DB_PORT=5432
|
||||
# MB_DB_DBNAME="metabase"
|
||||
# MB_DB_USER="<username>"
|
||||
# MB_DB_PASS="<password>"
|
||||
|
||||
# Option B: Single connection URI (overrides individual params)
|
||||
# MB_DB_CONNECTION_URI="jdbc:postgresql://localhost:5432/metabase?user=<username>&password=<password>"
|
||||
|
||||
# 2.2 Server & Integrations
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://www.metabase.com/docs/latest/configuring-metabase/customizing-jetty-webserver
|
||||
|
||||
# Jetty server (defaults: localhost:3000)
|
||||
# MB_JETTY_HOST="localhost"
|
||||
# MB_JETTY_PORT=3000
|
||||
|
||||
# SMTP for testing email notifications (use with maildev/maildev container)
|
||||
MB_EMAIL_SMTP_HOST="localhost"
|
||||
MB_EMAIL_SMTP_PORT=1025
|
||||
MB_EMAIL_SMTP_USERNAME="admin"
|
||||
MB_EMAIL_SMTP_PASSWORD="admin"
|
||||
|
||||
# 2.3 Feature Flags & Tokens
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Use staging server for license validation (avoids hitting production)
|
||||
METASTORE_DEV_SERVER_URL="https://token-check.staging.metabase.com"
|
||||
|
||||
# Metabase license token
|
||||
# Uncomment this line only when running local dev and NEVER when running Cypress
|
||||
# Tests need to start from zero, and set different tokens programmatically (on-demand)
|
||||
# MB_PREMIUM_EMBEDDING_TOKEN="op://Shared/Metabase License Dev Tokens/Licenses/MB_ALL_FEATURES_TOKEN"
|
||||
|
||||
# Skip embedding SDK build for faster frontend compilation
|
||||
# SKIP_EMBEDDING_SDK="true"
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# 3. E2E / CYPRESS TESTING
|
||||
# ==============================================================================
|
||||
#
|
||||
# Configuration for running Cypress end-to-end tests locally.
|
||||
#
|
||||
# The whole environment is already orchestrated via deps.edn using :e2e alias.
|
||||
# Treat it as a source of truth for JVM flags and ENVs whether you decide to run
|
||||
# backend on your own, or using `bun run test-cypress` (local test runner).
|
||||
#
|
||||
# You don't _have to_ export any of these vars (except for the tokens below).
|
||||
# Things will still work.
|
||||
|
||||
# 3.1. E2E Application Database (H2 required)
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Whether to run the enterprise or the open-source Metabase version
|
||||
MB_EDITION="ee"
|
||||
|
||||
# Which file to use for the ephemeral testing application database?
|
||||
# MB_DB_FILE="./e2e/tmp/cypress-test.db"
|
||||
|
||||
# Where does Cypress go to look for the application? (defaults: localhost:4000)
|
||||
# MB_JETTY_HOST="localhost"
|
||||
# MB_JETTY_PORT=4000
|
||||
|
||||
# Frontend dev server port (for hot reload). Must match the port used by `yarn build-hot`.
|
||||
# MB_FRONTEND_DEV_PORT=8080
|
||||
|
||||
# 3.2 E2E Local Runner Setup
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Whether to run true e2e test suite, or SDK component tests (e2e | component)
|
||||
CYPRESS_TESTING_TYPE="e2e"
|
||||
|
||||
# Whether to start Cypress in an interactive (open) mode with full GUI
|
||||
CYPRESS_GUI="true"
|
||||
|
||||
# Whether to generate application database dumps (colloquially "snapshots")
|
||||
# Keep this enabled unless you're experimenting, or know what you're doing.
|
||||
GENERATE_SNAPSHOTS="true"
|
||||
|
||||
# E2E tests run the backend from source by default.
|
||||
# To run tests against a JAR instead, set the JAR_PATH variable.
|
||||
#
|
||||
# Hint: build the JAR with ./bin/build.sh
|
||||
# JAR_PATH="target/uberjar/metabase.jar"
|
||||
|
||||
# 3.3 E2E Tokens
|
||||
# ------------------------------------------------------------------------------
|
||||
#
|
||||
# These make sense for Metabase core devs only
|
||||
# op inject --in-file .env.example --out-file .env
|
||||
|
||||
CYPRESS_MB_ALL_FEATURES_TOKEN="op://Shared/Metabase License Dev Tokens/Licenses/MB_ALL_FEATURES_TOKEN"
|
||||
CYPRESS_MB_STARTER_CLOUD_TOKEN="op://Shared/Metabase License Dev Tokens/Licenses/MB_STARTER_CLOUD_TOKEN"
|
||||
CYPRESS_MB_PRO_CLOUD_TOKEN="op://Shared/Metabase License Dev Tokens/Licenses/MB_PRO_CLOUD_TOKEN"
|
||||
CYPRESS_MB_PRO_SELF_HOSTED_TOKEN="op://Shared/Metabase License Dev Tokens/Licenses/MB_PRO_SELF_HOSTED_TOKEN"
|
||||
|
||||
# 3.4 E2E Misc.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# To make Node.js match the instance tz
|
||||
TZ="US/Pacific"
|
||||
|
||||
# Export this environment variable when running tests on Apple Silicon.
|
||||
# DO NOT USE Docker Desktop. Use Orbstack instead. Metabase will fail to connect
|
||||
# to the dockerized Mongo QA database otherwise.
|
||||
EXPERIMENTAL_DOCKER_DESKTOP_FORCE_QEMU=1
|
||||
5
.eslintignore
Normal file
5
.eslintignore
Normal file
@@ -0,0 +1,5 @@
|
||||
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
|
||||
348
.eslintrc.js
Normal file
348
.eslintrc.js
Normal file
@@ -0,0 +1,348 @@
|
||||
/* eslint-disable import/no-commonjs */
|
||||
/* eslint-disable no-undef */
|
||||
|
||||
const path = require("path");
|
||||
|
||||
// `postcss-modules` lints css modules class names, but it currently crashes
|
||||
// eslint on vscode. If you use webstorm or want to run the lint for the cli, you
|
||||
// can use this flag to enable it. This is set to true in CI
|
||||
const shouldLintCssModules =
|
||||
process.env.LINT_CSS_MODULES === "true" || process.env.CI;
|
||||
const plugins = ["react", "no-only-tests", "ttag", "i18next"];
|
||||
if (shouldLintCssModules) {
|
||||
plugins.push("postcss-modules");
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ignorePatterns: ["!.storybook"],
|
||||
rules: {
|
||||
strict: [2, "never"],
|
||||
"no-undef": 2,
|
||||
"no-var": 1,
|
||||
"no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
vars: "all",
|
||||
args: "none",
|
||||
varsIgnorePattern: "^_.+$",
|
||||
ignoreRestSiblings: true,
|
||||
},
|
||||
],
|
||||
"no-empty": [1, { allowEmptyCatch: true }],
|
||||
// Note: adding this rule to a eslint config file in a subfolder will remove
|
||||
// *not* carry over the restricted imports from parent folders, you will
|
||||
// need to copy them over
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
paths: [
|
||||
{
|
||||
name: "moment",
|
||||
message: "Moment is deprecated, please use dayjs",
|
||||
},
|
||||
{
|
||||
name: "moment-timezone",
|
||||
message: "Moment is deprecated, please use dayjs",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
curly: [1, "all"],
|
||||
eqeqeq: [1, "smart"],
|
||||
"import/no-duplicates": ["warn", { considerQueryString: true }],
|
||||
"import/no-default-export": 2,
|
||||
"import/no-named-as-default": 0,
|
||||
"import/no-commonjs": 1,
|
||||
"import/order": [
|
||||
"error",
|
||||
{
|
||||
"newlines-between": "always",
|
||||
alphabetize: {
|
||||
order: "asc",
|
||||
orderImportKind: "asc",
|
||||
caseInsensitive: false,
|
||||
},
|
||||
groups: [
|
||||
"builtin",
|
||||
"external",
|
||||
"internal",
|
||||
"parent",
|
||||
"sibling",
|
||||
"index",
|
||||
],
|
||||
warnOnUnassignedImports: false,
|
||||
},
|
||||
],
|
||||
"sort-imports": [
|
||||
"error",
|
||||
{
|
||||
// allows this rule to work with import/order
|
||||
ignoreDeclarationSort: true,
|
||||
},
|
||||
],
|
||||
"no-console": [2, { allow: ["warn", "error", "errorBuffer"] }],
|
||||
"react/no-is-mounted": 2,
|
||||
"react/prefer-es6-class": 2,
|
||||
"react/display-name": 1,
|
||||
"react/prop-types": 2,
|
||||
"react/no-did-mount-set-state": 0,
|
||||
"react/no-did-update-set-state": 0,
|
||||
"react/no-find-dom-node": 0,
|
||||
"react/no-children-prop": 2,
|
||||
"react/no-string-refs": 2,
|
||||
"react/no-unescaped-entities": 2,
|
||||
"react/jsx-no-target-blank": 2,
|
||||
"react/jsx-key": 2,
|
||||
"react/forbid-component-props": [2, { forbid: ["sx"] }],
|
||||
"react-hooks/exhaustive-deps": ["warn"],
|
||||
"prefer-const": [1, { destructuring: "all" }],
|
||||
"no-restricted-globals": ["error", "close"],
|
||||
"no-useless-escape": 0,
|
||||
"no-only-tests/no-only-tests": [
|
||||
"error",
|
||||
{
|
||||
block: [
|
||||
"describe",
|
||||
"it",
|
||||
"context",
|
||||
"test",
|
||||
"tape",
|
||||
"fixture",
|
||||
"serial",
|
||||
"Feature",
|
||||
"Scenario",
|
||||
"Given",
|
||||
"And",
|
||||
"When",
|
||||
"Then",
|
||||
],
|
||||
},
|
||||
],
|
||||
complexity: ["error", { max: 55 }],
|
||||
...(shouldLintCssModules
|
||||
? {
|
||||
"postcss-modules/no-undef-class": "error",
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
globals: {
|
||||
before: true,
|
||||
cy: true,
|
||||
Cypress: true,
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
es2020: true,
|
||||
commonjs: true,
|
||||
jest: true,
|
||||
"jest/globals": true,
|
||||
},
|
||||
parser: "babel-eslint",
|
||||
plugins,
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:react/jsx-runtime",
|
||||
"plugin:react-hooks/recommended",
|
||||
"plugin:import/errors",
|
||||
"plugin:import/warnings",
|
||||
"plugin:import/typescript",
|
||||
"plugin:depend/recommended",
|
||||
"plugin:storybook/recommended",
|
||||
"plugin:i18next/recommended",
|
||||
],
|
||||
settings: {
|
||||
"import/internal-regex": "^metabase/|^metabase-lib/",
|
||||
"import/resolver": {
|
||||
webpack: {
|
||||
config: path.resolve(__dirname, "./rspack.main.config.js"),
|
||||
typescript: true,
|
||||
},
|
||||
},
|
||||
"import/ignore": ["\\.css$"],
|
||||
react: {
|
||||
version: "detect",
|
||||
},
|
||||
"postcss-modules": {
|
||||
baseDir: "./frontend/src",
|
||||
},
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaFeatures: {
|
||||
legacyDecorators: true,
|
||||
},
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ["*.js", "*.jsx", "*.ts", "*.tsx"],
|
||||
rules: {
|
||||
"jtag-missing-key": "error",
|
||||
"no-unconditional-metabase-links-render": "error",
|
||||
"no-color-literals": "error",
|
||||
"no-literal-metabase-strings": "error",
|
||||
"depend/ban-dependencies": [
|
||||
"error",
|
||||
{
|
||||
allowed: [
|
||||
"underscore",
|
||||
"moment",
|
||||
"lodash.orderby",
|
||||
"lodash.debounce",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
"*.unit.spec.*",
|
||||
"frontend/src/metabase/admin/**/*",
|
||||
"frontend/src/metabase/setup/**/*",
|
||||
"enterprise/frontend/src/metabase-enterprise/whitelabel/**/*",
|
||||
"enterprise/frontend/src/metabase-enterprise/embedding/**/*",
|
||||
"frontend/lint/**/*",
|
||||
"*.stories.*",
|
||||
"**/.storybook/*",
|
||||
"stories-data.*",
|
||||
"e2e/**/*",
|
||||
"**/tests/*",
|
||||
"release/**/*",
|
||||
"rspack.config.js",
|
||||
"rspack.main.config.js",
|
||||
"rspack.embedding-sdk-package.config.js",
|
||||
"rspack.embedding-sdk-bundle.config.js",
|
||||
],
|
||||
rules: {
|
||||
"no-color-literals": "off",
|
||||
"no-unconditional-metabase-links-render": "off",
|
||||
"no-literal-metabase-strings": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
"*.unit.spec.*",
|
||||
"frontend/lint/**/*",
|
||||
"*.stories.*",
|
||||
"stories-data.*",
|
||||
"e2e/**/*",
|
||||
"**/tests/*",
|
||||
"release/**/*",
|
||||
"rspack.main.config.js",
|
||||
],
|
||||
rules: {
|
||||
"i18next/no-literal-string": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
extends: ["plugin:@typescript-eslint/recommended"],
|
||||
files: ["*.ts", "*.tsx"],
|
||||
parser: "@typescript-eslint/parser",
|
||||
plugins: ["@typescript-eslint"],
|
||||
rules: {
|
||||
"prefer-rest-params": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"@typescript-eslint/no-inferrable-types": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-this-alias": "off",
|
||||
"@typescript-eslint/consistent-type-imports": [
|
||||
"error",
|
||||
{
|
||||
fixStyle: "inline-type-imports",
|
||||
},
|
||||
],
|
||||
"@typescript-eslint/no-import-type-side-effects": "error",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
argsIgnorePattern: "^_.+$",
|
||||
varsIgnorePattern: "^_.+$",
|
||||
ignoreRestSiblings: true,
|
||||
destructuredArrayIgnorePattern: "^_.+$",
|
||||
},
|
||||
],
|
||||
// This was introduced in 6.0.0
|
||||
"@typescript-eslint/no-unsafe-declaration-merging": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
extends: [
|
||||
"plugin:jest/recommended",
|
||||
"plugin:jest-dom/recommended",
|
||||
"plugin:testing-library/react",
|
||||
"plugin:jest-formatting/recommended",
|
||||
],
|
||||
plugins: ["jest", "jest-dom", "testing-library", "jest-formatting"],
|
||||
files: [
|
||||
"*.unit.spec.ts",
|
||||
"*.unit.spec.tsx",
|
||||
"*.unit.spec.js",
|
||||
"*.unit.spec.jsx",
|
||||
],
|
||||
rules: {
|
||||
"jest/valid-title": ["error", { ignoreTypeOfDescribeName: true }],
|
||||
"jest/expect-expect": [
|
||||
"error",
|
||||
{
|
||||
assertFunctionNames: ["expect*", "assert*"],
|
||||
additionalTestBlockFunctions: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
// Enable jest formatting for cypress tests too, the plugin logic just works
|
||||
extends: ["plugin:jest-formatting/recommended"],
|
||||
files: ["*.cy.spec.ts", "*.cy.spec.js"],
|
||||
},
|
||||
{
|
||||
files: ["frontend/src/**/*"],
|
||||
rules: {
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
{
|
||||
selector: "Literal[value=/mb-base-color-/]",
|
||||
message:
|
||||
"You may not use base colors in the application, use semantic colors instead. (see colors.module.css)",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["frontend/src/metabase/**/*"],
|
||||
rules: {
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
{
|
||||
selector: "Literal[value=/mb-base-color-/]",
|
||||
message:
|
||||
"You may not use base colors in the application, use semantic colors instead. (see colors.module.css)",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["frontend/src/metabase/query_builder/**/*"],
|
||||
rules: {
|
||||
"import/no-cycle": "error",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["docs/**/snippets/**/*.{ts,tsx,js,jsx}"],
|
||||
rules: {
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"import/no-commonjs": "off",
|
||||
"import/no-default-export": "off",
|
||||
"import/order": "off",
|
||||
"import/no-unresolved": "off",
|
||||
"no-color-literals": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["frontend/build/**/*.js"],
|
||||
rules: {
|
||||
"import/no-commonjs": "off",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
7
.gitattributes
vendored
7
.gitattributes
vendored
@@ -1,7 +0,0 @@
|
||||
# Custom merge driver for YAML migration files
|
||||
# Treats each changeset as an atomic unit to avoid conflicts within changesets
|
||||
# Execute this in your shell to enable custom merge driver:
|
||||
# echo '\n[include]\npath=../.gitconfig' >> .git/config
|
||||
#
|
||||
# TODO (Bryan 12/12/25) -- reenable this when we can completely verify correctness
|
||||
# resources/migrations/*.yaml merge=yaml-migrations
|
||||
@@ -1,5 +0,0 @@
|
||||
# execute this in your shell to enable custom merge driver:
|
||||
# echo '\n[include]\npath=../.gitconfig' >> .git/config
|
||||
[merge "yaml-migrations"]
|
||||
name = YAML migration changeset-aware merge
|
||||
driver = bin/mage -merge-yaml-migrations %O %A %B %L %P
|
||||
23
.github/ISSUE_TEMPLATE/epic.md
vendored
Normal file
23
.github/ISSUE_TEMPLATE/epic.md
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
name: Epic (Internal Use)
|
||||
about: This issue is used to track a feature/project that may span several days
|
||||
to weeks to implement. This is used by the core Metabase team to project plan
|
||||
and manage work.
|
||||
title: "[Epic] Title"
|
||||
labels: ".Epic"
|
||||
---
|
||||
|
||||
### Links
|
||||
|
||||
- [product doc](LINK_TO_PRODUCT_DOC)
|
||||
- [technical design doc](LINK_TO_TECHNICAL_DESIGN_DOC)
|
||||
- [testing plan](LINK_TO_TESTING_PLAN)
|
||||
- feature branch: `BRANCH_NAME`
|
||||
- issue links: _related issues, if any_
|
||||
|
||||
### Implementation Plan
|
||||
|
||||
```[tasklist]
|
||||
#### Milestone 1
|
||||
- [ ] _Add tasks here_
|
||||
```
|
||||
13
.github/ISSUE_TEMPLATE/feature_request.md
vendored
13
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -7,13 +7,16 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**What problem will this feature request solve?**
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like.**
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**How does this feature request impact you?**
|
||||
For example, is it a minor inconvenience, or does it block important work?
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional information**
|
||||
**How important is this feature to you?**
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
|
||||
14
.github/ISSUE_TEMPLATE/task.md
vendored
Normal file
14
.github/ISSUE_TEMPLATE/task.md
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: Task (Internal Use)
|
||||
about: This issue is used to track a smaller feature/task that does not require
|
||||
being broken down to milestones. This is used by the core Metabase team to
|
||||
project plan and manage work.
|
||||
labels: ".Task"
|
||||
|
||||
---
|
||||
|
||||
**Context**
|
||||
|
||||
|
||||
- issue links: _related issues if any_
|
||||
|
||||
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -25,4 +25,3 @@ _Upload a demo video or before/after screenshots if sensible or remove the secti
|
||||
### Checklist
|
||||
|
||||
- [ ] Tests have been added/updated to cover changes in this PR
|
||||
- [ ] If adding new Loki tests: they pass [stress testing](https://github.com/metabase/metabase/actions/workflows/loki-stress-test-flake-fix.yml)
|
||||
|
||||
@@ -5,7 +5,7 @@ PENDING CI / PENDING REVIEW / READY _(choose one and update accordingly)_
|
||||
- Reproduces #XXXXX
|
||||
|
||||
### How to test this manually?
|
||||
- `bun run test-cypress`
|
||||
- `yarn test-cypress`
|
||||
- `relative/path/to/the/file`
|
||||
> _(For still unfixed bug)_
|
||||
- Replace `it.skip()` with `it.only()` to run the test in isolation
|
||||
|
||||
21
.github/actions/fetch-artifact/action.yml
vendored
21
.github/actions/fetch-artifact/action.yml
vendored
@@ -2,16 +2,9 @@ name: Fetch artifact
|
||||
description: Fetches a specific artifact and downloads it to the current directory
|
||||
|
||||
inputs:
|
||||
commit:
|
||||
description: Commit SHA for the artifact
|
||||
name:
|
||||
description: The name of the artifact to fetch
|
||||
required: true
|
||||
edition:
|
||||
description: Metabase edition (ee or oss)
|
||||
required: true
|
||||
release:
|
||||
description: Whether this is a release artifact (uses metabase-release- prefix)
|
||||
required: false
|
||||
default: "false"
|
||||
output-name:
|
||||
description: Optional custom name for the output jar file (defaults to metabase.jar)
|
||||
required: false
|
||||
@@ -32,21 +25,15 @@ runs:
|
||||
script: | # js
|
||||
const fs = require('fs');
|
||||
|
||||
const isRelease = "${{ inputs.release }}" === "true";
|
||||
const prefix = isRelease ? "metabase-release" : "metabase";
|
||||
const artifactName = `${prefix}-${{ inputs.edition }}-${{ inputs.commit }}-uberjar`;
|
||||
console.log(`Looking for artifact: ${artifactName}`);
|
||||
|
||||
const artifacts = await github.rest.actions.listArtifactsForRepo({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
name: artifactName,
|
||||
name: "${{ inputs.name }}",
|
||||
per_page: 1,
|
||||
});
|
||||
|
||||
if (!artifacts.data?.artifacts?.[0]?.id) {
|
||||
core.setFailed(`No artifacts found for ${artifactName}`);
|
||||
return;
|
||||
throw new Error(`No artifacts found for ${{ inputs.name }}`);
|
||||
}
|
||||
|
||||
const artifact_id = artifacts.data.artifacts[0].id;
|
||||
|
||||
5
.github/actions/get-cache-keys/action.yml
vendored
5
.github/actions/get-cache-keys/action.yml
vendored
@@ -13,9 +13,6 @@ outputs:
|
||||
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"
|
||||
@@ -26,12 +23,10 @@ runs:
|
||||
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
|
||||
|
||||
19
.github/actions/maximize-disk-space/action.yml
vendored
19
.github/actions/maximize-disk-space/action.yml
vendored
@@ -1,19 +0,0 @@
|
||||
name: Maximize disk space
|
||||
description: Removes unused stuff from GH runner
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Maximize build space
|
||||
shell: bash
|
||||
run: |
|
||||
echo "-- Dotnet"
|
||||
sudo rm -rf /usr/share/dotnet
|
||||
echo "-- Android"
|
||||
sudo rm -rf /usr/local/lib/android
|
||||
echo "-- Haskell"
|
||||
sudo rm -rf /opt/ghc
|
||||
echo "-- CodeQL"
|
||||
sudo rm -rf /opt/hostedtoolcache/CodeQL
|
||||
|
||||
df -h
|
||||
19
.github/actions/prepare-backend/action.yml
vendored
19
.github/actions/prepare-backend/action.yml
vendored
@@ -1,13 +1,14 @@
|
||||
name: Prepare back-end environment
|
||||
description: |
|
||||
Prepare the backend. Installs jdk, Clojure, and fetches the cache for m2.
|
||||
The CI config fetches `ci-test-config.json` from master and lets us ignore tests at the var level if they are flaking.
|
||||
inputs:
|
||||
java-version:
|
||||
required: true
|
||||
default: "21"
|
||||
default: '21'
|
||||
clojure-version:
|
||||
required: true
|
||||
default: "1.12.0.1488"
|
||||
default: '1.12.0.1488'
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
@@ -16,7 +17,7 @@ runs:
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: ${{ inputs.java-version }}
|
||||
distribution: "temurin"
|
||||
distribution: 'temurin'
|
||||
- name: Install Clojure CLI
|
||||
shell: bash
|
||||
continue-on-error: true
|
||||
@@ -24,17 +25,15 @@ 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
|
||||
|
||||
- name: Install uv (for Python package installation)
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
enable-cache: false
|
||||
version: "0.10.2" # keep in sync with mise.toml
|
||||
github-token: ${{ github.token }}
|
||||
|
||||
- name: Restore M2 cache
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
|
||||
31
.github/actions/prepare-cypress/action.yml
vendored
31
.github/actions/prepare-cypress/action.yml
vendored
@@ -3,7 +3,7 @@ description: Cypress preparation steps
|
||||
|
||||
inputs:
|
||||
working-directory:
|
||||
description: "Optional working directory for bun commands"
|
||||
description: "Optional working directory for yarn commands"
|
||||
required: false
|
||||
default: "."
|
||||
|
||||
@@ -15,27 +15,28 @@ outputs:
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Install Chrome v144
|
||||
uses: browser-actions/setup-chrome@v2
|
||||
- name: Install Chrome v111
|
||||
uses: browser-actions/setup-chrome@v1
|
||||
with:
|
||||
chrome-version: 144
|
||||
# https://chromium.cypress.io/linux/stable/111.0.5563.146
|
||||
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: |
|
||||
bun run cypress install
|
||||
bun run cypress cache path
|
||||
bun run cypress cache list
|
||||
bun run cypress verify
|
||||
yarn cypress install
|
||||
yarn cypress cache path
|
||||
yarn cypress cache list
|
||||
yarn cypress verify
|
||||
shell: bash
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
|
||||
25
.github/actions/prepare-frontend/action.yml
vendored
25
.github/actions/prepare-frontend/action.yml
vendored
@@ -1,5 +1,4 @@
|
||||
name: Prepare front-end environment
|
||||
description: "Prepare the front-end environment for the CI build"
|
||||
inputs:
|
||||
node-version:
|
||||
description: "The version of Node.js to use, overriding .nvmrc"
|
||||
@@ -7,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:
|
||||
@@ -17,12 +16,6 @@ runs:
|
||||
node-version: ${{ inputs.node-version }} # this overrides, if present
|
||||
node-version-file: ".nvmrc"
|
||||
|
||||
- name: Install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: "1.3.7"
|
||||
token: ${{ github.token }}
|
||||
|
||||
- name: Get cache keys
|
||||
uses: ./.github/actions/get-cache-keys
|
||||
id: get-cache-keys
|
||||
@@ -35,30 +28,20 @@ runs:
|
||||
~/.gitlibs
|
||||
key: ${{ steps.get-cache-keys.outputs.m2-cache-key }}
|
||||
|
||||
- name: Get bun cache directory
|
||||
id: bun-cache
|
||||
run: |
|
||||
BUN_CACHE_DIR=$(bun pm cache)
|
||||
echo "Bun cache directory: $BUN_CACHE_DIR"
|
||||
echo "dir=$BUN_CACHE_DIR" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Restore node_modules cache
|
||||
uses: actions/cache/restore@v4
|
||||
id: node-modules-cache
|
||||
with:
|
||||
path: |
|
||||
${{ steps.bun-cache.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)
|
||||
# If a job wants to patch packages, it does so explicitly in the CI. This setting is opt-out.
|
||||
# For example, we never want to cache node_modules with patches already applied so cache-generator.yml skips that step.
|
||||
- name: Clean install
|
||||
run: bun install --frozen-lockfile
|
||||
run: yarn install --frozen-lockfile --prefer-offline
|
||||
shell: bash
|
||||
- name: Patch frontend dependencies
|
||||
if: inputs.apply-patches == 'true'
|
||||
run: bun run patch-package
|
||||
run: yarn patch-package
|
||||
shell: bash
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
name: Prepare Release Scripts
|
||||
description: Set up bun and build release scripts
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: "1.3.7"
|
||||
token: ${{ github.token }}
|
||||
|
||||
- name: Build release scripts
|
||||
shell: bash
|
||||
run: cd ${{ github.workspace }}/release && bun install --frozen-lockfile && bun run build
|
||||
132
.github/actions/prepare-shoppy-e2e/action.yml
vendored
132
.github/actions/prepare-shoppy-e2e/action.yml
vendored
@@ -1,132 +0,0 @@
|
||||
name: Prepare Shoppy E2E environment
|
||||
description: Shared setup steps for Shoppy E2E tests (regular and synthetic)
|
||||
|
||||
inputs:
|
||||
commit:
|
||||
description: "Commit SHA for artifact retrieval"
|
||||
required: true
|
||||
sample-app-branch-name:
|
||||
description: "Branch name for the sample app"
|
||||
required: true
|
||||
sample-app-environment:
|
||||
description: "Environment (production/development)"
|
||||
required: true
|
||||
staging-mb-all-features-token:
|
||||
description: "Staging MB all features token"
|
||||
required: true
|
||||
shoppy-iam-role:
|
||||
description: "Shoppy IAM role for AWS credentials"
|
||||
required: true
|
||||
shoppy-metabase-app-db-s3-bucket:
|
||||
description: "S3 bucket for Shoppy app DB dump"
|
||||
required: true
|
||||
shoppy-metabase-app-db-s3-key:
|
||||
description: "S3 key for Shoppy app DB dump"
|
||||
required: true
|
||||
shoppy-metabase-app-db-user:
|
||||
description: "Shoppy Metabase app DB user"
|
||||
required: true
|
||||
shoppy-metabase-app-db-database:
|
||||
description: "Shoppy Metabase app DB database"
|
||||
required: true
|
||||
metastore-dev-server-url:
|
||||
description: "Metastore dev server URL"
|
||||
required: true
|
||||
shoppy-datadog-application-id:
|
||||
description: "Shoppy Datadog application ID"
|
||||
required: true
|
||||
shoppy-datadog-client-token:
|
||||
description: "Shoppy Datadog client token"
|
||||
required: true
|
||||
|
||||
outputs:
|
||||
chrome-path:
|
||||
description: "Path to Chrome executable"
|
||||
value: ${{ steps.cypress-prep.outputs.chrome-path }}
|
||||
sample_app_branch_exists:
|
||||
description: "Whether the sample app branch exists"
|
||||
value: ${{ steps.check-sample-app-branch.outputs.sample_app_branch_exists }}
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: ./.github/actions/maximize-disk-space
|
||||
|
||||
# We have to use the latest version that does not have this issue https://github.com/docker/compose/pull/12752
|
||||
- name: Set up Docker Compose
|
||||
uses: docker/setup-compose-action@v1
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- name: Retrieve uberjar artifact for ee
|
||||
uses: ./.github/actions/fetch-artifact
|
||||
with:
|
||||
commit: ${{ inputs.commit }}
|
||||
edition: ee
|
||||
extract-path: "target/uberjar"
|
||||
output-name: "metabase.jar"
|
||||
|
||||
- name: Retrieve Embedding SDK dist artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: embedding-sdk-${{ inputs.commit }}
|
||||
path: ${{ github.workspace }}/resources/embedding-sdk
|
||||
|
||||
- name: Prepare front-end environment
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
|
||||
- name: Prepare Cypress environment
|
||||
id: cypress-prep
|
||||
uses: ./.github/actions/prepare-cypress
|
||||
|
||||
- name: Check if sample app branch exists
|
||||
id: check-sample-app-branch
|
||||
shell: bash
|
||||
env:
|
||||
SAMPLE_APP_BRANCH_NAME: ${{ inputs.sample-app-branch-name }}
|
||||
run: |
|
||||
REPO_URL="https://github.com/metabase/shoppy.git"
|
||||
echo "Checking if branch '${SAMPLE_APP_BRANCH_NAME}' exists in ${REPO_URL}..."
|
||||
if git ls-remote --exit-code --heads "$REPO_URL" "${SAMPLE_APP_BRANCH_NAME}"; then
|
||||
echo "sample_app_branch_exists=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "sample_app_branch_exists=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Configure AWS credentials
|
||||
if: ${{ steps.check-sample-app-branch.outputs.sample_app_branch_exists == 'true' }}
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
role-to-assume: ${{ inputs.shoppy-iam-role }}
|
||||
role-session-name: GitHub_to_AWS_via_FederatedOIDC
|
||||
aws-region: us-east-1
|
||||
|
||||
- name: Download Shoppy's App DB PostgreSQL dump from S3
|
||||
if: ${{ steps.check-sample-app-branch.outputs.sample_app_branch_exists == 'true' }}
|
||||
shell: bash
|
||||
run: |
|
||||
aws s3 cp s3://${{ inputs.shoppy-metabase-app-db-s3-bucket }}/${{ inputs.shoppy-metabase-app-db-s3-key }} ./e2e/tmp/db_dumps/shoppy_metabase_app_db_dump.sql
|
||||
|
||||
- name: Prepare and launch Shoppy containers
|
||||
if: ${{ steps.check-sample-app-branch.outputs.sample_app_branch_exists == 'true' }}
|
||||
shell: bash
|
||||
env:
|
||||
SAMPLE_APP_BRANCH_NAME: ${{ inputs.sample-app-branch-name }}
|
||||
SAMPLE_APP_ENVIRONMENT: ${{ inputs.sample-app-environment }}
|
||||
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ inputs.staging-mb-all-features-token }}
|
||||
METASTORE_DEV_SERVER_URL: ${{ inputs.metastore-dev-server-url }}
|
||||
METABASE_APP_DB_USER: ${{ inputs.shoppy-metabase-app-db-user }}
|
||||
METABASE_APP_DB: ${{ inputs.shoppy-metabase-app-db-database }}
|
||||
SHOPPY_DATADOG_APPLICATION_ID: ${{ inputs.shoppy-datadog-application-id }}
|
||||
SHOPPY_DATADOG_CLIENT_TOKEN: ${{ inputs.shoppy-datadog-client-token }}
|
||||
SHOPPY_DATADOG_SITE: "us5.datadoghq.com"
|
||||
SHOPPY_DATADOG_ENV: "staging"
|
||||
run: npx tsx ./e2e/runner/embedding-sdk/sample-apps/start-ci.ts shoppy-e2e
|
||||
|
||||
- name: Prune build cache
|
||||
shell: bash
|
||||
run: docker builder prune -af
|
||||
|
||||
- name: Log free disk space
|
||||
shell: bash
|
||||
run: df -h /
|
||||
@@ -2,10 +2,9 @@ name: Prepare Uberjar Artifact
|
||||
description: Prepare and upload Metabase JAR as a GitHub artifact.
|
||||
|
||||
inputs:
|
||||
edition:
|
||||
required: false
|
||||
default: "ee"
|
||||
description: Metabase edition (ee or oss)
|
||||
name:
|
||||
required: true
|
||||
description: Artifact name
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
@@ -19,7 +18,7 @@ runs:
|
||||
- name: Upload JARs as artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: metabase-${{ inputs.edition }}-${{ github.event.pull_request.head.sha || github.sha }}-uberjar
|
||||
name: ${{ inputs.name }}
|
||||
path: |
|
||||
./target/uberjar/metabase.jar
|
||||
./COMMIT-ID
|
||||
|
||||
77
.github/actions/sdk-bundle-size-check/action.yml
vendored
77
.github/actions/sdk-bundle-size-check/action.yml
vendored
@@ -1,77 +0,0 @@
|
||||
name: Embedding SDK Bundle Size Check
|
||||
description: |
|
||||
Downloads the current branch's uberjar and builds the base ref's
|
||||
embedding-sdk.js from source, compares their sizes,
|
||||
and sets output if the size change exceeds the threshold.
|
||||
|
||||
inputs:
|
||||
edition:
|
||||
description: 'Metabase edition (ee or oss)'
|
||||
required: true
|
||||
base-ref-commit:
|
||||
description: 'Commit SHA of the base ref (e.g. merge base).'
|
||||
required: true
|
||||
threshold-percent:
|
||||
description: 'Percent threshold for bundle size change'
|
||||
required: true
|
||||
|
||||
outputs:
|
||||
status:
|
||||
description: "Bundle size status: increased, decreased, or stable"
|
||||
value: ${{ steps.compare.outputs.status }}
|
||||
size_change_percent:
|
||||
description: "size change percent (positive = increase, negative = decrease)"
|
||||
value: ${{ steps.compare.outputs.size_change_percent }}
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Prepare backend
|
||||
uses: ./.github/actions/prepare-backend
|
||||
- name: Prepare frontend
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
- name: Download current uberjar
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: metabase-${{ inputs.edition }}-${{ github.event.pull_request.head.sha || github.sha }}-uberjar
|
||||
path: temp/current-uberjar
|
||||
- name: Extract embedding-sdk.js from current uberjar
|
||||
shell: bash
|
||||
run: unzip -j temp/current-uberjar/target/uberjar/metabase.jar frontend_client/app/embedding-sdk.js -d temp/current-bundle
|
||||
- name: Checkout base ref
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.base-ref-commit }}
|
||||
path: temp/base-ref-src
|
||||
- name: Build base-ref embedding-sdk.js
|
||||
shell: bash
|
||||
run: |
|
||||
cd temp/base-ref-src
|
||||
bun install
|
||||
MB_EDITION=${{ inputs.edition }} bun run build-release
|
||||
- name: Compare sizes and set output
|
||||
id: compare
|
||||
shell: bash
|
||||
run: |
|
||||
current_size=$(wc -c < temp/current-bundle/embedding-sdk.js) || { echo "::error::Failed to read current bundle"; exit 1; }
|
||||
base_ref_size=$(wc -c < temp/base-ref-src/resources/frontend_client/app/embedding-sdk.js) || { echo "::error::Failed to read base-ref bundle"; exit 1; }
|
||||
[ "$current_size" -gt 0 ] && [ "$base_ref_size" -gt 0 ] || { echo "::error::Bundle file is empty"; exit 1; }
|
||||
|
||||
diff=$((current_size - base_ref_size))
|
||||
percent=$(( diff * 100 / base_ref_size ))
|
||||
|
||||
echo "Current size: $current_size bytes"
|
||||
echo "Base ref size: $base_ref_size bytes"
|
||||
echo "Difference: $diff bytes ($percent%)"
|
||||
|
||||
threshold="${{ inputs.threshold-percent }}"
|
||||
if [ "$percent" -gt "$threshold" ]; then
|
||||
status="increased"
|
||||
elif [ "$percent" -lt "$(( -threshold ))" ]; then
|
||||
status="decreased"
|
||||
else
|
||||
status="stable"
|
||||
fi
|
||||
|
||||
echo "status=$status" >> $GITHUB_OUTPUT
|
||||
echo "size_change_percent=$percent" >> $GITHUB_OUTPUT
|
||||
24
.github/actions/test-driver/action.yml
vendored
24
.github/actions/test-driver/action.yml
vendored
@@ -9,18 +9,6 @@ inputs:
|
||||
build-static-viz:
|
||||
required: true
|
||||
default: false
|
||||
logs-artifact-suffix:
|
||||
required: false
|
||||
description: Suffix to append to the test logs artifact name to avoid conflicts in matrix jobs
|
||||
edition:
|
||||
required: false
|
||||
default: 'ee'
|
||||
description: Edition to test (oss or ee)
|
||||
|
||||
outputs:
|
||||
test-outcome:
|
||||
description: "The outcome of the test run (success, failure, cancelled, skipped)"
|
||||
value: ${{ steps.run-test.outcome }}
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
@@ -28,16 +16,16 @@ runs:
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
- name: Prepare front-end environment
|
||||
if: ${{ inputs.build-static-viz == 'true' }}
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
- name: Build static viz frontend
|
||||
if: ${{ inputs.build-static-viz == 'true' }}
|
||||
run: bun run build-static-viz
|
||||
- name: Build static viz frontend
|
||||
run: yarn build-static-viz
|
||||
shell: bash
|
||||
if: ${{ inputs.build-static-viz == 'true' }}
|
||||
env:
|
||||
MB_EDITION: ee
|
||||
- name: Test database driver
|
||||
run: clojure -X:dev:ci:${{ inputs.edition }}:${{ inputs.edition }}-dev:drivers:drivers-dev:test ${{ inputs.test-args }}
|
||||
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!
|
||||
@@ -49,7 +37,7 @@ runs:
|
||||
# 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' }}
|
||||
if: steps.run-test.outcome != 'success'
|
||||
with:
|
||||
path: "target/junit/**/*_test.xml"
|
||||
name: JUnit Test Report ${{ inputs.junit-name }}
|
||||
@@ -61,7 +49,7 @@ runs:
|
||||
if: always() && steps.run-test.outcome != 'success'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ format('test-logs-{0}{1}', github.job, inputs.logs-artifact-suffix && format('-{0}', inputs.logs-artifact-suffix) || '') }}
|
||||
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.
|
||||
|
||||
@@ -20,9 +20,7 @@ inputs:
|
||||
required: true
|
||||
variant:
|
||||
required: false
|
||||
default: ""
|
||||
previous-step-outcome:
|
||||
required: true
|
||||
default: ''
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
@@ -58,5 +56,3 @@ runs:
|
||||
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 }}
|
||||
use-cache: true
|
||||
previous-step-outcome: ${{ inputs.previous-step-outcome }}
|
||||
|
||||
75
.github/copilot-instructions.md
vendored
75
.github/copilot-instructions.md
vendored
@@ -1,55 +1,52 @@
|
||||
# Metabase Developer Assistant Instructions
|
||||
|
||||
For detailed coding standards and conventions, see `CLAUDE.md` and the skills in `.claude/skills/`.
|
||||
## Frontend Development
|
||||
|
||||
## Code Review Standards
|
||||
### Repository Structure
|
||||
|
||||
Review focus areas: Security → Performance → Testing → Documentation (in priority order)
|
||||
```
|
||||
frontend/src/
|
||||
├── metabase/ # Main application components and pages
|
||||
├── metabase-lib/ # Query building and data modeling utilities
|
||||
├── metabase-types/ # TypeScript type definitions
|
||||
└── metabase/ui/ # Design system components
|
||||
|
||||
When reviewing code, use these emoji prefixes to categorize feedback:
|
||||
- 🔒 Security concerns
|
||||
- ⚡ Performance opportunities
|
||||
- 🧹 Cleanup needs
|
||||
- 📚 Documentation gaps
|
||||
- 🚨 Blocking issues
|
||||
- 💭 Clarification questions
|
||||
e2e/ # Cypress end-to-end tests
|
||||
frontend/test/ # Jest unit tests
|
||||
```
|
||||
|
||||
### Security Critical Issues
|
||||
### Technology Stack
|
||||
|
||||
- Check for hardcoded secrets, API keys, or credentials
|
||||
- Look for SQL injection and XSS vulnerabilities
|
||||
- Verify proper input validation and sanitization
|
||||
- Review authentication and authorization logic
|
||||
- Ensure enterprise features use the plugin system (no enterprise code in OSS)
|
||||
- **Framework**: React 18 with TypeScript
|
||||
- **State**: Redux Toolkit
|
||||
- **Styling**: CSS Modules (preferred) > Emotion styled-components
|
||||
- **UI**: `metabase/ui` components built with Mantine v8
|
||||
- **Build**: Rspack (primary), Webpack (legacy)
|
||||
- **Testing**: Jest + React Testing Library, Cypress
|
||||
|
||||
### Performance Red Flags
|
||||
### Coding Standards
|
||||
|
||||
- Identify N+1 database query problems
|
||||
- Spot inefficient loops and algorithmic issues
|
||||
- Check for memory leaks and resource cleanup
|
||||
- Review caching opportunities for expensive operations
|
||||
#### Component Preferences
|
||||
|
||||
### Code Quality Essentials
|
||||
- Prefer `metabase/ui`components over `metabase/common/components`
|
||||
- Use `.tsx` for components, `.ts` for utilities
|
||||
|
||||
- Functions should be focused and appropriately sized
|
||||
- Use clear, descriptive naming conventions
|
||||
- Ensure proper error handling throughout
|
||||
- Remove dead code and unused imports
|
||||
#### Styling
|
||||
|
||||
### Review Style
|
||||
ALWAYS prefer Mantine style props,then CSS modules. DO NOT suggest styled components, they are deprecated.
|
||||
|
||||
- Only flag actual issues worth mentioning
|
||||
- Do not post "looks good" comments or congratulate following conventions
|
||||
- Be specific and actionable in feedback
|
||||
- Explain the "why" behind recommendations
|
||||
- Ask clarifying questions when code intent is unclear
|
||||
#### TypeScript Migration
|
||||
|
||||
### Review Format
|
||||
When heavily editing `.js`/`.jsx` files, create a separate PR to convert to TypeScript first, then implement changes.
|
||||
|
||||
- Condense feedback into as few comments as possible
|
||||
- Prefer a single summary comment over dozens of inline comments
|
||||
- Only use inline comments for critical issues that require immediate attention
|
||||
- For non-critical issues, link to the specific line from the summary instead
|
||||
- Delete all stale comments and summaries on each new push or CI run
|
||||
#### Enterprise Features
|
||||
|
||||
Language-specific rules are in `.github/instructions/*.instructions.md`.
|
||||
Enterprise functionality MUST use the plugin system. It is very important to not expose enterprise code in the OSS version.
|
||||
|
||||
#### Testing Requirements
|
||||
|
||||
All PRs should include tests. Prefer Unit tests over E2E tests.
|
||||
|
||||
#### Localization
|
||||
|
||||
All user-facing strings MUST be localized using the ttag library. Localized strings should be complete phrases, do not concatenate a few separately localized strings. You should add context to strings where the meaning of the string might not be obvious in isolation: e.g. "Home" might have different words in some languages depending on whether you're talking about a dwelling or the landing page for a website.
|
||||
|
||||
66
.github/file-paths.yaml
vendored
66
.github/file-paths.yaml
vendored
@@ -11,22 +11,18 @@ shared_specs: &shared_specs
|
||||
- "test/**/*.cljc"
|
||||
- "test/**/*.cljs"
|
||||
|
||||
shared_ci: &shared_ci
|
||||
frontend_ci: &frontend_ci
|
||||
- ".github/actions/prepare-frontend/**"
|
||||
- ".github/actions/prepare-backend/**"
|
||||
- ".github/workflows/run-tests.yml"
|
||||
|
||||
shared_e2e: &shared_e2e
|
||||
- "e2e/runner/**"
|
||||
- "e2e/snapshot-creators/**"
|
||||
- "e2e/support/**"
|
||||
|
||||
frontend_ci: &frontend_ci
|
||||
- *shared_ci
|
||||
- ".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
|
||||
- "bun.lock"
|
||||
- "yarn.lock"
|
||||
- "**/tsconfig*.json"
|
||||
- "package.json"
|
||||
- "babel.config.json"
|
||||
@@ -45,12 +41,14 @@ frontend_sources: &frontend_sources
|
||||
- "enterprise/frontend/!(test)**/**/{!(*.spec),}.{js,jsx,ts,tsx,css}"
|
||||
- "frontend/!(test)**/**/{!(*.spec),}.{js,jsx,ts,tsx,css}"
|
||||
|
||||
# Used only in .github/workflows/conventional-commit-pr-title-reminder.yml
|
||||
frontend_embedding_sources:
|
||||
- "enterprise/frontend/src/{embedding,embedding-*}/**/{!(*.spec),}.{js,jsx,ts,tsx,css}"
|
||||
- "enterprise/frontend/src/metabase-enterprise/{embedding,embedding-*}**/{!(*.spec),}.{js,jsx,ts,tsx,css}"
|
||||
|
||||
# Used only in .github/workflows/embedding-sdk-labels-reminder.yml
|
||||
frontend_embedding_sdk_sources:
|
||||
- *frontend_configs
|
||||
- "enterprise/frontend/!(test)**/**/{!(*.spec),}.{js,jsx,ts,tsx,css}"
|
||||
|
||||
frontend_embedding_sdk_package_sources:
|
||||
- "enterprise/frontend/src/embedding-sdk-package/**/!(*.spec).{js,jsx,ts,tsx,css}"
|
||||
|
||||
@@ -78,16 +76,15 @@ frontend_loki_ci: &frontend_loki_ci
|
||||
- ".storybook/**"
|
||||
|
||||
backend_ci: &backend_ci
|
||||
- *shared_ci
|
||||
- ".github/actions/prepare-frontend/**"
|
||||
- ".github/actions/prepare-backend/**"
|
||||
- ".github/actions/test-driver/**"
|
||||
- ".github/workflows/backend.yml"
|
||||
- ".github/workflows/drivers.yml"
|
||||
|
||||
backend_presto_kerberos:
|
||||
- "**/presto_jdbc/**"
|
||||
- "**/presto-jdbc/**" # the driver module has a hyphen. underscores are classpaths
|
||||
- "**/presto_jdbc.clj"
|
||||
- "**/presto_jdbc_test.clj"
|
||||
- ".github/workflows/presto-kerberos-integration-test.yml"
|
||||
|
||||
backend_sources: &backend_sources
|
||||
@@ -120,9 +117,9 @@ sources: &sources
|
||||
- *backend_sources
|
||||
|
||||
e2e_ci: &e2e_ci
|
||||
- *shared_ci
|
||||
- ".github/actions/prepare-frontend/**"
|
||||
- ".github/actions/prepare-backend/**"
|
||||
- ".github/actions/prepare-uberjar-artifact/**"
|
||||
- ".github/actions/fetch-artifact/action.yml"
|
||||
- ".github/actions/e2e-prepare-containers/**"
|
||||
- ".github/actions/prepare-cypress/**"
|
||||
- ".github/workflows/e2e-*"
|
||||
@@ -130,27 +127,18 @@ e2e_ci: &e2e_ci
|
||||
e2e_specs: &e2e_specs
|
||||
- e2e/!(test-host-app)**/*.cy.*.{js,ts}
|
||||
|
||||
embedding_sdk_ci: &embedding_sdk_ci
|
||||
- *shared_ci
|
||||
- ".github/actions/prepare-uberjar-artifact/**"
|
||||
- ".github/actions/fetch-artifact/action.yml"
|
||||
- ".github/actions/prepare-cypress/**"
|
||||
- ".github/workflows/embedding-sdk*.yml"
|
||||
|
||||
embedding_sdk_components:
|
||||
- *default
|
||||
- *embedding_sdk_ci
|
||||
e2e_embedding_sdk:
|
||||
- *frontend_embedding_sdk_ci
|
||||
- *sources
|
||||
- *shared_e2e
|
||||
- "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
|
||||
- *shared_e2e
|
||||
- "e2e/embedding-sdk-host-apps/**"
|
||||
- "e2e/runner/embedding-sdk/**"
|
||||
- "e2e/test-host-app/**/*.cy.*.{js,ts}"
|
||||
|
||||
snowplow: &snowplow
|
||||
@@ -199,15 +187,3 @@ cljs:
|
||||
- *shared_specs
|
||||
- ".github/workflows/cljs.yml"
|
||||
- "shadow-cljs.edn"
|
||||
|
||||
openapi:
|
||||
- ".github/workflows/openapi.yml"
|
||||
- "src/metabase/api/**"
|
||||
- "src/metabase/api_routes/**"
|
||||
- "src/metabase/*_rest/api.clj"
|
||||
- "src/metabase/*/api.clj"
|
||||
- "src/metabase/*/api/*.clj"
|
||||
- "enterprise/backend/src/metabase_enterprise/**/api.clj"
|
||||
- "enterprise/backend/src/metabase_enterprise/**/api/*.clj"
|
||||
- "resources/openapi/**"
|
||||
- "package.json"
|
||||
|
||||
9
.github/instructions/clojure.instructions.md
vendored
9
.github/instructions/clojure.instructions.md
vendored
@@ -1,9 +0,0 @@
|
||||
---
|
||||
applyTo: "**/*.{clj,cljs,cljc}"
|
||||
---
|
||||
|
||||
# Clojure/ClojureScript Code Review
|
||||
|
||||
When reviewing Clojure or ClojureScript code, apply the guidelines in these files:
|
||||
- `.claude/skills/_shared/clojure-style-guide.md`
|
||||
- `.claude/skills/clojure-review/SKILL.md`
|
||||
9
.github/instructions/docs.instructions.md
vendored
9
.github/instructions/docs.instructions.md
vendored
@@ -1,9 +0,0 @@
|
||||
---
|
||||
applyTo: "docs/**/*.md"
|
||||
---
|
||||
|
||||
# Documentation Review
|
||||
|
||||
When reviewing documentation, apply the guidelines in these files:
|
||||
- `.claude/skills/_shared/metabase-style-guide.md`
|
||||
- `.claude/skills/docs-review/SKILL.md`
|
||||
11
.github/instructions/typescript.instructions.md
vendored
11
.github/instructions/typescript.instructions.md
vendored
@@ -1,11 +0,0 @@
|
||||
---
|
||||
applyTo: "**/*.{ts,tsx,js,jsx}"
|
||||
---
|
||||
|
||||
# TypeScript/JavaScript Code Review
|
||||
|
||||
When reviewing TypeScript or JavaScript code, apply the guidelines in these files:
|
||||
|
||||
- `.claude/skills/typescript-review/SKILL.md`
|
||||
- `docs/developers-guide/frontend.md`
|
||||
- `frontend/CLAUDE.md`
|
||||
41
.github/scripts/csv-to-mb.js
vendored
41
.github/scripts/csv-to-mb.js
vendored
@@ -1,41 +0,0 @@
|
||||
/**
|
||||
* jsonData must be in the format:
|
||||
* [
|
||||
* { column1: value1, column2: value2, ... },
|
||||
* { column1: value3, column2: value4, ... },
|
||||
* ...
|
||||
* ]
|
||||
*/
|
||||
function jsonToCsvFormData(jsonData) {
|
||||
const formData = new FormData();
|
||||
const csvHeader = Object.keys(jsonData[0]).join(',') + '\n';
|
||||
const csvContent = jsonData.map(row =>
|
||||
Object.values(row).map(value => `"${String(value).replace(/"/g, '""')}"`).join(',')
|
||||
).join('\n');
|
||||
|
||||
const blob = new Blob([csvHeader + csvContent], { type: 'text/csv' });
|
||||
formData.append('file', blob, 'data.csv');
|
||||
|
||||
return formData;
|
||||
}
|
||||
|
||||
function uploadCsvToMb({ baseUrl, tableId, jsonData, mode = 'append' }) {
|
||||
const formData = jsonToCsvFormData(jsonData);
|
||||
const operation = mode === 'replace' ? 'replace-csv' : 'append-csv';
|
||||
|
||||
return fetch(`${baseUrl}/api/table/${tableId}/${operation}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
"x-api-key": process.env.API_KEY
|
||||
},
|
||||
body: formData
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Upload failed`);
|
||||
}
|
||||
return { success: true };
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { uploadCsvToMb };
|
||||
40
.github/scripts/find-linear-issue.js
vendored
40
.github/scripts/find-linear-issue.js
vendored
@@ -1,40 +0,0 @@
|
||||
async function findLinearIssue({ githubIssueUrl, linearApiKey }) {
|
||||
const response = await fetch("https://api.linear.app/graphql", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: linearApiKey,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query: `
|
||||
query {
|
||||
attachmentsForURL(url: "${githubIssueUrl}") {
|
||||
nodes {
|
||||
issue { identifier }
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
return { success: false, message: `Linear API error: ${response.status}` };
|
||||
}
|
||||
|
||||
if (!data.data?.attachmentsForURL?.nodes?.length) {
|
||||
return {
|
||||
success: false,
|
||||
message: `No Linear issue linked to ${githubIssueUrl}`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
issue: data.data.attachmentsForURL.nodes[0].issue.identifier,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { findLinearIssue };
|
||||
32
.github/team.json
vendored
32
.github/team.json
vendored
@@ -10,7 +10,14 @@
|
||||
"metamben",
|
||||
"wzimrin",
|
||||
"ericnormand",
|
||||
"galdre",
|
||||
"galdre"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Drivers",
|
||||
"label": ".Team/Drivers",
|
||||
"members": [
|
||||
"snoe",
|
||||
"rileythomp"
|
||||
]
|
||||
},
|
||||
@@ -43,7 +50,7 @@
|
||||
"nemanjaglumac"
|
||||
]
|
||||
},
|
||||
{
|
||||
{
|
||||
"name": "Metabot",
|
||||
"label": ".Team/Metabot",
|
||||
"members": [
|
||||
@@ -55,8 +62,7 @@
|
||||
"tsplude",
|
||||
"lbrdnk",
|
||||
"piranha",
|
||||
"crisptrutski",
|
||||
"undrfined"
|
||||
"crisptrutski"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -80,12 +86,13 @@
|
||||
"eliotmetabase",
|
||||
"npfitz",
|
||||
"johnswanson",
|
||||
"iethree",
|
||||
"nvoxland",
|
||||
"edpaget",
|
||||
"deniskaber",
|
||||
"alxnddr",
|
||||
"andsalves",
|
||||
"kevinkelleher12"
|
||||
"bpander",
|
||||
"andsalves"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -99,18 +106,7 @@
|
||||
"chodorowicz",
|
||||
"kamilmielnik",
|
||||
"stasgavrylov",
|
||||
"uladzimirdev",
|
||||
"egorgrushin"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Growth Eng",
|
||||
"label": ".Team/GrowthEng",
|
||||
"members": [
|
||||
"iethree",
|
||||
"bpander",
|
||||
"gantoreno",
|
||||
"losrebellos"
|
||||
"uladzimirdev"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
24
.github/workflows/app-db.yml
vendored
24
.github/workflows/app-db.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
be-tests-mariadb:
|
||||
if: ${{ !inputs.skip }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 40
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -68,8 +68,8 @@ jobs:
|
||||
(
|
||||
github.event_name == 'push' &&
|
||||
(
|
||||
github.ref_name == 'master' ||
|
||||
startsWith(github.ref_name, 'release-')
|
||||
github.ref == 'master' ||
|
||||
startsWith(github.ref, 'release-')
|
||||
) &&
|
||||
''
|
||||
) ||
|
||||
@@ -105,7 +105,6 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test ${{ matrix.version.name }}
|
||||
id: test-driver
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
build-static-viz: ${{ matrix.job.build-static-viz }}
|
||||
@@ -124,12 +123,11 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
|
||||
|
||||
be-tests-mysql:
|
||||
if: ${{ !inputs.skip }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 40
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -184,8 +182,8 @@ jobs:
|
||||
(
|
||||
github.event_name == 'push' &&
|
||||
(
|
||||
github.ref_name == 'master' ||
|
||||
startsWith(github.ref_name, 'release-')
|
||||
github.ref == 'master' ||
|
||||
startsWith(github.ref, 'release-')
|
||||
) &&
|
||||
''
|
||||
) ||
|
||||
@@ -229,7 +227,6 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test ${{ matrix.version.name }}
|
||||
id: test-driver
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
build-static-viz: ${{ matrix.job.build-static-viz }}
|
||||
@@ -248,12 +245,11 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
|
||||
|
||||
be-tests-postgres:
|
||||
if: ${{ !inputs.skip }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 40
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -301,8 +297,8 @@ jobs:
|
||||
(
|
||||
github.event_name == 'push' &&
|
||||
(
|
||||
github.ref_name == 'master' ||
|
||||
startsWith(github.ref_name, 'release-')
|
||||
github.ref == 'master' ||
|
||||
startsWith(github.ref, 'release-')
|
||||
) &&
|
||||
''
|
||||
) ||
|
||||
@@ -340,7 +336,6 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test ${{ matrix.version.name }} (${{ matrix.job.name }})
|
||||
id: test-driver
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
build-static-viz: ${{ matrix.job.build-static-viz }}
|
||||
@@ -359,7 +354,6 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
|
||||
|
||||
app-db-tests-result:
|
||||
needs:
|
||||
|
||||
176
.github/workflows/backend-cloverage.yml
vendored
176
.github/workflows/backend-cloverage.yml
vendored
@@ -1,33 +1,12 @@
|
||||
name: Backend-Cloverage
|
||||
|
||||
# Module lists are generated dynamically by listing source directories.
|
||||
# The setup-oss and setup-ee jobs list folders in src/metabase/ and
|
||||
# enterprise/backend/src/metabase_enterprise/ respectively, convert
|
||||
# underscores to dashes (for Clojure namespace convention), and output
|
||||
# as JSON arrays for the matrix jobs to consume.
|
||||
#
|
||||
# jq flags: -R (raw string input), -s (slurp all lines into one string),
|
||||
# -c (compact single-line output for GitHub Actions).
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 10 * * 1-5' # every weekday at 5am US eastern time
|
||||
|
||||
jobs:
|
||||
setup-oss:
|
||||
runs-on: ubuntu-22.04
|
||||
outputs:
|
||||
modules: ${{ steps.get-modules.outputs.modules }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- id: get-modules
|
||||
run: |
|
||||
modules=$(ls -1d src/metabase/*/ | xargs -n1 basename | tr '_' '-' | jq -R -s -c 'split("\n") | map(select(length > 0))')
|
||||
echo "modules=$modules" >> $GITHUB_OUTPUT
|
||||
|
||||
be-linter-cloverage:
|
||||
needs: setup-oss
|
||||
# don't run this workflow on a cron for forks
|
||||
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
|
||||
runs-on: ubuntu-22.04
|
||||
@@ -35,7 +14,96 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
modules: ${{ fromJson(needs.setup-oss.outputs.modules) }}
|
||||
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
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Prepare front-end environment
|
||||
@@ -44,7 +112,7 @@ jobs:
|
||||
uses: ./.github/actions/prepare-backend
|
||||
|
||||
- name: Build static viz frontend
|
||||
run: bun run build-static-viz
|
||||
run: yarn build-static-viz
|
||||
env:
|
||||
MB_EDITION: ee
|
||||
- name: Collect the test coverage
|
||||
@@ -62,19 +130,7 @@ jobs:
|
||||
files: ./target/coverage/codecov.json
|
||||
flags: back-end
|
||||
|
||||
setup-ee:
|
||||
runs-on: ubuntu-22.04
|
||||
outputs:
|
||||
modules: ${{ steps.get-modules.outputs.modules }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- id: get-modules
|
||||
run: |
|
||||
modules=$(ls -1d enterprise/backend/src/metabase_enterprise/*/ | xargs -n1 basename | tr '_' '-' | jq -R -s -c 'split("\n") | map(select(length > 0))')
|
||||
echo "modules=$modules" >> $GITHUB_OUTPUT
|
||||
|
||||
be-linter-cloverage-enterprise:
|
||||
needs: setup-ee
|
||||
# don't run this workflow on a cron for forks
|
||||
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
|
||||
runs-on: ubuntu-22.04
|
||||
@@ -82,7 +138,53 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
modules: ${{ fromJson(needs.setup-ee.outputs.modules) }}
|
||||
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
|
||||
@@ -91,7 +193,7 @@ jobs:
|
||||
uses: ./.github/actions/prepare-backend
|
||||
|
||||
- name: Build static viz frontend
|
||||
run: bun run build-static-viz
|
||||
run: yarn build-static-viz
|
||||
env:
|
||||
MB_EDITION: ee
|
||||
- name: Collect the test coverage
|
||||
|
||||
24
.github/workflows/backend.yml
vendored
24
.github/workflows/backend.yml
vendored
@@ -39,7 +39,7 @@ jobs:
|
||||
be-tests:
|
||||
if: ${{ !inputs.skip }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 40
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -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
|
||||
@@ -88,8 +93,8 @@ jobs:
|
||||
(
|
||||
github.event_name == 'push' &&
|
||||
(
|
||||
github.ref_name == 'master' ||
|
||||
startsWith(github.ref_name, 'release-')
|
||||
github.ref == 'master' ||
|
||||
startsWith(github.ref, 'release-')
|
||||
) &&
|
||||
''
|
||||
) ||
|
||||
@@ -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
|
||||
run: bun run build-static-viz
|
||||
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
|
||||
@@ -130,7 +145,6 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
previous-step-outcome: ${{ steps.run-java-tests.outcome }}
|
||||
|
||||
- name: Publish Test Report (JUnit)
|
||||
uses: dorny/test-reporter@v1
|
||||
|
||||
35
.github/workflows/build-for-release.yml
vendored
35
.github/workflows/build-for-release.yml
vendored
@@ -19,11 +19,9 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: |
|
||||
release
|
||||
.github
|
||||
- name: Prepare release scripts
|
||||
uses: ./.github/actions/prepare-release-scripts
|
||||
sparse-checkout: release
|
||||
- name: Prepare build scripts
|
||||
run: cd ${{ github.workspace }}/release && yarn && yarn build
|
||||
- name: Send build start message
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
@@ -81,11 +79,9 @@ jobs:
|
||||
exit 1
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: |
|
||||
release
|
||||
.github
|
||||
- name: Prepare release scripts
|
||||
uses: ./.github/actions/prepare-release-scripts
|
||||
sparse-checkout: release
|
||||
- name: Prepare build scripts
|
||||
run: cd ${{ github.workspace }}/release && yarn && yarn build
|
||||
- name: Get Release Version
|
||||
uses: actions/github-script@v7
|
||||
id: canonical_version
|
||||
@@ -125,11 +121,9 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # IMPORTANT! to get all the branches
|
||||
sparse-checkout: |
|
||||
release
|
||||
.github
|
||||
- name: Prepare release scripts
|
||||
uses: ./.github/actions/prepare-release-scripts
|
||||
sparse-checkout: release
|
||||
- name: Prepare build scripts
|
||||
run: cd ${{ github.workspace }}/release && yarn && yarn build
|
||||
- name: Get Release Branch
|
||||
uses: actions/github-script@v7
|
||||
id: release_branch
|
||||
@@ -141,13 +135,12 @@ jobs:
|
||||
const releaseBranch = getReleaseBranch(version);
|
||||
return releaseBranch;
|
||||
- name: Ensure that the specified commit exists in the ${{ steps.release_branch.outputs.result }} release branch
|
||||
run: | # bash
|
||||
run: |
|
||||
RELEASE_BRANCH=${{ steps.release_branch.outputs.result }}
|
||||
git checkout $RELEASE_BRANCH
|
||||
git branch --contains ${{ inputs.commit }} | grep -q $RELEASE_BRANCH \
|
||||
&& echo "Commit found in correct release branch" \
|
||||
|| (echo "Commit not found in correct release branch" && exit 1)
|
||||
git checkout -
|
||||
|
||||
get-build-requirements:
|
||||
runs-on: ubuntu-22.04
|
||||
@@ -159,11 +152,9 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: |
|
||||
release
|
||||
.github
|
||||
- name: Prepare release scripts
|
||||
uses: ./.github/actions/prepare-release-scripts
|
||||
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
|
||||
|
||||
69
.github/workflows/cache-generator.yml
vendored
69
.github/workflows/cache-generator.yml
vendored
@@ -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
|
||||
@@ -49,27 +49,21 @@ jobs:
|
||||
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-caches:
|
||||
generate-m2-cache:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 30
|
||||
timeout-minutes: 15
|
||||
needs: get-cache-keys
|
||||
env:
|
||||
M2_CACHE_KEY: ${{ needs.get-cache-keys.outputs.m2-cache-key }}
|
||||
NODE_CACHE_KEY: ${{ needs.get-cache-keys.outputs.node-cache-key }}
|
||||
CYPRESS_CACHE_KEY: ${{ needs.get-cache-keys.outputs.cypress-cache-key }}
|
||||
ESLINT_CACHE_KEY: ${{ needs.get-cache-keys.outputs.eslint-cache-key }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# Backend preparation and cache
|
||||
- name: Prepare backend
|
||||
uses: ./.github/actions/prepare-backend
|
||||
|
||||
# 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
|
||||
@@ -80,9 +74,17 @@ jobs:
|
||||
~/.gitlibs
|
||||
key: ${{ env.M2_CACHE_KEY }}
|
||||
|
||||
# Frontend preparation and caches
|
||||
generate-frontend-caches:
|
||||
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 }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# `prepare-frontend` action (1) prepares Node.js and (2) fetches the node_modules cache if it exists.
|
||||
# It then (3) runs `bun install --frozen-lockfile` which ensures the local cache is fully populated.
|
||||
# It then (3) runs `yarn install --frozen-lockfile --prefer-offline` which ensures the local cache is fully populated.
|
||||
# IMPORTANT: Caching node_modules after patches are already applied will break any PR that attempts to either:
|
||||
# - upgrade a patched library
|
||||
# - change a patch
|
||||
@@ -90,51 +92,16 @@ jobs:
|
||||
- name: Install front-end dependencies
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
with:
|
||||
apply-patches: "false"
|
||||
|
||||
- name: Get bun cache directory
|
||||
id: bun-cache
|
||||
run: |
|
||||
BUN_CACHE_DIR=$(bun pm cache)
|
||||
echo "Bun cache directory: $BUN_CACHE_DIR"
|
||||
echo "dir=$BUN_CACHE_DIR" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Ensure that Cypress executable is ready
|
||||
run: |
|
||||
bun run cypress install
|
||||
bun run cypress cache path
|
||||
bun run cypress cache list
|
||||
bun run cypress verify
|
||||
shell: bash
|
||||
|
||||
# Run Prettier to populate its cache (stored in node_modules/.cache/prettier)
|
||||
# so it gets saved as part of the node_modules cache below
|
||||
- name: Run Prettier to populate cache
|
||||
run: bun run lint-prettier-pure
|
||||
apply-patches: 'false'
|
||||
|
||||
- name: Update node_modules cache
|
||||
uses: ./.github/actions/update-cache
|
||||
with:
|
||||
path: |
|
||||
${{ steps.bun-cache.outputs.dir }}
|
||||
node_modules
|
||||
path: node_modules
|
||||
key: ${{ env.NODE_CACHE_KEY }}
|
||||
|
||||
- name: Update Cypress cache
|
||||
uses: ./.github/actions/update-cache
|
||||
with:
|
||||
path: ~/.cache/Cypress
|
||||
key: ${{ env.CYPRESS_CACHE_KEY }}
|
||||
|
||||
# ESLint cache (requires both backend and frontend)
|
||||
- name: Compile CLJS
|
||||
run: bun run build-pure:cljs
|
||||
|
||||
- name: Run ESLint to populate cache
|
||||
run: bun run lint-eslint-pure
|
||||
|
||||
- name: Update eslint cache
|
||||
uses: ./.github/actions/update-cache
|
||||
with:
|
||||
path: ${{ github.workspace}}/.eslintcache
|
||||
path: .eslintcache
|
||||
key: ${{ env.ESLINT_CACHE_KEY }}
|
||||
|
||||
8
.github/workflows/check-milestone.yml
vendored
8
.github/workflows/check-milestone.yml
vendored
@@ -26,13 +26,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: |
|
||||
release
|
||||
.github
|
||||
sparse-checkout: release
|
||||
fetch-depth: ${{ env.isNewReleaseBranch == 'false' && 3 || 0 }} # we need the full history to get all the commit messages
|
||||
ref: master # we want the logic from master, even though we're triggering on a release branch
|
||||
- name: Prepare release scripts
|
||||
uses: ./.github/actions/prepare-release-scripts
|
||||
- name: Prepare build scripts
|
||||
run: cd ${{ github.workspace }}/release && yarn && yarn build
|
||||
- uses: actions/github-script@v7
|
||||
if: ${{ env.isNewReleaseBranch == 'false' }}
|
||||
name: Set milestone for single commit
|
||||
|
||||
2
.github/workflows/chromatic.yml
vendored
2
.github/workflows/chromatic.yml
vendored
@@ -41,7 +41,7 @@ jobs:
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
- name: Compile CLJS
|
||||
run: NODE_ENV=development bun run build-pure:cljs
|
||||
run: NODE_ENV=development yarn build-pure:cljs
|
||||
- name: Publish to Chromatic
|
||||
uses: chromaui/action@v10.2.0
|
||||
env:
|
||||
|
||||
50
.github/workflows/claude-code-bug-fix.yml
vendored
50
.github/workflows/claude-code-bug-fix.yml
vendored
@@ -70,31 +70,6 @@ jobs:
|
||||
core.setFailed(error.message);
|
||||
}
|
||||
|
||||
- name: Find linked Linear issue
|
||||
id: find_linear
|
||||
continue-on-error: true
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
|
||||
ISSUE_NUMBER: ${{ steps.get_issue.outputs.issue_number }}
|
||||
with:
|
||||
script: |
|
||||
const { findLinearIssue } = require('${{ github.workspace }}/.github/scripts/find-linear-issue.js');
|
||||
|
||||
const githubIssueUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/issues/${process.env.ISSUE_NUMBER}`;
|
||||
|
||||
const result = await findLinearIssue({
|
||||
githubIssueUrl,
|
||||
linearApiKey: process.env.LINEAR_API_KEY
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
console.log(`Found Linear issue: ${result.issue}`);
|
||||
core.setOutput('linear_issue', result.issue);
|
||||
} else {
|
||||
console.log(`Could not find Linear issue: ${result.message}`);
|
||||
}
|
||||
|
||||
- name: Set up git configuration
|
||||
run: |
|
||||
git config --global user.name "Claude Code[bot]"
|
||||
@@ -184,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
|
||||
@@ -230,12 +205,6 @@ jobs:
|
||||
mkdir -p .claude-scratchpad
|
||||
echo "Scratchpad directory created for Claude's temporary verification files"
|
||||
|
||||
- name: Prepare front-end environment
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
|
||||
- name: Generate GitHub App Token for Claude
|
||||
uses: actions/create-github-app-token@v1
|
||||
id: claude-app-token
|
||||
@@ -250,7 +219,7 @@ jobs:
|
||||
github_token: ${{ steps.claude-app-token.outputs.token }}
|
||||
prompt: ${{ env.BUG_FIX_PROMPT }}
|
||||
claude_args: |
|
||||
--allowedTools "Bash(bun run test-unit*),Bash(bun run lint-eslint-pure),Bash(./bin/mage*),Bash(clojure*),Bash(find*),Bash(ls*),Bash(grep*),Write,Read,Glob,Grep,Edit,MultiEdit"
|
||||
--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*)"
|
||||
|
||||
- name: Clean up scratchpad directory
|
||||
@@ -290,7 +259,6 @@ jobs:
|
||||
core.setOutput('reviewers', issueCreator);
|
||||
|
||||
- name: Create Pull Request
|
||||
id: create_pr
|
||||
if: steps.check_changes.outputs.has_changes == 'true'
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
@@ -311,8 +279,6 @@ jobs:
|
||||
body: |
|
||||
This PR automatically fixes the bug reported in #${{ env.ISSUE_NUMBER }}.
|
||||
|
||||
${{ steps.find_linear.outputs.linear_issue && format('Closes {0}', steps.find_linear.outputs.linear_issue) || '' }}
|
||||
|
||||
## Bug Report
|
||||
**Original Issue:** #${{ env.ISSUE_NUMBER }}
|
||||
**Reporter:** @${{ env.ISSUE_AUTHOR }}
|
||||
@@ -333,6 +299,7 @@ jobs:
|
||||
- If issues are found, either fix them or close this PR
|
||||
|
||||
---
|
||||
Closes #${{ env.ISSUE_NUMBER }}
|
||||
|
||||
🤖 Generated with [Claude Code](https://claude.ai/code)
|
||||
|
||||
@@ -342,7 +309,6 @@ jobs:
|
||||
automated-fix
|
||||
ci skip
|
||||
reviewers: ${{ steps.prepare_reviewers.outputs.reviewers }}
|
||||
assignees: ${{ github.actor }}
|
||||
|
||||
- name: Log existing branch update
|
||||
if: steps.check_changes.outputs.has_changes == 'true' && env.BRANCH_EXISTS == 'true'
|
||||
|
||||
101
.github/workflows/claude-code-review.yaml
vendored
Normal file
101
.github/workflows/claude-code-review.yaml
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
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
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
review:
|
||||
if: |
|
||||
(
|
||||
github.event.action == 'closed' &&
|
||||
github.event.pull_request.merged == true
|
||||
) ||
|
||||
contains(github.event.pull_request.labels.*.name, 'Claude Code 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
|
||||
|
||||
# Apparently comments on specific lines and top-level comments about the PR as a whole are TWO TOTALLY DIFFERENT
|
||||
# TYPES OF THINGS. So we have to fetch and delete separately.
|
||||
- name: Delete old Claude PR review comments
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
REPO: ${{ github.repository }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
shell: bash
|
||||
run: |
|
||||
# Get old comment IDs from the bot
|
||||
old_line_comment_ids=$(gh api "/repos/$REPO/pulls/$PR_NUMBER/comments" --jq '.[] | select(.user.login=="metabase-bot[bot]") | .id')
|
||||
echo "Deleting line comments: $old_line_comment_ids"
|
||||
# Delete each comment
|
||||
for id in $old_line_comment_ids; do
|
||||
gh api -X DELETE "/repos/$REPO/pulls/comments/$id"
|
||||
echo "Deleted line comment $id"
|
||||
done
|
||||
# Get old top-level comment IDs (technically "issue" comments on the PR?)
|
||||
old_top_level_comment_ids=$(gh api "/repos/$REPO/issues/$PR_NUMBER/comments" --jq '.[] | select(.user.login=="metabase-bot[bot]") | .id')
|
||||
echo "Deleting top-level comments: $old_top_level_comment_ids"
|
||||
# Delete each comment
|
||||
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
|
||||
|
||||
- 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: Fetch Community Clojure Style Guide
|
||||
run: >-
|
||||
curl https://raw.githubusercontent.com/bbatsov/clojure-style-guide/refs/heads/master/README.adoc -o CLOJURE_STYLE_GUIDE.adoc
|
||||
|
||||
- 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 }}
|
||||
prompt: |
|
||||
REPO: ${{ github.repository }}
|
||||
PR NUMBER: ${{ github.event.pull_request.number }}
|
||||
|
||||
Review this pull request with a focus on:
|
||||
|
||||
- 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 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.
|
||||
|
||||
Use `gh pr comment` for top-level feedback.
|
||||
Use `mcp__github_inline_comment__create_inline_comment` to highlight specific code issues.
|
||||
Only post GitHub comments - don't submit review text as messages.
|
||||
|
||||
claude_args: |
|
||||
--allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),ReadFile(CLOJURE_STYLE_GUIDE.adoc)"
|
||||
33
.github/workflows/clean-cache.yml
vendored
Normal file
33
.github/workflows/clean-cache.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: Cleanup caches by a branch
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- closed
|
||||
|
||||
jobs:
|
||||
cleanup:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Cleanup
|
||||
run: |
|
||||
gh extension install actions/gh-actions-cache
|
||||
|
||||
REPO=${{ github.repository }}
|
||||
BRANCH="refs/pull/${{ github.event.pull_request.number }}/merge"
|
||||
|
||||
echo "Fetching list of cache key"
|
||||
cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH | cut -f 1 )
|
||||
|
||||
## Setting this to not fail the workflow while deleting cache keys.
|
||||
set +e
|
||||
echo "Deleting caches..."
|
||||
for cacheKey in $cacheKeysForPR
|
||||
do
|
||||
gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm
|
||||
done
|
||||
echo "Done"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
2
.github/workflows/cljs.yml
vendored
2
.github/workflows/cljs.yml
vendored
@@ -39,4 +39,4 @@ jobs:
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
- name: Run Cljs tests
|
||||
run: bun run test-cljs
|
||||
run: yarn run test-cljs
|
||||
|
||||
111
.github/workflows/codespell.yml
vendored
111
.github/workflows/codespell.yml
vendored
@@ -1,110 +1,19 @@
|
||||
name: Codespell
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run nightly at 4:00 AM UTC
|
||||
- cron: "0 4 * * *"
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
- 'release-**'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
codespell:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install codespell
|
||||
run: pip install codespell
|
||||
|
||||
- name: Set up git configuration
|
||||
run: |
|
||||
git config --global user.name "Claude Code[bot]"
|
||||
git config --global user.email "noreply@anthropic.com"
|
||||
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Run codespell check
|
||||
id: codespell_check
|
||||
continue-on-error: true
|
||||
run: |
|
||||
set -o pipefail
|
||||
./bin/mage codespell 2>&1 | tee codespell_output.txt
|
||||
|
||||
- name: Generate GitHub App Token for Claude
|
||||
if: steps.codespell_check.outcome == 'failure'
|
||||
uses: actions/create-github-app-token@v1
|
||||
id: claude-app-token
|
||||
- uses: actions/checkout@v3
|
||||
- uses: codespell-project/actions-codespell@v1
|
||||
with:
|
||||
app-id: ${{ secrets.METABASE_BOT_APP_ID }}
|
||||
private-key: ${{ secrets.METABASE_BOT_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Fix spelling errors with Claude Code
|
||||
if: steps.codespell_check.outcome == 'failure'
|
||||
uses: anthropics/claude-code-action@v1
|
||||
with:
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
github_token: ${{ steps.claude-app-token.outputs.token }}
|
||||
prompt: |
|
||||
You are tasked with fixing spelling errors detected by codespell in this repository.
|
||||
|
||||
## Codespell Output
|
||||
The following spelling errors were detected:
|
||||
```
|
||||
$(cat codespell_output.txt)
|
||||
```
|
||||
|
||||
## Instructions
|
||||
1. **Review the codespell output and identify reported spelling errors** - The usual format codespell uses is `typo ==> correction`
|
||||
2. **Fix every spelling error** - Use the Edit tool to fix each misspelling with the suggested correction
|
||||
- If codespell suggests multiple corrections, choose the most appropriate one based on context
|
||||
|
||||
Human reviewers will verify the changes before merging, so fix all errors reported by codespell.
|
||||
claude_args: |
|
||||
--allowedTools "Write,Read,Glob,Grep,Edit,MultiEdit,Bash(cat*),Bash(ls*)"
|
||||
--disallowedTools "Bash(git commit*),Bash(git push*),Bash(gh*)"
|
||||
|
||||
- name: Clean up temporary files
|
||||
if: steps.codespell_check.outcome == 'failure'
|
||||
run: rm -f codespell_output.txt
|
||||
|
||||
- name: Create Pull Request
|
||||
if: steps.codespell_check.outcome == 'failure'
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
branch: claude-fix/codespell-${{ steps.date.outputs.date }}
|
||||
base: master
|
||||
delete-branch: true
|
||||
commit-message: |
|
||||
Fix spelling errors detected by codespell (${{ steps.date.outputs.date }})
|
||||
|
||||
🤖 Generated with [Claude Code](https://claude.ai/code)
|
||||
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>
|
||||
committer: "Claude Code[bot] <noreply@anthropic.com>"
|
||||
author: "Claude Code[bot] <noreply@anthropic.com>"
|
||||
title: "Fix spelling errors detected by codespell (${{ steps.date.outputs.date }})"
|
||||
body: |
|
||||
This PR automatically fixes spelling errors detected by the scheduled codespell check.
|
||||
|
||||
## Automated Fix
|
||||
🤖 This fix was generated automatically by Claude Code based on codespell output.
|
||||
|
||||
**Please review carefully:**
|
||||
- [ ] The spelling fixes are correct
|
||||
- [ ] No code functionality was changed
|
||||
- [ ] Technical terms were preserved correctly
|
||||
|
||||
---
|
||||
|
||||
🤖 Generated with [Claude Code](https://claude.ai/code)
|
||||
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>
|
||||
labels: |
|
||||
automated-fix
|
||||
backport
|
||||
# keep this list in sync with the list in `mage/src/mage/codespell.yml`
|
||||
path: >-
|
||||
docs
|
||||
|
||||
33
.github/workflows/containerize-jar.yml
vendored
33
.github/workflows/containerize-jar.yml
vendored
@@ -4,10 +4,8 @@ run-name: Containerize ${{ inputs.tag }} Uberjar
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
edition:
|
||||
description: Metabase edition (ee or oss)
|
||||
artifact-name:
|
||||
type: string
|
||||
required: true
|
||||
repo:
|
||||
description: owner and repo like "metabase/metabase"
|
||||
type: string
|
||||
@@ -17,18 +15,14 @@ on:
|
||||
type: string
|
||||
release:
|
||||
type: string
|
||||
default: 'false'
|
||||
default: 'true'
|
||||
build-args:
|
||||
type: string
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
edition:
|
||||
description: Metabase edition (ee or oss)
|
||||
type: choice
|
||||
options:
|
||||
- ee
|
||||
- oss
|
||||
required: true
|
||||
artifact-name:
|
||||
description: usually something like "metabase-release-{{ edition }}-{{ hash }}-uberjar"
|
||||
type: string
|
||||
repo:
|
||||
description: docker owner and repo like "metabase/metabase"
|
||||
type: string
|
||||
@@ -38,13 +32,6 @@ on:
|
||||
commit:
|
||||
description: the full commit hash
|
||||
type: string
|
||||
release:
|
||||
description: Whether this is a release artifact (uses metabase-release- prefix)
|
||||
type: choice
|
||||
options:
|
||||
- "false"
|
||||
- "true"
|
||||
default: "false"
|
||||
|
||||
jobs:
|
||||
containerize:
|
||||
@@ -58,14 +45,12 @@ jobs:
|
||||
fetch-depth: 0
|
||||
# we want the dockerfile from the commit we're building
|
||||
- run: git checkout ${{ inputs.commit }} -- bin/docker
|
||||
- name: Retrieve uberjar artifact
|
||||
- name: Retrieve test build uberjar artifact
|
||||
uses: ./.github/actions/fetch-artifact
|
||||
with:
|
||||
commit: ${{ inputs.commit }}
|
||||
edition: ${{ inputs.edition }}
|
||||
release: ${{ inputs.release }}
|
||||
- name: Prepare release scripts
|
||||
uses: ./.github/actions/prepare-release-scripts
|
||||
name: ${{ inputs.artifact-name }}
|
||||
- name: Prepare build scripts
|
||||
run: cd ${{ github.workspace }}/release && yarn && yarn build
|
||||
- name: Get platforms
|
||||
id: platforms
|
||||
uses: actions/github-script@v7
|
||||
|
||||
8
.github/workflows/containerize-uberjar.yml
vendored
8
.github/workflows/containerize-uberjar.yml
vendored
@@ -55,13 +55,13 @@ jobs:
|
||||
uses: ./.github/workflows/containerize-jar.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
edition: ${{ matrix.edition }}
|
||||
commit: ${{ github.event.inputs.commit || github.event.pull_request.head.sha || github.sha }}
|
||||
artifact-name: metabase-${{ matrix.edition }}-${{ github.event.inputs.commit || github.event.pull_request.head.sha || github.sha }}-uberjar
|
||||
commit: ${{ github.event.inputs.commit || github.sha }}
|
||||
repo: ${{ fromJson(needs.get-container-info.outputs[matrix.edition]).repo }}
|
||||
tag: ${{ fromJson(needs.get-container-info.outputs[matrix.edition]).tag }}
|
||||
release: "false"
|
||||
build-args: |
|
||||
GIT_COMMIT_SHA=${{ github.event.inputs.commit || github.event.pull_request.head.sha || github.sha }}
|
||||
GIT_COMMIT_SHA=${{ github.event.inputs.commit || github.sha }}
|
||||
|
||||
run-trivy:
|
||||
needs: [get-container-info, containerize]
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.commit || github.event.pull_request.head.sha || github.sha }}
|
||||
ref: ${{ github.event.inputs.commit || github.sha }}
|
||||
- name: Run Trivy vulnerability scanner
|
||||
uses: aquasecurity/trivy-action@0.29.0
|
||||
env:
|
||||
|
||||
2
.github/workflows/coverage.yml
vendored
2
.github/workflows/coverage.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
uses: ./.github/actions/prepare-backend
|
||||
- name: Run frontend unit tests
|
||||
run: |
|
||||
bun run test-unit \
|
||||
yarn run test-unit \
|
||||
--silent \
|
||||
--coverage \
|
||||
--testTimeout=60000
|
||||
|
||||
6
.github/workflows/docs-links.yml
vendored
6
.github/workflows/docs-links.yml
vendored
@@ -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
|
||||
|
||||
4
.github/workflows/docs-release-generate.yml
vendored
4
.github/workflows/docs-release-generate.yml
vendored
@@ -8,7 +8,7 @@ on:
|
||||
target_branch:
|
||||
description: "Target branch for documentation update"
|
||||
required: false
|
||||
default: "release-x.58.x"
|
||||
default: "release-x.56.x"
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
@@ -16,6 +16,6 @@ jobs:
|
||||
name: Call documentation update for release branch
|
||||
uses: ./.github/workflows/docs-generate-base.yml
|
||||
with:
|
||||
target_branch: ${{ inputs.target_branch || 'release-x.58.x' }}
|
||||
target_branch: ${{ inputs.target_branch || 'release-x.56.x' }}
|
||||
secrets:
|
||||
automation_token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
|
||||
455
.github/workflows/drivers.yml
vendored
455
.github/workflows/drivers.yml
vendored
@@ -12,52 +12,12 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# This job determines which driver tests should run based on PR context.
|
||||
# All decision logic is consolidated in mage driver-decisions.
|
||||
# This job will determine if the tier 3 driver tests should be skipped.
|
||||
# It will always run first.
|
||||
determine-driver-skips:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
# All driver run decisions (from mage driver-decisions)
|
||||
h2-should-run: ${{ steps.driver-decisions.outputs.h2-should-run }}
|
||||
athena-should-run: ${{ steps.driver-decisions.outputs.athena-should-run }}
|
||||
bigquery-should-run: ${{ steps.driver-decisions.outputs.bigquery-should-run }}
|
||||
clickhouse-should-run: ${{ steps.driver-decisions.outputs.clickhouse-should-run }}
|
||||
databricks-should-run: ${{ steps.driver-decisions.outputs.databricks-should-run }}
|
||||
druid-should-run: ${{ steps.driver-decisions.outputs.druid-should-run }}
|
||||
druid-jdbc-should-run: ${{ steps.driver-decisions.outputs.druid-jdbc-should-run }}
|
||||
mongo-should-run: ${{ steps.driver-decisions.outputs.mongo-should-run }}
|
||||
mongo-ssl-should-run: ${{ steps.driver-decisions.outputs.mongo-ssl-should-run }}
|
||||
mongo-sharded-cluster-should-run: ${{ steps.driver-decisions.outputs.mongo-sharded-cluster-should-run }}
|
||||
mysql-mariadb-should-run: ${{ steps.driver-decisions.outputs.mysql-mariadb-should-run }}
|
||||
oracle-should-run: ${{ steps.driver-decisions.outputs.oracle-should-run }}
|
||||
postgres-should-run: ${{ steps.driver-decisions.outputs.postgres-should-run }}
|
||||
presto-jdbc-should-run: ${{ steps.driver-decisions.outputs.presto-jdbc-should-run }}
|
||||
redshift-should-run: ${{ steps.driver-decisions.outputs.redshift-should-run }}
|
||||
snowflake-should-run: ${{ steps.driver-decisions.outputs.snowflake-should-run }}
|
||||
sparksql-should-run: ${{ steps.driver-decisions.outputs.sparksql-should-run }}
|
||||
sqlite-should-run: ${{ steps.driver-decisions.outputs.sqlite-should-run }}
|
||||
sqlserver-should-run: ${{ steps.driver-decisions.outputs.sqlserver-should-run }}
|
||||
vertica-should-run: ${{ steps.driver-decisions.outputs.vertica-should-run }}
|
||||
# Per-driver quarantine conflict flags (set when driver is quarantined but has file changes)
|
||||
athena-quarantine-conflict: ${{ steps.driver-decisions.outputs.athena-quarantine-conflict }}
|
||||
bigquery-quarantine-conflict: ${{ steps.driver-decisions.outputs.bigquery-quarantine-conflict }}
|
||||
clickhouse-quarantine-conflict: ${{ steps.driver-decisions.outputs.clickhouse-quarantine-conflict }}
|
||||
databricks-quarantine-conflict: ${{ steps.driver-decisions.outputs.databricks-quarantine-conflict }}
|
||||
druid-quarantine-conflict: ${{ steps.driver-decisions.outputs.druid-quarantine-conflict }}
|
||||
druid-jdbc-quarantine-conflict: ${{ steps.driver-decisions.outputs.druid-jdbc-quarantine-conflict }}
|
||||
mongo-quarantine-conflict: ${{ steps.driver-decisions.outputs.mongo-quarantine-conflict }}
|
||||
mongo-ssl-quarantine-conflict: ${{ steps.driver-decisions.outputs.mongo-ssl-quarantine-conflict }}
|
||||
mongo-sharded-cluster-quarantine-conflict: ${{ steps.driver-decisions.outputs.mongo-sharded-cluster-quarantine-conflict }}
|
||||
mysql-mariadb-quarantine-conflict: ${{ steps.driver-decisions.outputs.mysql-mariadb-quarantine-conflict }}
|
||||
oracle-quarantine-conflict: ${{ steps.driver-decisions.outputs.oracle-quarantine-conflict }}
|
||||
postgres-quarantine-conflict: ${{ steps.driver-decisions.outputs.postgres-quarantine-conflict }}
|
||||
presto-jdbc-quarantine-conflict: ${{ steps.driver-decisions.outputs.presto-jdbc-quarantine-conflict }}
|
||||
redshift-quarantine-conflict: ${{ steps.driver-decisions.outputs.redshift-quarantine-conflict }}
|
||||
snowflake-quarantine-conflict: ${{ steps.driver-decisions.outputs.snowflake-quarantine-conflict }}
|
||||
sparksql-quarantine-conflict: ${{ steps.driver-decisions.outputs.sparksql-quarantine-conflict }}
|
||||
sqlite-quarantine-conflict: ${{ steps.driver-decisions.outputs.sqlite-quarantine-conflict }}
|
||||
sqlserver-quarantine-conflict: ${{ steps.driver-decisions.outputs.sqlserver-quarantine-conflict }}
|
||||
vertica-quarantine-conflict: ${{ steps.driver-decisions.outputs.vertica-quarantine-conflict }}
|
||||
skip: ${{ steps.check-branch-and-files-changed.outputs.skip }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
@@ -66,31 +26,46 @@ jobs:
|
||||
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 }}
|
||||
- name: Determine which drivers should run
|
||||
id: driver-decisions
|
||||
# Only run this if it's a pull request and the base ref exists
|
||||
- name: Get Date
|
||||
id: get-date
|
||||
shell: bash
|
||||
run: |
|
||||
# All decision logic is in mage - it analyzes module dependencies,
|
||||
# branch context, labels, and file changes to decide what runs.
|
||||
MAGE_ARGS=(
|
||||
--git-ref="${{ github.event.pull_request.base.ref || github.ref_name }}"
|
||||
--is-master-or-release="${{ github.ref_name == 'master' || startsWith(github.ref_name, 'release-') }}"
|
||||
--pr-labels="${{ join(github.event.pull_request.labels.*.name, ',') }}"
|
||||
--skip="${{ inputs.skip }}"
|
||||
)
|
||||
echo "Running:"
|
||||
echo "./bin/mage -driver-decisions ${MAGE_ARGS[@]}"
|
||||
echo ""
|
||||
# First call: show human-readable output for debugging
|
||||
./bin/mage -driver-decisions "${MAGE_ARGS[@]}"
|
||||
# Second call: output only key=value lines for GITHUB_OUTPUT
|
||||
./bin/mage -driver-decisions "${MAGE_ARGS[@]}" --github-output-only >> $GITHUB_OUTPUT
|
||||
echo "date=$(/bin/date -u "+%Y%m%d")" >> $GITHUB_OUTPUT
|
||||
- name: Restore Babashka cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.m2
|
||||
~/.deps.clj
|
||||
restore-keys: |
|
||||
mage-drivers-
|
||||
key: >-
|
||||
mage-drivers-${{ steps.get-date.outputs.date }}
|
||||
- name: Check if driver tests should run
|
||||
id: check-branch-and-files-changed
|
||||
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}"
|
||||
|
||||
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."
|
||||
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
|
||||
else
|
||||
echo 'skip=false' >> $GITHUB_OUTPUT
|
||||
fi
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
be-tests-athena-ee:
|
||||
needs: determine-driver-skips
|
||||
if: ${{ needs.determine-driver-skips.outputs.athena-should-run == 'true' || needs.determine-driver-skips.outputs.athena-quarantine-conflict == 'true' }}
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
CI: 'true'
|
||||
DRIVERS: athena
|
||||
@@ -105,19 +80,8 @@ jobs:
|
||||
MB_ATHENA_TEST_WITHOUT_GET_TABLE_METADATA_SECRET_KEY: ${{ secrets.MB_ATHENA_TEST_WITHOUT_GET_TABLE_METADATA_SECRET_KEY }}
|
||||
name: Athena
|
||||
steps:
|
||||
- name: Fail on quarantine conflict
|
||||
if: ${{ needs.determine-driver-skips.outputs.athena-quarantine-conflict == 'true' }}
|
||||
run: |
|
||||
echo "::error::Athena driver is quarantined but you changed files in modules/drivers/athena/"
|
||||
echo ""
|
||||
echo "Your changes will NOT be tested because the driver is currently quarantined."
|
||||
echo "To run Athena tests, add the label: break-quarantine-athena"
|
||||
echo ""
|
||||
echo "If you merge without testing, you risk breaking the Athena driver!"
|
||||
exit 1
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test Athena driver
|
||||
id: test-driver
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-athena-ee'
|
||||
@@ -135,11 +99,10 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
|
||||
|
||||
be-tests-bigquery-cloud-sdk-ee:
|
||||
needs: determine-driver-skips
|
||||
if: ${{ needs.determine-driver-skips.outputs.bigquery-should-run == 'true' || needs.determine-driver-skips.outputs.bigquery-quarantine-conflict == 'true' }}
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
@@ -150,7 +113,6 @@ jobs:
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
:exclude-tags [:mb/transforms-python-test]
|
||||
:fail-fast? true
|
||||
# DS 2025-09-26 temporarily disabled while we diagnose flakes
|
||||
# - name: Transforms Python Tests
|
||||
# test-args: >-
|
||||
@@ -168,16 +130,6 @@ jobs:
|
||||
MB_BIGQUERY_CLOUD_SDK_TEST_SERVICE_ACCOUNT_JSON_ROUTING: ${{ secrets.MB_BIGQUERY_CLOUD_SDK_TEST_SERVICE_ACCOUNT_JSON_ROUTING }}
|
||||
name: BigQuery ${{ matrix.job.name }}
|
||||
steps:
|
||||
- name: Fail on quarantine conflict
|
||||
if: ${{ needs.determine-driver-skips.outputs.bigquery-quarantine-conflict == 'true' }}
|
||||
run: |
|
||||
echo "::error::BigQuery driver is quarantined but you changed files in modules/drivers/bigquery-cloud-sdk/"
|
||||
echo ""
|
||||
echo "Your changes will NOT be tested because the driver is currently quarantined."
|
||||
echo "To run BigQuery tests, add the label: break-quarantine-bigquery"
|
||||
echo ""
|
||||
echo "If you merge without testing, you risk breaking the BigQuery driver!"
|
||||
exit 1
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup python-runner
|
||||
if: ${{ matrix.job.needs-python-runner == true }}
|
||||
@@ -190,7 +142,6 @@ jobs:
|
||||
with:
|
||||
junit-name: 'be-tests-bigquery-cloud-sdk-ee'
|
||||
test-args: ${{ matrix.job.test-args }}
|
||||
logs-artifact-suffix: ${{ matrix.job.name }}
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -202,7 +153,6 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
|
||||
- name: Cleanup python-runner
|
||||
if: ${{ always() && matrix.job.needs-python-runner == true }}
|
||||
uses: ./.github/actions/cleanup-python-runner
|
||||
@@ -212,9 +162,9 @@ jobs:
|
||||
|
||||
be-tests-clickhouse-ee:
|
||||
needs: determine-driver-skips
|
||||
if: ${{ needs.determine-driver-skips.outputs.clickhouse-should-run == 'true' || needs.determine-driver-skips.outputs.clickhouse-quarantine-conflict == 'true' }}
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -236,16 +186,6 @@ jobs:
|
||||
MB_CLICKHOUSE_TEST_NGINX_PORT: 8127
|
||||
name: Clickhouse ${{ matrix.job.name }}
|
||||
steps:
|
||||
- name: Fail on quarantine conflict
|
||||
if: ${{ needs.determine-driver-skips.outputs.clickhouse-quarantine-conflict == 'true' }}
|
||||
run: |
|
||||
echo "::error::ClickHouse driver is quarantined but you changed files in modules/drivers/clickhouse/"
|
||||
echo ""
|
||||
echo "Your changes will NOT be tested because the driver is currently quarantined."
|
||||
echo "To run ClickHouse tests, add the label: break-quarantine-clickhouse"
|
||||
echo ""
|
||||
echo "If you merge without testing, you risk breaking the ClickHouse driver!"
|
||||
exit 1
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup python-runner
|
||||
if: ${{ matrix.job.needs-python-runner == true }}
|
||||
@@ -273,7 +213,6 @@ jobs:
|
||||
with:
|
||||
junit-name: 'be-tests-clickhouse-ee'
|
||||
test-args: ${{ matrix.job.test-args }}
|
||||
logs-artifact-suffix: ${{ matrix.job.name }}
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -285,7 +224,6 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
|
||||
- name: Cleanup python-runner
|
||||
if: ${{ always() && matrix.job.needs-python-runner == true }}
|
||||
uses: ./.github/actions/cleanup-python-runner
|
||||
@@ -295,9 +233,9 @@ jobs:
|
||||
|
||||
be-tests-druid-ee:
|
||||
needs: determine-driver-skips
|
||||
if: ${{ needs.determine-driver-skips.outputs.druid-should-run == 'true' || needs.determine-driver-skips.outputs.druid-quarantine-conflict == 'true' }}
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
CI: 'true'
|
||||
DRIVERS: druid
|
||||
@@ -310,19 +248,8 @@ jobs:
|
||||
CLUSTER_SIZE: nano-quickstart
|
||||
name: Druid (Legacy)
|
||||
steps:
|
||||
- name: Fail on quarantine conflict
|
||||
if: ${{ needs.determine-driver-skips.outputs.druid-quarantine-conflict == 'true' }}
|
||||
run: |
|
||||
echo "::error::Druid driver is quarantined but you changed files in modules/drivers/druid/"
|
||||
echo ""
|
||||
echo "Your changes will NOT be tested because the driver is currently quarantined."
|
||||
echo "To run Druid tests, add the label: break-quarantine-druid"
|
||||
echo ""
|
||||
echo "If you merge without testing, you risk breaking the Druid driver!"
|
||||
exit 1
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test Druid driver
|
||||
id: test-driver
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-druid-ee'
|
||||
@@ -340,13 +267,12 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
|
||||
|
||||
be-tests-druid-jdbc-ee:
|
||||
needs: determine-driver-skips
|
||||
if: ${{ needs.determine-driver-skips.outputs.druid-jdbc-should-run == 'true' || needs.determine-driver-skips.outputs.druid-jdbc-quarantine-conflict == 'true' }}
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
CI: 'true'
|
||||
DRIVERS: druid-jdbc
|
||||
@@ -359,19 +285,8 @@ jobs:
|
||||
CLUSTER_SIZE: nano-quickstart
|
||||
name: Druid (JDBC)
|
||||
steps:
|
||||
- name: Fail on quarantine conflict
|
||||
if: ${{ needs.determine-driver-skips.outputs.druid-jdbc-quarantine-conflict == 'true' }}
|
||||
run: |
|
||||
echo "::error::Druid JDBC driver is quarantined but you changed files in modules/drivers/druid-jdbc/"
|
||||
echo ""
|
||||
echo "Your changes will NOT be tested because the driver is currently quarantined."
|
||||
echo "To run Druid JDBC tests, add the label: break-quarantine-druid-jdbc"
|
||||
echo ""
|
||||
echo "If you merge without testing, you risk breaking the Druid JDBC driver!"
|
||||
exit 1
|
||||
- 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'
|
||||
@@ -389,13 +304,12 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
|
||||
|
||||
be-tests-databricks-ee:
|
||||
needs: determine-driver-skips
|
||||
if: ${{ needs.determine-driver-skips.outputs.databricks-should-run == 'true' || needs.determine-driver-skips.outputs.databricks-quarantine-conflict == 'true' }}
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
CI: 'true'
|
||||
DRIVERS: databricks
|
||||
@@ -408,19 +322,8 @@ jobs:
|
||||
MB_DATABRICKS_TEST_OAUTH_SECRET: ${{ secrets.MB_DATABRICKS_JDBC_TEST_OAUTH_SECRET }}
|
||||
name: Databricks
|
||||
steps:
|
||||
- name: Fail on quarantine conflict
|
||||
if: ${{ needs.determine-driver-skips.outputs.databricks-quarantine-conflict == 'true' }}
|
||||
run: |
|
||||
echo "::error::Databricks driver is quarantined but you changed files in modules/drivers/databricks/"
|
||||
echo ""
|
||||
echo "Your changes will NOT be tested because the driver is currently quarantined."
|
||||
echo "To run Databricks tests, add the label: break-quarantine-databricks"
|
||||
echo ""
|
||||
echo "If you merge without testing, you risk breaking the Databricks driver!"
|
||||
exit 1
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test Databricks driver
|
||||
id: test-driver
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-databricks-ee'
|
||||
@@ -438,13 +341,12 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
|
||||
|
||||
be-tests-databricks-multi-catalog-ee:
|
||||
needs: determine-driver-skips
|
||||
if: ${{ needs.determine-driver-skips.outputs.databricks-should-run == 'true' || needs.determine-driver-skips.outputs.databricks-quarantine-conflict == 'true' }}
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
CI: 'true'
|
||||
DRIVERS: databricks
|
||||
@@ -458,19 +360,8 @@ jobs:
|
||||
MB_DATABRICKS_TEST_OAUTH_SECRET: ${{ secrets.MB_DATABRICKS_JDBC_TEST_OAUTH_SECRET }}
|
||||
name: Databricks (Multi-Catalog)
|
||||
steps:
|
||||
- name: Fail on quarantine conflict
|
||||
if: ${{ needs.determine-driver-skips.outputs.databricks-quarantine-conflict == 'true' }}
|
||||
run: |
|
||||
echo "::error::Databricks driver is quarantined but you changed files in modules/drivers/databricks/"
|
||||
echo ""
|
||||
echo "Your changes will NOT be tested because the driver is currently quarantined."
|
||||
echo "To run Databricks tests, add the label: break-quarantine-databricks"
|
||||
echo ""
|
||||
echo "If you merge without testing, you risk breaking the Databricks driver!"
|
||||
exit 1
|
||||
- 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'
|
||||
@@ -488,13 +379,11 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
|
||||
|
||||
be-tests-h2:
|
||||
needs: determine-driver-skips
|
||||
if: ${{ needs.determine-driver-skips.outputs.h2-should-run == 'true' }}
|
||||
if: ${{ !inputs.skip }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
CI: 'true'
|
||||
name: H2
|
||||
@@ -519,45 +408,12 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
|
||||
|
||||
be-tests-h2-oss:
|
||||
needs: determine-driver-skips
|
||||
if: ${{ needs.determine-driver-skips.outputs.h2-should-run == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
env:
|
||||
CI: 'true'
|
||||
name: H2 (OSS)
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test H2 driver (OSS)
|
||||
id: test-driver
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-h2-oss'
|
||||
edition: 'oss'
|
||||
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 }}
|
||||
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
|
||||
|
||||
be-tests-mysql-mariadb:
|
||||
needs: determine-driver-skips
|
||||
if: ${{ needs.determine-driver-skips.outputs.mysql-mariadb-should-run == 'true' }}
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -613,8 +469,8 @@ jobs:
|
||||
(
|
||||
github.event_name == 'push' &&
|
||||
(
|
||||
github.ref_name == 'master' ||
|
||||
startsWith(github.ref_name, 'release-')
|
||||
github.ref == 'master' ||
|
||||
startsWith(github.ref, 'release-')
|
||||
) &&
|
||||
''
|
||||
) ||
|
||||
@@ -667,7 +523,6 @@ jobs:
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: ${{ matrix.version.junit-name }}
|
||||
logs-artifact-suffix: ${{ matrix.version.name }}
|
||||
test-args: >-
|
||||
${{ matrix.job.test-args }}
|
||||
:exclude-tags '[ ${{ matrix.job.exclude-tag }} ${{ env.__ADDITIONAL_EXCLUDED_TAG__ }}]'
|
||||
@@ -682,7 +537,6 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
|
||||
- name: Cleanup python-runner
|
||||
if: always()
|
||||
uses: ./.github/actions/cleanup-python-runner
|
||||
@@ -692,9 +546,9 @@ jobs:
|
||||
|
||||
be-tests-mongo:
|
||||
needs: determine-driver-skips
|
||||
if: ${{ needs.determine-driver-skips.outputs.mongo-should-run == 'true' || needs.determine-driver-skips.outputs.mongo-quarantine-conflict == 'true' }}
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -729,16 +583,6 @@ jobs:
|
||||
MONGO_INITDB_ROOT_PASSWORD: metasample123
|
||||
name: ${{ matrix.version.name }} ${{ matrix.job.name }}
|
||||
steps:
|
||||
- name: Fail on quarantine conflict
|
||||
if: ${{ needs.determine-driver-skips.outputs.mongo-quarantine-conflict == 'true' }}
|
||||
run: |
|
||||
echo "::error::MongoDB driver is quarantined but you changed files in modules/drivers/mongo/"
|
||||
echo ""
|
||||
echo "Your changes will NOT be tested because the driver is currently quarantined."
|
||||
echo "To run MongoDB tests, add the label: break-quarantine-mongo"
|
||||
echo ""
|
||||
echo "If you merge without testing, you risk breaking the MongoDB driver!"
|
||||
exit 1
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup python-runner
|
||||
if: ${{ matrix.job.needs-python-runner == true }}
|
||||
@@ -751,7 +595,6 @@ jobs:
|
||||
with:
|
||||
junit-name: ${{ matrix.version.junit-name }}
|
||||
test-args: ${{ matrix.job.test-args }}
|
||||
logs-artifact-suffix: ${{ matrix.job.name }}
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -763,7 +606,6 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
|
||||
- name: Cleanup python-runner
|
||||
if: ${{ always() && matrix.job.needs-python-runner == true }}
|
||||
uses: ./.github/actions/cleanup-python-runner
|
||||
@@ -773,9 +615,9 @@ jobs:
|
||||
|
||||
be-tests-mongo-ssl:
|
||||
needs: determine-driver-skips
|
||||
if: ${{ needs.determine-driver-skips.outputs.mongo-ssl-should-run == 'true' || needs.determine-driver-skips.outputs.mongo-ssl-quarantine-conflict == 'true' }}
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -791,16 +633,6 @@ jobs:
|
||||
MB_TEST_MONGO_REQUIRES_SSL: true
|
||||
name: "${{ matrix.version.name }} (SSL)"
|
||||
steps:
|
||||
- name: Fail on quarantine conflict
|
||||
if: ${{ needs.determine-driver-skips.outputs.mongo-ssl-quarantine-conflict == 'true' }}
|
||||
run: |
|
||||
echo "::error::MongoDB SSL driver is quarantined but you changed files in modules/drivers/mongo/"
|
||||
echo ""
|
||||
echo "Your changes will NOT be tested because the driver is currently quarantined."
|
||||
echo "To run MongoDB SSL tests, add the label: break-quarantine-mongo-ssl"
|
||||
echo ""
|
||||
echo "If you merge without testing, you risk breaking the MongoDB SSL driver!"
|
||||
exit 1
|
||||
- uses: actions/checkout@v4
|
||||
- name: Spin up Mongo docker container
|
||||
run: >-
|
||||
@@ -828,7 +660,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 }}
|
||||
@@ -846,13 +677,12 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
|
||||
|
||||
be-tests-mongo-sharded-cluster-ee:
|
||||
needs: determine-driver-skips
|
||||
if: ${{ needs.determine-driver-skips.outputs.mongo-sharded-cluster-should-run == 'true' || needs.determine-driver-skips.outputs.mongo-sharded-cluster-quarantine-conflict == 'true' }}
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
CI: 'true'
|
||||
DRIVERS: mongo
|
||||
@@ -863,19 +693,8 @@ jobs:
|
||||
- "27017:17017"
|
||||
name: MongoDB Sharded Cluster
|
||||
steps:
|
||||
- name: Fail on quarantine conflict
|
||||
if: ${{ needs.determine-driver-skips.outputs.mongo-sharded-cluster-quarantine-conflict == 'true' }}
|
||||
run: |
|
||||
echo "::error::MongoDB Sharded Cluster driver is quarantined but you changed files in modules/drivers/mongo/"
|
||||
echo ""
|
||||
echo "Your changes will NOT be tested because the driver is currently quarantined."
|
||||
echo "To run MongoDB Sharded Cluster tests, add the label: break-quarantine-mongo-sharded-cluster"
|
||||
echo ""
|
||||
echo "If you merge without testing, you risk breaking the MongoDB Sharded Cluster driver!"
|
||||
exit 1
|
||||
- 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'
|
||||
@@ -892,13 +711,12 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
|
||||
|
||||
be-tests-oracle:
|
||||
needs: determine-driver-skips
|
||||
if: ${{ needs.determine-driver-skips.outputs.oracle-should-run == 'true' || needs.determine-driver-skips.outputs.oracle-quarantine-conflict == 'true' }}
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -947,23 +765,11 @@ jobs:
|
||||
- "1521:${{ matrix.version.port }}"
|
||||
name: ${{ matrix.version.name }}
|
||||
steps:
|
||||
- name: Fail on quarantine conflict
|
||||
if: ${{ needs.determine-driver-skips.outputs.oracle-quarantine-conflict == 'true' }}
|
||||
run: |
|
||||
echo "::error::Oracle driver is quarantined but you changed files in modules/drivers/oracle/"
|
||||
echo ""
|
||||
echo "Your changes will NOT be tested because the driver is currently quarantined."
|
||||
echo "To run Oracle tests, add the label: break-quarantine-oracle"
|
||||
echo ""
|
||||
echo "If you merge without testing, you risk breaking the Oracle driver!"
|
||||
exit 1
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test ${{ matrix.version.name }}
|
||||
id: test-driver
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: ${{ matrix.version.junit-name }}
|
||||
logs-artifact-suffix: ${{ matrix.version.name }}
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
:exclude-tags [:mb/transforms-python-test]
|
||||
@@ -978,13 +784,12 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
|
||||
|
||||
be-tests-postgres:
|
||||
needs: determine-driver-skips
|
||||
if: ${{ needs.determine-driver-skips.outputs.postgres-should-run == 'true' }}
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -1020,8 +825,8 @@ jobs:
|
||||
(
|
||||
github.event_name == 'push' &&
|
||||
(
|
||||
github.ref_name == 'master' ||
|
||||
startsWith(github.ref_name, 'release-')
|
||||
github.ref == 'master' ||
|
||||
startsWith(github.ref, 'release-')
|
||||
) &&
|
||||
''
|
||||
) ||
|
||||
@@ -1068,7 +873,6 @@ jobs:
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-${{ matrix.version.junit-name }}'
|
||||
logs-artifact-suffix: ${{ matrix.version.name }}-${{ matrix.job.name }}
|
||||
test-args: >-
|
||||
${{ matrix.job.test-args }}
|
||||
:exclude-tags '[ ${{ matrix.job.exclude-tag }} ${{ env.__ADDITIONAL_EXCLUDED_TAG__ }}]'
|
||||
@@ -1083,7 +887,6 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
|
||||
- name: Cleanup python-runner
|
||||
if: always()
|
||||
uses: ./.github/actions/cleanup-python-runner
|
||||
@@ -1093,9 +896,9 @@ jobs:
|
||||
|
||||
be-tests-presto-jdbc-ee:
|
||||
needs: determine-driver-skips
|
||||
if: ${{ needs.determine-driver-skips.outputs.presto-jdbc-should-run == 'true' || needs.determine-driver-skips.outputs.presto-jdbc-quarantine-conflict == 'true' }}
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
CI: 'true'
|
||||
DRIVERS: presto-jdbc
|
||||
@@ -1116,16 +919,6 @@ jobs:
|
||||
env:
|
||||
JAVA_TOOL_OPTIONS: "-Xmx2g"
|
||||
steps:
|
||||
- name: Fail on quarantine conflict
|
||||
if: ${{ needs.determine-driver-skips.outputs.presto-jdbc-quarantine-conflict == 'true' }}
|
||||
run: |
|
||||
echo "::error::Presto JDBC driver is quarantined but you changed files in modules/drivers/presto-jdbc/"
|
||||
echo ""
|
||||
echo "Your changes will NOT be tested because the driver is currently quarantined."
|
||||
echo "To run Presto JDBC tests, add the label: break-quarantine-presto-jdbc"
|
||||
echo ""
|
||||
echo "If you merge without testing, you risk breaking the Presto JDBC driver!"
|
||||
exit 1
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/await-port
|
||||
with:
|
||||
@@ -1153,7 +946,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'
|
||||
@@ -1171,13 +963,12 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
|
||||
|
||||
be-tests-redshift-ee:
|
||||
needs: determine-driver-skips
|
||||
if: ${{ needs.determine-driver-skips.outputs.redshift-should-run == 'true' || needs.determine-driver-skips.outputs.redshift-quarantine-conflict == 'true' }}
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -1187,22 +978,12 @@ jobs:
|
||||
test-args: >-
|
||||
:only-tags [:mb/upload-tests]
|
||||
:fail-fast? true
|
||||
- name: Redshift (Excluding Upload Tests) Part 1
|
||||
- name: Redshift (Excluding Upload Tests)
|
||||
skip: false
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
:exclude-tags '[:mb/upload-tests :mb/transforms-python-test]'
|
||||
:fail-fast? true
|
||||
:partition/total 2
|
||||
:partition/index 0
|
||||
- name: Redshift (Excluding Upload Tests) Part 2
|
||||
skip: false
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
:exclude-tags '[:mb/upload-tests :mb/transforms-python-test]'
|
||||
:fail-fast? true
|
||||
:partition/total 2
|
||||
:partition/index 1
|
||||
- name: Redshift Transforms Python Tests
|
||||
skip: false
|
||||
test-args: >-
|
||||
@@ -1220,16 +1001,6 @@ jobs:
|
||||
MB_REDSHIFT_TEST_PASSWORD: ${{ secrets.MB_REDSHIFT_TEST_PASSWORD }}
|
||||
name: ${{ matrix.job.name }}
|
||||
steps:
|
||||
- name: Fail on quarantine conflict
|
||||
if: ${{ needs.determine-driver-skips.outputs.redshift-quarantine-conflict == 'true' }}
|
||||
run: |
|
||||
echo "::error::Redshift driver is quarantined but you changed files in modules/drivers/redshift/"
|
||||
echo ""
|
||||
echo "Your changes will NOT be tested because the driver is currently quarantined."
|
||||
echo "To run Redshift tests, add the label: break-quarantine-redshift"
|
||||
echo ""
|
||||
echo "If you merge without testing, you risk breaking the Redshift driver!"
|
||||
exit 1
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup python-runner
|
||||
if: ${{ matrix.job.needs-python-runner == true }}
|
||||
@@ -1244,7 +1015,6 @@ jobs:
|
||||
junit-name: 'be-tests-redshift-ee'
|
||||
test-args: >-
|
||||
${{ matrix.job.test-args }}
|
||||
logs-artifact-suffix: ${{ matrix.job.name }}
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: ${{ !matrix.job.skip && !cancelled() }}
|
||||
@@ -1256,7 +1026,6 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
|
||||
- name: Cleanup python-runner
|
||||
if: ${{ always() && matrix.job.needs-python-runner == true }}
|
||||
uses: ./.github/actions/cleanup-python-runner
|
||||
@@ -1265,8 +1034,7 @@ jobs:
|
||||
logs-artifact-suffix: ${{ matrix.job.name }} ${{ matrix.version.name }}
|
||||
|
||||
be-tests-snowflake-ee:
|
||||
needs: determine-driver-skips
|
||||
if: ${{ needs.determine-driver-skips.outputs.snowflake-should-run == 'true' || needs.determine-driver-skips.outputs.snowflake-quarantine-conflict == 'true' }}
|
||||
if: ${{ !inputs.skip }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
@@ -1277,7 +1045,6 @@ jobs:
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
:exclude-tags [:mb/transforms-python-test]
|
||||
:fail-fast? true
|
||||
|
||||
# DS 2025-09-26 temporarily disabled while we diagnose flakes
|
||||
# - name: Transforms Python Tests
|
||||
@@ -1305,16 +1072,6 @@ jobs:
|
||||
MB_SNOWFLAKE_TEST_RSA_ROLE_TEST_DB: RSA_ROLE_TEST_DB
|
||||
name: Snowflake ${{ matrix.job.name }}
|
||||
steps:
|
||||
- name: Fail on quarantine conflict
|
||||
if: ${{ needs.determine-driver-skips.outputs.snowflake-quarantine-conflict == 'true' }}
|
||||
run: |
|
||||
echo "::error::Snowflake driver is quarantined but you changed files in modules/drivers/snowflake/"
|
||||
echo ""
|
||||
echo "Your changes will NOT be tested because the driver is currently quarantined."
|
||||
echo "To run Snowflake tests, add the label: break-quarantine-snowflake"
|
||||
echo ""
|
||||
echo "If you merge without testing, you risk breaking the Snowflake driver!"
|
||||
exit 1
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup python-runner
|
||||
if: ${{ matrix.job.needs-python-runner == true }}
|
||||
@@ -1327,7 +1084,6 @@ jobs:
|
||||
with:
|
||||
junit-name: 'be-tests-snowflake-ee'
|
||||
test-args: ${{ matrix.job.test-args }}
|
||||
logs-artifact-suffix: ${{ matrix.job.name }}
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -1339,7 +1095,6 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
|
||||
- name: Cleanup python-runner
|
||||
if: ${{ always() && matrix.job.needs-python-runner == true }}
|
||||
uses: ./.github/actions/cleanup-python-runner
|
||||
@@ -1349,9 +1104,9 @@ jobs:
|
||||
|
||||
be-tests-sparksql-ee:
|
||||
needs: determine-driver-skips
|
||||
if: ${{ needs.determine-driver-skips.outputs.sparksql-should-run == 'true' || needs.determine-driver-skips.outputs.sparksql-quarantine-conflict == 'true' }}
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
CI: 'true'
|
||||
DRIVERS: sparksql
|
||||
@@ -1362,19 +1117,8 @@ jobs:
|
||||
- "10000:10000"
|
||||
name: Spark SQL
|
||||
steps:
|
||||
- name: Fail on quarantine conflict
|
||||
if: ${{ needs.determine-driver-skips.outputs.sparksql-quarantine-conflict == 'true' }}
|
||||
run: |
|
||||
echo "::error::SparkSQL driver is quarantined but you changed files in modules/drivers/sparksql/"
|
||||
echo ""
|
||||
echo "Your changes will NOT be tested because the driver is currently quarantined."
|
||||
echo "To run SparkSQL tests, add the label: break-quarantine-sparksql"
|
||||
echo ""
|
||||
echo "If you merge without testing, you risk breaking the SparkSQL driver!"
|
||||
exit 1
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test Spark driver
|
||||
id: test-driver
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-sparksql-ee'
|
||||
@@ -1392,31 +1136,19 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
|
||||
|
||||
be-tests-sqlite-ee:
|
||||
needs: determine-driver-skips
|
||||
if: ${{ needs.determine-driver-skips.outputs.sqlite-should-run == 'true' || needs.determine-driver-skips.outputs.sqlite-quarantine-conflict == 'true' }}
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
CI: 'true'
|
||||
DRIVERS: sqlite
|
||||
name: SQLite
|
||||
steps:
|
||||
- name: Fail on quarantine conflict
|
||||
if: ${{ needs.determine-driver-skips.outputs.sqlite-quarantine-conflict == 'true' }}
|
||||
run: |
|
||||
echo "::error::SQLite driver is quarantined but you changed files in modules/drivers/sqlite/"
|
||||
echo ""
|
||||
echo "Your changes will NOT be tested because the driver is currently quarantined."
|
||||
echo "To run SQLite tests, add the label: break-quarantine-sqlite"
|
||||
echo ""
|
||||
echo "If you merge without testing, you risk breaking the SQLite driver!"
|
||||
exit 1
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test SQLite driver
|
||||
id: test-driver
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-sqlite-ee'
|
||||
@@ -1434,11 +1166,10 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
|
||||
|
||||
be-tests-sqlserver:
|
||||
needs: determine-driver-skips
|
||||
if: ${{ needs.determine-driver-skips.outputs.sqlserver-should-run == 'true' || needs.determine-driver-skips.outputs.sqlserver-quarantine-conflict == 'true' }}
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
@@ -1477,16 +1208,6 @@ jobs:
|
||||
MSSQL_MEMORY_LIMIT_MB: 1024
|
||||
name: ${{ matrix.version.name }} ${{ matrix.job.name }}
|
||||
steps:
|
||||
- name: Fail on quarantine conflict
|
||||
if: ${{ needs.determine-driver-skips.outputs.sqlserver-quarantine-conflict == 'true' }}
|
||||
run: |
|
||||
echo "::error::SQL Server driver is quarantined but you changed files in modules/drivers/sqlserver/"
|
||||
echo ""
|
||||
echo "Your changes will NOT be tested because the driver is currently quarantined."
|
||||
echo "To run SQL Server tests, add the label: break-quarantine-sqlserver"
|
||||
echo ""
|
||||
echo "If you merge without testing, you risk breaking the SQL Server driver!"
|
||||
exit 1
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup python-runner
|
||||
if: ${{ matrix.job.needs-python-runner == true }}
|
||||
@@ -1499,7 +1220,6 @@ jobs:
|
||||
with:
|
||||
junit-name: ${{ matrix.version.junit-name }}
|
||||
test-args: ${{ matrix.job.test-args }}
|
||||
logs-artifact-suffix: ${{ matrix.version.name }}
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -1511,7 +1231,6 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
|
||||
- name: Cleanup python-runner
|
||||
if: ${{ always() && matrix.job.needs-python-runner == true }}
|
||||
uses: ./.github/actions/cleanup-python-runner
|
||||
@@ -1524,7 +1243,7 @@ jobs:
|
||||
# needs: determine-driver-skips
|
||||
# if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
# runs-on: ubuntu-latest
|
||||
# timeout-minutes: 40
|
||||
# timeout-minutes: 60
|
||||
# env:
|
||||
# CI: 'true'
|
||||
# DRIVERS: starburst
|
||||
@@ -1560,7 +1279,7 @@ jobs:
|
||||
|
||||
login-to-ecr:
|
||||
needs: determine-driver-skips
|
||||
if: ${{ needs.determine-driver-skips.outputs.vertica-should-run == 'true' || needs.determine-driver-skips.outputs.vertica-quarantine-conflict == 'true' }}
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
@@ -1590,9 +1309,9 @@ jobs:
|
||||
|
||||
be-tests-vertica-ee:
|
||||
needs: [determine-driver-skips, login-to-ecr]
|
||||
if: ${{ needs.determine-driver-skips.outputs.vertica-should-run == 'true' || needs.determine-driver-skips.outputs.vertica-quarantine-conflict == 'true' }}
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
CI: 'true'
|
||||
DRIVERS: vertica
|
||||
@@ -1606,21 +1325,10 @@ jobs:
|
||||
password: ${{ needs.login-to-ecr.outputs.docker_password }}
|
||||
name: Vertica
|
||||
steps:
|
||||
- name: Fail on quarantine conflict
|
||||
if: ${{ needs.determine-driver-skips.outputs.vertica-quarantine-conflict == 'true' }}
|
||||
run: |
|
||||
echo "::error::Vertica driver is quarantined but you changed files in modules/drivers/vertica/"
|
||||
echo ""
|
||||
echo "Your changes will NOT be tested because the driver is currently quarantined."
|
||||
echo "To run Vertica tests, add the label: break-quarantine-vertica"
|
||||
echo ""
|
||||
echo "If you merge without testing, you risk breaking the Vertica driver!"
|
||||
exit 1
|
||||
- uses: actions/checkout@v4
|
||||
- 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'
|
||||
@@ -1638,7 +1346,6 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
|
||||
|
||||
drivers-tests-result:
|
||||
needs:
|
||||
|
||||
185
.github/workflows/e2e-component-tests-embedding-sdk-backward-compatibility.yml
vendored
Normal file
185
.github/workflows/e2e-component-tests-embedding-sdk-backward-compatibility.yml
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
# This 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: ""
|
||||
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
|
||||
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();
|
||||
203
.github/workflows/e2e-component-tests-embedding-sdk.yml
vendored
Normal file
203
.github/workflows/e2e-component-tests-embedding-sdk.yml
vendored
Normal 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: ""
|
||||
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: 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();
|
||||
409
.github/workflows/e2e-embedding-sdk-compatibility-tests.yml
vendored
Normal file
409
.github/workflows/e2e-embedding-sdk-compatibility-tests.yml
vendored
Normal file
@@ -0,0 +1,409 @@
|
||||
name: "[NOT REQUIRED] SDK Compatibility tests"
|
||||
|
||||
on:
|
||||
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 last two stable releases
|
||||
SUPPORTED_RELEASES_COUNT: 2
|
||||
TEST_SUITES: |
|
||||
[
|
||||
"metabase-nodejs-react-sdk-embedding-sample-e2e",
|
||||
"metabase-nextjs-sdk-embedding-sample-e2e",
|
||||
"shoppy-e2e"
|
||||
]
|
||||
TEST_SUITES_TO_RUN_IN_MASTER_ONLY: |
|
||||
[
|
||||
"shoppy-e2e"
|
||||
]
|
||||
SAMPLE_APP_MAIN_BRANCH_NAME: "main"
|
||||
SAMPLE_APP_STABLE_RELEASE_BRANCH_NAME_SUFFIX: "stable"
|
||||
outputs:
|
||||
branch_name: ${{ fromJson(steps.build-sample-apps-data.outputs.result).branch_name }}
|
||||
matrix: ${{ toJSON(fromJson(steps.build-sample-apps-data.outputs.result).matrix) }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Prepare build scripts
|
||||
run: cd ${{ github.workspace }}/release && yarn && yarn build
|
||||
|
||||
- name: Build sample app compatibility data
|
||||
uses: actions/github-script@v7
|
||||
id: build-sample-apps-data
|
||||
with:
|
||||
script: | # js
|
||||
const {
|
||||
isReleaseBranch,
|
||||
getMajorVersionNumberFromReleaseBranch,
|
||||
} = require('${{ github.workspace }}/release/dist/index.cjs');
|
||||
|
||||
const testSuites = JSON.parse(process.env.TEST_SUITES);
|
||||
const testSuitesToRunInMasterOnly = JSON.parse(process.env.TEST_SUITES_TO_RUN_IN_MASTER_ONLY);
|
||||
|
||||
const ref = '${{ github.base_ref || github.ref }}';
|
||||
const branchName = ref.replace(/^refs\/heads\//, '');
|
||||
|
||||
const isMainBranchRef = branchName === 'master';
|
||||
const isReleaseBranchRef = isReleaseBranch(branchName);
|
||||
|
||||
if (isMainBranchRef) {
|
||||
return {
|
||||
branch_name: '${{ env.SAMPLE_APP_MAIN_BRANCH_NAME }}',
|
||||
matrix: testSuites,
|
||||
};
|
||||
}
|
||||
|
||||
if (isReleaseBranchRef) {
|
||||
const supportedReleasesCount = Number(${{ env.SUPPORTED_RELEASES_COUNT }});
|
||||
|
||||
const currentReleaseVersion = Number(${{ vars.CURRENT_VERSION }});
|
||||
const currentBranchVersion = Number(getMajorVersionNumberFromReleaseBranch(branchName));
|
||||
|
||||
const sampleAppBranchName = `${currentBranchVersion}-${{ env.SAMPLE_APP_STABLE_RELEASE_BRANCH_NAME_SUFFIX }}`
|
||||
|
||||
const shouldRunTestsForVersion = (currentReleaseVersion - currentBranchVersion) <= (supportedReleasesCount - 1);
|
||||
const releaseBranchTestSuites = testSuites.filter(testSuite => !testSuitesToRunInMasterOnly.includes(testSuite));
|
||||
|
||||
return {
|
||||
branch_name: sampleAppBranchName,
|
||||
matrix: shouldRunTestsForVersion
|
||||
? releaseBranchTestSuites
|
||||
: []
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
branch_name: '',
|
||||
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
|
||||
# - runs its Cypress tests against the local metabase.jar and local @metabase/embedding-sdk-react package.
|
||||
# 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-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.build.result == 'success'
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 45
|
||||
name: e2e-sample-app-${{ matrix.test_suite }}-tests-${{ matrix.environment }}-bundle
|
||||
env:
|
||||
MB_EDITION: ee
|
||||
DISPLAY: ""
|
||||
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 }}
|
||||
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 }}
|
||||
SAMPLE_APP_ENVIRONMENT: ${{ matrix.environment }}
|
||||
permissions:
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# We have to use the latest version that does not have this issue https://github.com/docker/compose/pull/12752
|
||||
- name: Set up Docker Compose
|
||||
uses: docker/setup-compose-action@v1
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- name: Get Sample App name
|
||||
id: get-sample-app-name
|
||||
shell: bash
|
||||
run: |
|
||||
test_suite="${{ matrix.test_suite }}"
|
||||
echo "sample_app_name=${test_suite%-e2e}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Retrieve uberjar artifact for ee
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: metabase-ee-${{ github.event.pull_request.head.sha || github.sha }}-uberjar
|
||||
|
||||
- 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 Cypress environment
|
||||
id: cypress-prep
|
||||
uses: ./.github/actions/prepare-cypress
|
||||
|
||||
- name: Check if sample app branch exists
|
||||
id: check-sample-app-branch
|
||||
run: |
|
||||
TEST_SUITE="${{ matrix.test_suite }}"
|
||||
REPO_NAME="${TEST_SUITE%-e2e}"
|
||||
REPO_URL="https://github.com/metabase/${REPO_NAME}.git"
|
||||
echo "Checking if branch '${SAMPLE_APP_BRANCH_NAME}' exists in ${REPO_URL}..."
|
||||
if git ls-remote --exit-code --heads "$REPO_URL" "${SAMPLE_APP_BRANCH_NAME}"; then
|
||||
echo "sample_app_branch_exists=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "sample_app_branch_exists=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Configure AWS credentials
|
||||
if: ${{ matrix.test_suite == 'shoppy-e2e' }}
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
role-to-assume: ${{ secrets.SHOPPY_IAM_ROLE }}
|
||||
role-session-name: GitHub_to_AWS_via_FederatedOIDC
|
||||
aws-region: us-east-1
|
||||
|
||||
- name: Download Shoppy's App DB PostgreSQL dump from S3
|
||||
if: ${{ matrix.test_suite == 'shoppy-e2e' }}
|
||||
run: |
|
||||
aws s3 cp s3://${{ secrets.SHOPPY_METABASE_APP_DB_S3_BUCKET }}/${{ secrets.SHOPPY_METABASE_APP_DB_S3_KEY }} ./e2e/tmp/db_dumps/shoppy_metabase_app_db_dump.sql
|
||||
|
||||
- name: Prepare and launch Sample Apps for shoppy-e2e test suite
|
||||
if: ${{ steps.check-sample-app-branch.outputs.sample_app_branch_exists == 'true' && matrix.test_suite == 'shoppy-e2e' }}
|
||||
env:
|
||||
METABASE_APP_DB_USER: ${{ secrets.SHOPPY_METABASE_APP_DB_USER }}
|
||||
METABASE_APP_DB: ${{ secrets.SHOPPY_METABASE_APP_DB_DATABASE }}
|
||||
run: npx tsx ./e2e/runner/embedding-sdk/sample-apps/start-ci.ts shoppy-e2e
|
||||
|
||||
- name: Prepare and launch Sample Apps for other test suites
|
||||
if: ${{ steps.check-sample-app-branch.outputs.sample_app_branch_exists == 'true' && matrix.test_suite != 'shoppy-e2e' }}
|
||||
run: npx tsx ./e2e/runner/embedding-sdk/sample-apps/start-ci.ts ${{ matrix.test_suite }}
|
||||
|
||||
- name: Run e2e tests for Sample App
|
||||
id: run-e2e-tests
|
||||
if: ${{ steps.check-sample-app-branch.outputs.sample_app_branch_exists == 'true' }}
|
||||
continue-on-error: true
|
||||
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
|
||||
if: ${{ steps.check-sample-app-branch.outputs.sample_app_branch_exists == 'true' && steps.run-e2e-tests.outcome != 'success' }}
|
||||
with:
|
||||
name: cypress-recording-sample-app-${{ matrix.test_suite }}-latest
|
||||
path: |
|
||||
./e2e/tmp/${{ steps.get-sample-app-name.outputs.sample_app_name }}/cypress
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Fail job if tests failed
|
||||
if: ${{ steps.check-sample-app-branch.outputs.sample_app_branch_exists == 'true' && steps.run-e2e-tests.outcome != 'success' }}
|
||||
run: |
|
||||
exit 1
|
||||
|
||||
# The job checks integration of the Embedding SDK with consumer's apps that use different frameworks/bundlers,
|
||||
# or when we want to test some tricky integration cases like conflicting types.
|
||||
# The job
|
||||
# - starts an app
|
||||
# - runs its Cypress tests against the e2e test metabase instance and local @metabase/embedding-sdk-react package.
|
||||
# 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:
|
||||
[
|
||||
"vite-6-host-app-e2e",
|
||||
"next-15-app-router-host-app-e2e",
|
||||
"next-15-pages-router-host-app-e2e",
|
||||
"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
|
||||
env:
|
||||
MB_EDITION: ee
|
||||
DISPLAY: ""
|
||||
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 }}
|
||||
TZ: US/Pacific # to make node match the instance tz
|
||||
CYPRESS_CI: true
|
||||
HOST_APP_ENVIRONMENT: ${{ matrix.environment }}
|
||||
permissions:
|
||||
id-token: write
|
||||
|
||||
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
|
||||
|
||||
- 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 Cypress environment
|
||||
id: cypress-prep
|
||||
uses: ./.github/actions/prepare-cypress
|
||||
|
||||
- name: Prepare JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 21
|
||||
distribution: "temurin"
|
||||
|
||||
- name: Run Metabase
|
||||
run: node e2e/runner/run_cypress_ci.js start
|
||||
|
||||
- name: Generate database snapshots
|
||||
run: node e2e/runner/run_cypress_ci.js snapshot
|
||||
|
||||
- name: Prepare and launch Host App
|
||||
run: npx tsx ./e2e/runner/embedding-sdk/host-apps/start-ci.ts ${{ matrix.test_suite }}
|
||||
|
||||
- name: Run e2e tests for Host App
|
||||
id: run-e2e-tests
|
||||
continue-on-error: true
|
||||
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
|
||||
if: ${{ steps.run-e2e-tests.outcome != 'success' }}
|
||||
with:
|
||||
name: cypress-recording-host-app-${{ matrix.test_suite }}-${{ matrix.environment }}-latest
|
||||
path: |
|
||||
./cypress
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Fail job if tests failed
|
||||
if: ${{ steps.run-e2e-tests.outcome != 'success' }}
|
||||
run: |
|
||||
exit 1
|
||||
83
.github/workflows/e2e-stress-test-flake-fix.yml
vendored
83
.github/workflows/e2e-stress-test-flake-fix.yml
vendored
@@ -22,10 +22,14 @@ on:
|
||||
required: false
|
||||
default: "ee"
|
||||
qa_db:
|
||||
description: "Whether to set up and include external QA databases"
|
||||
type: boolean
|
||||
description: "Select the type of Database you want to run your tests with"
|
||||
type: choice
|
||||
required: true
|
||||
default: false
|
||||
default: "none"
|
||||
options:
|
||||
- none
|
||||
- sql
|
||||
- mongo
|
||||
enable_network_throttling:
|
||||
description: "Enable network throttling simulation (4g slow like in chrome dev tools)"
|
||||
type: boolean
|
||||
@@ -33,26 +37,26 @@ on:
|
||||
default: true
|
||||
|
||||
jobs:
|
||||
determine-test-type:
|
||||
name: Determine test type
|
||||
determine-suite:
|
||||
name: Determine test suite
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
type: ${{ steps.detect.outputs.type }}
|
||||
suite: ${{ steps.detect.outputs.suite }}
|
||||
steps:
|
||||
- name: Detect test type from spec path
|
||||
- name: Detect suite from spec path
|
||||
id: detect
|
||||
run: |
|
||||
SPEC='${{ inputs.spec }}'
|
||||
if [[ "$SPEC" == e2e/test-component/scenarios/embedding-sdk/* ]]; then
|
||||
echo "type=component" >> $GITHUB_OUTPUT
|
||||
echo "suite=component" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "type=e2e" >> $GITHUB_OUTPUT
|
||||
echo "suite=e2e" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
workflow-summary:
|
||||
name: Stress test inputs
|
||||
needs: [determine-test-type]
|
||||
needs: [determine-suite]
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
@@ -62,7 +66,7 @@ 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 '- `test_suite` (inferred): ${{ needs.determine-suite.outputs.suite }}' >> $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
|
||||
@@ -71,20 +75,26 @@ jobs:
|
||||
echo 'triggered by: @${{ github.event.sender.login }}' >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
stress-test-flake-fix:
|
||||
needs: [determine-test-type]
|
||||
needs: [determine-suite]
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 60
|
||||
name: Stress test E2E flake fix
|
||||
env:
|
||||
DISPLAY: "" # https://github.com/cypress-io/cypress/issues/4034
|
||||
TERM: xterm
|
||||
TZ: US/Pacific # to make Node match the instance tz
|
||||
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 }}
|
||||
MB_SNOWPLOW_AVAILABLE: true
|
||||
MB_SNOWPLOW_URL: "http://localhost:9090" # Snowplow micro
|
||||
MB_EDITION: ${{ inputs.mb_edition }}
|
||||
CYPRESS_ENABLE_NETWORK_THROTTLING: ${{ inputs.enable_network_throttling }}
|
||||
TERM: xterm
|
||||
TZ: US/Pacific # to make node match the instance tz
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -99,9 +109,9 @@ jobs:
|
||||
maildev: true
|
||||
openldap: true
|
||||
snowplow: true
|
||||
postgres: ${{ inputs.qa_db }}
|
||||
mysql: ${{ inputs.qa_db }}
|
||||
mongo: ${{ inputs.qa_db }}
|
||||
postgres: ${{ inputs.qa_db == 'sql' }}
|
||||
mysql: ${{ inputs.qa_db == 'sql' }}
|
||||
mongo: ${{ inputs.qa_db == 'mongo' }}
|
||||
|
||||
- name: Download Metabase ${{ matrix.edition }} uberjar
|
||||
uses: ./.github/actions/e2e-download-uberjar
|
||||
@@ -110,45 +120,40 @@ jobs:
|
||||
|
||||
- name: Prepare front-end environment
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
|
||||
- name: Prepare back-end environment
|
||||
if: ${{ needs.determine-suite.outputs.suite == '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: bun run build-embedding-sdk-package
|
||||
if: ${{ needs.determine-suite.outputs.suite == 'component' }}
|
||||
run: yarn build-embedding-sdk-package
|
||||
|
||||
- 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: Compile CLJS
|
||||
run: bun run build-pure:cljs
|
||||
|
||||
- name: Run Metabase
|
||||
run: node e2e/runner/run_cypress_ci.js start
|
||||
|
||||
- name: Generate default database snapshot
|
||||
if: inputs.qa_db == false # true boolean, do not use string here!
|
||||
run: |
|
||||
node e2e/runner/run_cypress_ci.js snapshot \
|
||||
--spec "e2e/snapshot-creators/default.cy.snap.js"
|
||||
|
||||
- name: Generate all database snapshots
|
||||
if: inputs.qa_db
|
||||
run: |
|
||||
node e2e/runner/run_cypress_ci.js snapshot
|
||||
- name: Generate database snapshots
|
||||
run: node e2e/runner/run_cypress_ci.js snapshot
|
||||
|
||||
# For all options and fine-grained control, take a look at the documentation
|
||||
# https://github.com/cypress-io/cypress/tree/develop/npm/grep
|
||||
- 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 ${{ needs.determine-suite.outputs.suite }} \
|
||||
--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
|
||||
|
||||
77
.github/workflows/e2e-test.yml
vendored
77
.github/workflows/e2e-test.yml
vendored
@@ -29,29 +29,36 @@ on:
|
||||
required: false
|
||||
type: number
|
||||
|
||||
env:
|
||||
DISPLAY: "" # https://github.com/cypress-io/cypress/issues/4034
|
||||
TERM: xterm
|
||||
TZ: US/Pacific # to make Node match the instance tz
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
HASH: ${{ github.event.pull_request.head.sha || github.sha }}-${{ github.run_attempt }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number || '' }}
|
||||
JOB_NAME: ${{ inputs.name }}
|
||||
|
||||
jobs:
|
||||
e2e-tests:
|
||||
runs-on: ${{ inputs.runner }}
|
||||
timeout-minutes: 40
|
||||
timeout-minutes: 45
|
||||
name: e2e-tests-${{ inputs.name }}-${{ inputs.edition }}
|
||||
env:
|
||||
MB_EDITION: ${{ inputs.edition }}
|
||||
# Sensitive env vars (tokens, secrets) starting with `CYPRESS_` are accessed via async `cy.env()`
|
||||
# Example: `cy.env(["FOO"]).then(({ FOO }) => ...)`
|
||||
# For non-sensitive config, use `Cypress.expose()` in cypress config instead (synchronous)
|
||||
DISPLAY: ""
|
||||
QA_DB_ENABLED: true
|
||||
PR_NUMBER: ${{ github.event.pull_request.number || '' }}
|
||||
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_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
|
||||
MB_SEARCH_ENGINE: appdb
|
||||
MB_SNOWPLOW_AVAILABLE: true
|
||||
MB_SNOWPLOW_URL: "http://localhost:9090" # Snowplow micro
|
||||
TZ: US/Pacific # to make node match the instance tz
|
||||
CYPRESS_CI: true
|
||||
CYPRESS_QA_DB_MONGO: ${{ inputs.name == 'mongo' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -73,22 +80,25 @@ jobs:
|
||||
openldap: true
|
||||
webhook: true
|
||||
snowplow: true
|
||||
postgres: true
|
||||
mysql: true
|
||||
mongo: true
|
||||
postgres: ${{ inputs.name != 'mongo' }}
|
||||
mysql: ${{ inputs.name != 'mongo' }}
|
||||
mongo: ${{ inputs.name == 'mongo' }}
|
||||
|
||||
- name: Retrieve uberjar artifact for ${{ inputs.edition }}
|
||||
uses: ./.github/actions/fetch-artifact
|
||||
with:
|
||||
commit: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
edition: ${{ inputs.edition }}
|
||||
name: metabase-${{ inputs.edition }}-${{ github.event.pull_request.head.sha || github.sha }}-uberjar
|
||||
extract-path: "target/uberjar"
|
||||
output-name: "metabase.jar"
|
||||
|
||||
- name: Prepare front-end environment
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
|
||||
- name: Prepare JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 21
|
||||
distribution: "temurin"
|
||||
|
||||
- name: Prepare Cypress environment
|
||||
id: cypress-prep
|
||||
@@ -106,19 +116,13 @@ jobs:
|
||||
if: github.run_attempt != '1'
|
||||
run: echo "specs=$(cat failed-specs)" >> $GITHUB_ENV
|
||||
|
||||
- name: Compile CLJS
|
||||
run: bun run build-pure:cljs
|
||||
|
||||
- name: Run Metabase
|
||||
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 }}
|
||||
id: split-e2e
|
||||
if: ${{ !inputs.specs }}
|
||||
env:
|
||||
SPLIT: ${{ inputs.total_chunks }}
|
||||
@@ -129,30 +133,29 @@ 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+-@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
|
||||
|
||||
- name: Run Tagged EE Cypress tests on ${{ inputs.name }}
|
||||
id: tagged-e2e
|
||||
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.
|
||||
@@ -172,12 +175,10 @@ jobs:
|
||||
# Related to Trunk uploader
|
||||
variant: e2e-tests
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
previous-step-outcome: ${{ inputs.specs && steps.tagged-e2e.outcome || steps.split-e2e.outcome }}
|
||||
|
||||
- name: Upload Cypress Artifacts upon failure
|
||||
uses: actions/upload-artifact@v4
|
||||
if: |
|
||||
!cancelled() && (steps.split-e2e.outcome == 'failure' || steps.tagged-e2e.outcome == 'failure')
|
||||
if: failure()
|
||||
with:
|
||||
name: cypress-recording-${{ inputs.name }}-${{ inputs.edition }}
|
||||
path: |
|
||||
@@ -187,12 +188,10 @@ jobs:
|
||||
|
||||
- name: Upload Failed Tests
|
||||
uses: actions/upload-artifact@v4
|
||||
if: |
|
||||
!cancelled() && (steps.split-e2e.outcome == 'failure' || steps.tagged-e2e.outcome == 'failure')
|
||||
if: failure()
|
||||
with:
|
||||
name: failed-tests-${{ inputs.name }}-${{ inputs.edition }}
|
||||
path: ./cypress/test-results
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Extract chunk-specific changed timings
|
||||
id: extract-timings
|
||||
|
||||
282
.github/workflows/embedding-sdk-component-tests.yml
vendored
282
.github/workflows/embedding-sdk-component-tests.yml
vendored
@@ -1,282 +0,0 @@
|
||||
name: Embedding SDK Component Tests
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
env:
|
||||
DISPLAY: "" # https://github.com/cypress-io/cypress/issues/4034
|
||||
TERM: xterm
|
||||
TZ: US/Pacific # to make Node match the instance tz
|
||||
MB_EDITION: "ee"
|
||||
|
||||
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:
|
||||
# Sensitive env vars (tokens, secrets) starting with `CYPRESS_` are accessed via async `cy.env()`
|
||||
# Example: `cy.env(["FOO"]).then(({ FOO }) => ...)`
|
||||
# For non-sensitive config, use `Cypress.expose()` in cypress config instead (synchronous)
|
||||
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 }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Prepare Docker containers
|
||||
uses: ./.github/actions/e2e-prepare-containers
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
maildev: true
|
||||
openldap: false
|
||||
webhook: false
|
||||
snowplow: false
|
||||
postgres: false
|
||||
mysql: false
|
||||
mongo: false
|
||||
|
||||
- name: Retrieve uberjar artifact for ee
|
||||
uses: ./.github/actions/fetch-artifact
|
||||
with:
|
||||
commit: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
edition: ee
|
||||
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 back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
|
||||
- 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 }}
|
||||
# Component tests only need the default snapshot
|
||||
run: |
|
||||
node e2e/runner/run_cypress_ci.js snapshot \
|
||||
--spec "e2e/snapshot-creators/default.cy.snap.js"
|
||||
|
||||
# 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
|
||||
id: run-tests
|
||||
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 }}
|
||||
previous-step-outcome: ${{ steps.run-tests.outcome }}
|
||||
|
||||
- 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_RUN_MODE: "e2e"
|
||||
METASTORE_DEV_SERVER_URL: ${{ secrets.METASTORE_DEV_SERVER_URL }}
|
||||
# Sensitive env vars (tokens, secrets) starting with `CYPRESS_` are accessed via async `cy.env()`
|
||||
# Example: `cy.env(["FOO"]).then(({ FOO }) => ...)`
|
||||
# For non-sensitive config, use `Cypress.expose()` in cypress config instead (synchronous)
|
||||
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 }}
|
||||
|
||||
steps:
|
||||
- name: Check out the code from the previous commit
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ needs.decide-target-ref.outputs.target-ref }}
|
||||
|
||||
- name: Prepare Docker containers
|
||||
uses: ./.github/actions/e2e-prepare-containers
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
maildev: true
|
||||
openldap: false
|
||||
webhook: false
|
||||
snowplow: false
|
||||
postgres: false
|
||||
mysql: false
|
||||
mongo: false
|
||||
|
||||
- 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: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
|
||||
- name: Attempt to retrieve the ee uberjar artifact from the previous commit
|
||||
id: download-uberjar
|
||||
uses: ./.github/actions/fetch-artifact
|
||||
with:
|
||||
commit: ${{ steps.get-commit-sha.outputs.sha }}
|
||||
edition: ee
|
||||
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 }}
|
||||
# Component tests only need the default snapshot
|
||||
run: |
|
||||
node e2e/runner/run_cypress_ci.js snapshot \
|
||||
--spec "e2e/snapshot-creators/default.cy.snap.js"
|
||||
|
||||
- name: Run component tests for Embedding SDK
|
||||
id: run-tests
|
||||
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 }}
|
||||
previous-step-outcome: ${{ steps.run-tests.outcome }}
|
||||
|
||||
- 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();
|
||||
@@ -1,419 +0,0 @@
|
||||
name: "[NOT REQUIRED] SDK Compatibility tests"
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
env:
|
||||
DISPLAY: "" # https://github.com/cypress-io/cypress/issues/4034
|
||||
TERM: xterm
|
||||
TZ: US/Pacific # to make Node match the instance tz
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
HASH: ${{ github.event.pull_request.head.sha || github.sha }}-${{ github.run_attempt }}
|
||||
MB_EDITION: ee
|
||||
jobs:
|
||||
get-sample-apps-data:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
# we must support the last two stable releases
|
||||
SUPPORTED_RELEASES_COUNT: 2
|
||||
TEST_SUITES: |
|
||||
[
|
||||
"metabase-nodejs-react-sdk-embedding-sample-e2e",
|
||||
"metabase-nextjs-sdk-embedding-sample-e2e"
|
||||
]
|
||||
SAMPLE_APP_MAIN_BRANCH_NAME: "main"
|
||||
SAMPLE_APP_STABLE_RELEASE_BRANCH_NAME_SUFFIX: "stable"
|
||||
outputs:
|
||||
branch_name: ${{ fromJson(steps.build-sample-apps-data.outputs.result).branch_name }}
|
||||
matrix: ${{ toJSON(fromJson(steps.build-sample-apps-data.outputs.result).matrix) }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Prepare release scripts
|
||||
uses: ./.github/actions/prepare-release-scripts
|
||||
|
||||
- name: Build sample app compatibility data
|
||||
uses: actions/github-script@v7
|
||||
id: build-sample-apps-data
|
||||
with:
|
||||
script: | # js
|
||||
const {
|
||||
isReleaseBranch,
|
||||
getMajorVersionNumberFromReleaseBranch,
|
||||
} = require('${{ github.workspace }}/release/dist/index.cjs');
|
||||
|
||||
const testSuites = JSON.parse(process.env.TEST_SUITES);
|
||||
|
||||
const ref = '${{ github.base_ref || github.ref }}';
|
||||
const branchName = ref.replace(/^refs\/heads\//, '');
|
||||
|
||||
const isMainBranchRef = branchName === 'master';
|
||||
const isReleaseBranchRef = isReleaseBranch(branchName);
|
||||
|
||||
if (isMainBranchRef) {
|
||||
return {
|
||||
branch_name: '${{ env.SAMPLE_APP_MAIN_BRANCH_NAME }}',
|
||||
matrix: testSuites,
|
||||
};
|
||||
}
|
||||
|
||||
if (isReleaseBranchRef) {
|
||||
const supportedReleasesCount = Number(${{ env.SUPPORTED_RELEASES_COUNT }});
|
||||
|
||||
const currentReleaseVersion = Number(${{ vars.CURRENT_VERSION }});
|
||||
const currentBranchVersion = Number(getMajorVersionNumberFromReleaseBranch(branchName));
|
||||
|
||||
const sampleAppBranchName = `${currentBranchVersion}-${{ env.SAMPLE_APP_STABLE_RELEASE_BRANCH_NAME_SUFFIX }}`
|
||||
|
||||
const shouldRunTestsForVersion = (currentReleaseVersion - currentBranchVersion) <= (supportedReleasesCount - 1);
|
||||
|
||||
return {
|
||||
branch_name: sampleAppBranchName,
|
||||
matrix: shouldRunTestsForVersion ? testSuites : [],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
branch_name: '',
|
||||
matrix: [],
|
||||
};
|
||||
|
||||
# In order to check compatibility between Sample Apps and Embedding SDK, the job for each sample app:
|
||||
# - pulls a Sample App
|
||||
# - starts it
|
||||
# - runs its Cypress tests against the local metabase.jar and local @metabase/embedding-sdk-react package.
|
||||
# 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]
|
||||
strategy:
|
||||
matrix:
|
||||
test_suite: ${{ fromJSON(needs.get-sample-apps-data.outputs.matrix) }}
|
||||
environment: ["production", "development"]
|
||||
if: |
|
||||
!cancelled() && needs.get-sample-apps-data.result == 'success'
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 45
|
||||
name: e2e-sample-app-${{ matrix.test_suite }}-tests-${{ matrix.environment }}-bundle
|
||||
env:
|
||||
MB_EDITION: ee
|
||||
MB_RUN_MODE: e2e
|
||||
METASTORE_DEV_SERVER_URL: ${{ secrets.METASTORE_DEV_SERVER_URL }}
|
||||
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ secrets.STAGING_MB_ALL_FEATURES_TOKEN }}
|
||||
SAMPLE_APP_BRANCH_NAME: ${{ needs.get-sample-apps-data.outputs.branch_name }}
|
||||
SAMPLE_APP_ENVIRONMENT: ${{ matrix.environment }}
|
||||
permissions:
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: ./.github/actions/maximize-disk-space
|
||||
|
||||
# We have to use the latest version that does not have this issue https://github.com/docker/compose/pull/12752
|
||||
- name: Set up Docker Compose
|
||||
uses: docker/setup-compose-action@v1
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- name: Get Sample App name
|
||||
id: get-sample-app-name
|
||||
shell: bash
|
||||
run: |
|
||||
test_suite="${{ matrix.test_suite }}"
|
||||
echo "sample_app_name=${test_suite%-e2e}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Retrieve uberjar artifact for ee
|
||||
uses: ./.github/actions/fetch-artifact
|
||||
with:
|
||||
commit: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
edition: ee
|
||||
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 Cypress environment
|
||||
id: cypress-prep
|
||||
uses: ./.github/actions/prepare-cypress
|
||||
|
||||
- name: Check if sample app branch exists
|
||||
id: check-sample-app-branch
|
||||
run: |
|
||||
TEST_SUITE="${{ matrix.test_suite }}"
|
||||
REPO_NAME="${TEST_SUITE%-e2e}"
|
||||
REPO_URL="https://github.com/metabase/${REPO_NAME}.git"
|
||||
echo "Checking if branch '${SAMPLE_APP_BRANCH_NAME}' exists in ${REPO_URL}..."
|
||||
if git ls-remote --exit-code --heads "$REPO_URL" "${SAMPLE_APP_BRANCH_NAME}"; then
|
||||
echo "sample_app_branch_exists=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "sample_app_branch_exists=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Prepare and launch Sample Apps
|
||||
if: ${{ steps.check-sample-app-branch.outputs.sample_app_branch_exists == 'true' }}
|
||||
run: npx tsx ./e2e/runner/embedding-sdk/sample-apps/start-ci.ts ${{ matrix.test_suite }}
|
||||
|
||||
- name: Prune build cache
|
||||
run: docker builder prune -af
|
||||
|
||||
- name: Log free disk space
|
||||
run: df -h /
|
||||
|
||||
- name: Run e2e tests for Sample App
|
||||
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 }}
|
||||
|
||||
- name: Upload Cypress Artifacts upon failure
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ steps.check-sample-app-branch.outputs.sample_app_branch_exists == 'true' && steps.run-e2e-tests.outcome != 'success' }}
|
||||
with:
|
||||
name: cypress-recording-sample-app-${{ matrix.test_suite }}-${{ matrix.environment }}-latest
|
||||
path: |
|
||||
./e2e/tmp/${{ steps.get-sample-app-name.outputs.sample_app_name }}/cypress
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Fail job if tests failed
|
||||
if: ${{ steps.check-sample-app-branch.outputs.sample_app_branch_exists == 'true' && steps.run-e2e-tests.outcome != 'success' }}
|
||||
run: |
|
||||
exit 1
|
||||
|
||||
# Shoppy e2e tests run separately using the shared composite action for setup.
|
||||
e2e-shoppy-tests:
|
||||
needs: [get-sample-apps-data]
|
||||
strategy:
|
||||
matrix:
|
||||
environment: ["production", "development"]
|
||||
if: |
|
||||
!cancelled() && needs.get-sample-apps-data.result == 'success' &&
|
||||
(github.ref == 'refs/heads/master' || github.base_ref == 'master')
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 45
|
||||
name: e2e-sample-app-shoppy-e2e-tests-${{ matrix.environment }}-bundle
|
||||
env:
|
||||
MB_EDITION: ee
|
||||
MB_RUN_MODE: e2e
|
||||
METASTORE_DEV_SERVER_URL: ${{ secrets.METASTORE_DEV_SERVER_URL }}
|
||||
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ secrets.STAGING_MB_ALL_FEATURES_TOKEN }}
|
||||
SAMPLE_APP_BRANCH_NAME: ${{ needs.get-sample-apps-data.outputs.branch_name }}
|
||||
SAMPLE_APP_ENVIRONMENT: ${{ matrix.environment }}
|
||||
permissions:
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Prepare Shoppy E2E environment
|
||||
uses: ./.github/actions/prepare-shoppy-e2e
|
||||
id: shoppy-prep
|
||||
with:
|
||||
commit: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
sample-app-branch-name: ${{ needs.get-sample-apps-data.outputs.branch_name }}
|
||||
sample-app-environment: ${{ matrix.environment }}
|
||||
staging-mb-all-features-token: ${{ secrets.STAGING_MB_ALL_FEATURES_TOKEN }}
|
||||
shoppy-iam-role: ${{ secrets.SHOPPY_IAM_ROLE }}
|
||||
shoppy-metabase-app-db-s3-bucket: ${{ secrets.SHOPPY_METABASE_APP_DB_S3_BUCKET }}
|
||||
shoppy-metabase-app-db-s3-key: ${{ secrets.SHOPPY_METABASE_APP_DB_S3_KEY }}
|
||||
shoppy-metabase-app-db-user: ${{ secrets.SHOPPY_METABASE_APP_DB_USER }}
|
||||
shoppy-metabase-app-db-database: ${{ secrets.SHOPPY_METABASE_APP_DB_DATABASE }}
|
||||
metastore-dev-server-url: ${{ secrets.METASTORE_DEV_SERVER_URL }}
|
||||
shoppy-datadog-application-id: ${{ secrets.SHOPPY_METABASE_APP_DATADOG_APPLICATION_ID }}
|
||||
shoppy-datadog-client-token: ${{ secrets.SHOPPY_METABASE_APP_DATADOG_CLIENT_TOKEN }}
|
||||
|
||||
- name: Run e2e tests for Shoppy
|
||||
id: run-e2e-tests
|
||||
if: ${{ steps.shoppy-prep.outputs.sample_app_branch_exists == 'true' }}
|
||||
continue-on-error: true
|
||||
env:
|
||||
CYPRESS_BROWSER: ${{ steps.shoppy-prep.outputs.chrome-path }}
|
||||
run: node e2e/runner/run_cypress_ci.js shoppy-e2e
|
||||
|
||||
- name: Upload Cypress Artifacts upon failure
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ steps.shoppy-prep.outputs.sample_app_branch_exists == 'true' && steps.run-e2e-tests.outcome != 'success' }}
|
||||
with:
|
||||
name: cypress-recording-sample-app-shoppy-e2e-${{ matrix.environment }}-latest
|
||||
path: |
|
||||
./e2e/tmp/shoppy/cypress
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Fail job if tests failed
|
||||
if: ${{ steps.shoppy-prep.outputs.sample_app_branch_exists == 'true' && steps.run-e2e-tests.outcome != 'success' }}
|
||||
run: |
|
||||
exit 1
|
||||
|
||||
# Synthetic monitoring tests run only on master merges, in parallel with the regular e2e tests.
|
||||
# They use the shoppy production environment and include performance measurement tests.
|
||||
e2e-shoppy-synthetic-tests:
|
||||
needs: [get-sample-apps-data]
|
||||
if: |
|
||||
!cancelled() && needs.get-sample-apps-data.result == 'success' &&
|
||||
github.event_name == 'push' &&
|
||||
github.ref == 'refs/heads/master'
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 45
|
||||
name: e2e-shoppy-synthetic-monitoring-tests
|
||||
env:
|
||||
MB_EDITION: ee
|
||||
MB_RUN_MODE: e2e
|
||||
METASTORE_DEV_SERVER_URL: ${{ secrets.METASTORE_DEV_SERVER_URL }}
|
||||
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ secrets.STAGING_MB_ALL_FEATURES_TOKEN }}
|
||||
SAMPLE_APP_BRANCH_NAME: ${{ needs.get-sample-apps-data.outputs.branch_name }}
|
||||
SAMPLE_APP_ENVIRONMENT: production
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Prepare Shoppy E2E environment
|
||||
uses: ./.github/actions/prepare-shoppy-e2e
|
||||
id: shoppy-prep
|
||||
with:
|
||||
commit: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
sample-app-branch-name: ${{ needs.get-sample-apps-data.outputs.branch_name }}
|
||||
sample-app-environment: production
|
||||
staging-mb-all-features-token: ${{ secrets.STAGING_MB_ALL_FEATURES_TOKEN }}
|
||||
shoppy-iam-role: ${{ secrets.SHOPPY_IAM_ROLE }}
|
||||
shoppy-metabase-app-db-s3-bucket: ${{ secrets.SHOPPY_METABASE_APP_DB_S3_BUCKET }}
|
||||
shoppy-metabase-app-db-s3-key: ${{ secrets.SHOPPY_METABASE_APP_DB_S3_KEY }}
|
||||
shoppy-metabase-app-db-user: ${{ secrets.SHOPPY_METABASE_APP_DB_USER }}
|
||||
shoppy-metabase-app-db-database: ${{ secrets.SHOPPY_METABASE_APP_DB_DATABASE }}
|
||||
metastore-dev-server-url: ${{ secrets.METASTORE_DEV_SERVER_URL }}
|
||||
shoppy-datadog-application-id: ${{ secrets.SHOPPY_METABASE_APP_DATADOG_APPLICATION_ID }}
|
||||
shoppy-datadog-client-token: ${{ secrets.SHOPPY_METABASE_APP_DATADOG_CLIENT_TOKEN }}
|
||||
|
||||
- name: Get merged PR number
|
||||
if: ${{ steps.shoppy-prep.outputs.sample_app_branch_exists == 'true' }}
|
||||
id: get-merged-pr-number
|
||||
run: |
|
||||
PR=$(gh api repos/${{ github.repository }}/commits/${{ github.sha }}/pulls --jq '.[0].number' 2>/dev/null || echo "")
|
||||
if [ -z "$PR" ] || [ "$PR" = "null" ]; then
|
||||
echo "number=" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "number=$PR" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Run synthetic monitoring tests
|
||||
id: run-e2e-tests
|
||||
if: ${{ steps.shoppy-prep.outputs.sample_app_branch_exists == 'true' }}
|
||||
continue-on-error: true
|
||||
env:
|
||||
CYPRESS_BROWSER: ${{ steps.shoppy-prep.outputs.chrome-path }}
|
||||
PR_NUMBER: ${{ steps.get-merged-pr-number.outputs.number }}
|
||||
INCLUDE_SYNTHETIC_MONITORING_TESTS: "true"
|
||||
run: node e2e/runner/run_cypress_ci.js shoppy-e2e
|
||||
|
||||
- name: Upload Cypress Artifacts upon failure
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ steps.shoppy-prep.outputs.sample_app_branch_exists == 'true' && steps.run-e2e-tests.outcome != 'success' }}
|
||||
with:
|
||||
name: cypress-recording-shoppy-synthetic-tests-latest
|
||||
path: |
|
||||
./e2e/tmp/shoppy/cypress
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Fail job if tests failed
|
||||
if: ${{ steps.shoppy-prep.outputs.sample_app_branch_exists == 'true' && steps.run-e2e-tests.outcome != 'success' }}
|
||||
run: |
|
||||
exit 1
|
||||
|
||||
# The job checks integration of the Embedding SDK with consumer's apps that use different frameworks/bundlers,
|
||||
# or when we want to test some tricky integration cases like conflicting types.
|
||||
# The job
|
||||
# - starts an app
|
||||
# - runs its Cypress tests against the e2e test metabase instance and local @metabase/embedding-sdk-react package.
|
||||
# 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:
|
||||
strategy:
|
||||
matrix:
|
||||
test_suite:
|
||||
[
|
||||
"vite-6-host-app-e2e",
|
||||
"next-15-app-router-host-app-e2e",
|
||||
"next-15-pages-router-host-app-e2e",
|
||||
"angular-20-host-app-e2e",
|
||||
]
|
||||
environment: ["production", "development"]
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 45
|
||||
name: e2e-host-app-${{ matrix.test_suite }}-tests-${{ matrix.environment }}-bundle
|
||||
env:
|
||||
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ secrets.STAGING_MB_ALL_FEATURES_TOKEN }}
|
||||
HOST_APP_ENVIRONMENT: ${{ matrix.environment }}
|
||||
permissions:
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Retrieve uberjar artifact for ee
|
||||
uses: ./.github/actions/fetch-artifact
|
||||
with:
|
||||
commit: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
edition: ee
|
||||
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 back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
|
||||
- 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: Generate database snapshots
|
||||
run: |
|
||||
node e2e/runner/run_cypress_ci.js snapshot \
|
||||
--spec "e2e/snapshot-creators/default.cy.snap.js"
|
||||
|
||||
- name: Prepare and launch Host App
|
||||
run: npx tsx ./e2e/runner/embedding-sdk/host-apps/start-ci.ts ${{ matrix.test_suite }}
|
||||
|
||||
- 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 }}
|
||||
|
||||
- name: Upload Cypress Artifacts upon failure
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ steps.run-e2e-tests.outcome != 'success' }}
|
||||
with:
|
||||
name: cypress-recording-host-app-${{ matrix.test_suite }}-${{ matrix.environment }}-latest
|
||||
path: |
|
||||
./cypress
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Fail job if tests failed
|
||||
if: ${{ steps.run-e2e-tests.outcome != 'success' }}
|
||||
run: |
|
||||
exit 1
|
||||
@@ -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::"
|
||||
bun run 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
|
||||
165
.github/workflows/embedding-sdk.yml
vendored
165
.github/workflows/embedding-sdk.yml
vendored
@@ -2,43 +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
|
||||
bundle-size-check:
|
||||
description: "Whether to run bundle size check."
|
||||
required: true
|
||||
type: boolean
|
||||
skip:
|
||||
description: Whether to skip running tests due to one of the artifacts failing to build.
|
||||
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 && !inputs.skip
|
||||
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:
|
||||
@@ -51,10 +69,13 @@ jobs:
|
||||
name: embedding-sdk-${{ github.event.pull_request.head.sha || github.sha }}
|
||||
path: ${{ github.workspace }}/resources/embedding-sdk
|
||||
- name: Run frontend embedding SDK snippets type check
|
||||
run: bun run embedding-sdk:cli-snippets:type-check
|
||||
run: yarn run embedding-sdk:cli-snippets:type-check
|
||||
|
||||
embedding-sdk-docs-snippets-type-check:
|
||||
if: inputs.docs-snippets-type-check && !inputs.skip
|
||||
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:
|
||||
@@ -67,10 +88,13 @@ jobs:
|
||||
name: embedding-sdk-${{ github.event.pull_request.head.sha || github.sha }}
|
||||
path: ${{ github.workspace }}/resources/embedding-sdk
|
||||
- name: Run frontend embedding SDK snippets type check
|
||||
run: bun run embedding-sdk:docs-snippets:type-check
|
||||
run: yarn run embedding-sdk:docs-snippets:type-check
|
||||
|
||||
embedding-sdk-api-documentation-generation-validation:
|
||||
if: inputs.documentation-validation && !inputs.skip
|
||||
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:
|
||||
@@ -90,7 +114,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
set -o pipefail
|
||||
bun run embedding-sdk:docs:generate:pure --emit none 2>&1 \
|
||||
yarn embedding-sdk:docs:generate:pure --emit none 2>&1 \
|
||||
| tee /dev/stdout \
|
||||
| tee >( tail -n 10 \
|
||||
| sed '1s/^/output<<EOF\n/' \
|
||||
@@ -102,82 +126,43 @@ jobs:
|
||||
with:
|
||||
remove-previous-comment: true
|
||||
add-new-comment: ${{ steps.docs-generation.outcome == 'failure' && contains(steps.docs-generation.outputs.output, 'not included in the documentation') }}
|
||||
message: "📝 Some exported types from `@metabase/embedding-sdk-react` reference other types that are not exported, i.e. internal types.<br><br>The internal type reference breaks API documentation generation for the SDK and also may unexpectedly increase the output size of the type declaration file.<br><br>Please check the CI logs to find the name of such type.<br><br>- If a type should be publicly available in the SDK, add a description to it or a TypeDoc annotation and export it from SDK.<br>- If a type is internal and should _not_ be publicly exposed, refactor types to prevent referencing other types that are not exported from SDK.<br><br>You can run `bun run embedding-sdk:docs:generate` locally to verify that the issue has been resolved."
|
||||
message: "📝 Some exported types from `@metabase/embedding-sdk-react` reference other types that are not exported, i.e. internal types.<br><br>The internal type reference breaks API documentation generation for the SDK and also may unexpectedly increase the output size of the type declaration file.<br><br>Please check the CI logs to find the name of such type.<br><br>- If a type should be publicly available in the SDK, add a description to it or a TypeDoc annotation and export it from SDK.<br>- If a type is internal and should _not_ be publicly exposed, refactor types to prevent referencing other types that are not exported from SDK.<br><br>You can run `yarn embedding-sdk:docs:generate` locally to verify that the issue has been resolved."
|
||||
include-log: true
|
||||
- name: Comment on PR if 'Failed to resolve link' error happens
|
||||
uses: ./.github/actions/notify-pull-request
|
||||
with:
|
||||
remove-previous-comment: true
|
||||
add-new-comment: ${{ steps.docs-generation.outcome == 'failure' && contains(steps.docs-generation.outputs.output, 'Failed to resolve link') }}
|
||||
message: "📝 A typedoc `{@link <TYPE_NAME>}` annotation is found that can't be resolved.<br><br>Most likely it happens when the referenced type is not exported. Please check the CI logs to find the name of such type.<br><br>- If a referenced in `{@link}` type should be publicly available in the SDK, add a description to it or a TypeDoc annotation and export it from SDK.<br>- If a type is internal and should _not_ be publicly exposed, change types to prevent unexported type reference in `{@link}` annotation.<br><br>You can run `bun run embedding-sdk:docs:generate` locally to verify that the issue has been resolved."
|
||||
message: "📝 A typedoc `{@link <TYPE_NAME>}` annotation is found that can't be resolved.<br><br>Most likely it happens when the referenced type is not exported. Please check the CI logs to find the name of such type.<br><br>- If a referenced in `{@link}` type should be publicly available in the SDK, add a description to it or a TypeDoc annotation and export it from SDK.<br>- If a type is internal and should _not_ be publicly exposed, change types to prevent unexported type reference in `{@link}` annotation.<br><br>You can run `yarn embedding-sdk:docs:generate` locally to verify that the issue has been resolved."
|
||||
include-log: true
|
||||
- name: Fail Job
|
||||
if: ${{ steps.docs-generation.outcome == 'failure' }}
|
||||
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 && !inputs.skip
|
||||
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 && !inputs.skip
|
||||
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
|
||||
|
||||
embedding-sdk-bundle-size-check:
|
||||
if: inputs.bundle-size-check
|
||||
continue-on-error: true
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 20
|
||||
env:
|
||||
BUNDLE_SIZE_THRESHOLD_PERCENT: 2
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/sdk-bundle-size-check
|
||||
id: compare
|
||||
with:
|
||||
edition: ee
|
||||
base-ref-commit: ${{ github.event.pull_request.base.sha }}
|
||||
threshold-percent: ${{ env.BUNDLE_SIZE_THRESHOLD_PERCENT }}
|
||||
|
||||
- name: Comment on PR if bundle size has increased
|
||||
uses: ./.github/actions/notify-pull-request
|
||||
if: success() && steps.compare.outputs.status == 'increased'
|
||||
with:
|
||||
remove-previous-comment: true
|
||||
add-new-comment: true
|
||||
message: "⚠️ Embedding SDK bundle size increased by ${{ steps.compare.outputs.size_change_percent }}% compared to the ${{ github.base_ref }} branch (threshold: ${{ env.BUNDLE_SIZE_THRESHOLD_PERCENT }}%). Please contact Embedding team."
|
||||
include-log: false
|
||||
|
||||
- name: Send notification to Slack via Zapier
|
||||
if: success() && steps.compare.outputs.status == 'increased'
|
||||
shell: bash
|
||||
run: |
|
||||
curl -s -X POST "${{ secrets.EMBEDDING_ZAPIER_WEBHOOK_URL }}" \
|
||||
-d '⚠️ Embedding SDK bundle size increased by ${{ steps.compare.outputs.size_change_percent }}% (threshold: ${{ env.BUNDLE_SIZE_THRESHOLD_PERCENT }}%)
|
||||
|
||||
PR #${{ github.event.pull_request.number }} ${{ github.event.pull_request.title }} (${{ github.event.pull_request.html_url }})'
|
||||
|
||||
- name: Comment on PR if bundle size has decreased
|
||||
uses: ./.github/actions/notify-pull-request
|
||||
if: success() && steps.compare.outputs.status == 'decreased'
|
||||
with:
|
||||
remove-previous-comment: true
|
||||
add-new-comment: true
|
||||
message: "🎉 Embedding SDK bundle size decreased by ${{ steps.compare.outputs.size_change_percent }}% compared to the ${{ github.base_ref }} branch. Nice work!"
|
||||
include-log: false
|
||||
with:
|
||||
cached-embedding-sdk-dist-artifact-name: embedding-sdk-${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
embedding-sdk-tests-result:
|
||||
needs:
|
||||
- embedding-sdk-bundle-size-check
|
||||
- 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
|
||||
|
||||
@@ -44,12 +44,12 @@ jobs:
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
- name: Build cljs
|
||||
run: bun run build:cljs
|
||||
run: yarn build:cljs
|
||||
- name: Stress-test ${{ github.event.inputs.spec }} ${{ github.event.inputs.burn_in }} times
|
||||
run: |
|
||||
for i in {1..${{ github.event.inputs.burn_in }}}; do
|
||||
bun run jest --runInBand --ci --silent --no-cache \
|
||||
--testPathPatterns '${{ github.event.inputs.spec }}' \
|
||||
yarn jest --runInBand --ci --silent --no-cache \
|
||||
--testPathPattern '${{ github.event.inputs.spec }}' \
|
||||
--testNamePattern '${{ github.event.inputs.grep }}'
|
||||
if [[ "$?" != 0 ]]; then
|
||||
echo "Failed after $i attempts" && break;
|
||||
|
||||
22
.github/workflows/frontend.yml
vendored
22
.github/workflows/frontend.yml
vendored
@@ -42,19 +42,19 @@ jobs:
|
||||
- name: Restore ESLint cache
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: ${{ github.workspace}}/.eslintcache
|
||||
path: .eslintcache
|
||||
key: ${{ steps.get-cache-keys.outputs.eslint-cache-key }}
|
||||
|
||||
- name: Compile CLJS
|
||||
run: bun run build-pure:cljs
|
||||
run: yarn build-pure:cljs
|
||||
- name: Find CSS variables that are used but never defined
|
||||
run: bun run find-never-defined-css-variables
|
||||
run: yarn find-never-defined-css-variables
|
||||
- name: Run Prettier formatting linter
|
||||
run: bun run lint-prettier-pure
|
||||
run: yarn run lint-prettier-pure
|
||||
- name: Run ESLint linter
|
||||
run: bun run lint-eslint-pure
|
||||
run: yarn run lint-eslint-pure
|
||||
- name: Run CSS linter
|
||||
run: bun run lint-css
|
||||
run: yarn run lint-css
|
||||
|
||||
fe-type-check:
|
||||
if: ${{ !inputs.skip }}
|
||||
@@ -66,9 +66,9 @@ jobs:
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
- run: bun run build-pure:cljs
|
||||
- run: yarn build-pure:cljs
|
||||
name: Compile CLJS
|
||||
- run: bun run type-check-pure
|
||||
- run: yarn type-check-pure
|
||||
name: Check types
|
||||
|
||||
fe-tests-unit:
|
||||
@@ -90,8 +90,7 @@ jobs:
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
- name: Run frontend unit tests
|
||||
id: fe-tests
|
||||
run: bun run test-unit --silent --shard=${{ matrix.shard }}/${{ env.SHARDS }}
|
||||
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.
|
||||
@@ -107,7 +106,6 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
previous-step-outcome: ${{ steps.fe-tests.outcome }}
|
||||
|
||||
fe-tests-timezones:
|
||||
if: ${{ !inputs.skip }}
|
||||
@@ -120,7 +118,7 @@ jobs:
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
- name: Run frontend timezones tests
|
||||
run: bun run test-timezones
|
||||
run: yarn run test-timezones
|
||||
|
||||
fe-tests-result:
|
||||
needs:
|
||||
|
||||
56
.github/workflows/fuzzing.yml
vendored
Normal file
56
.github/workflows/fuzzing.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
name: Fuzzing
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- "master"
|
||||
- "release-**"
|
||||
paths:
|
||||
- "frontend/src/metabase/querying/expressions/**"
|
||||
pull_request:
|
||||
paths:
|
||||
- "frontend/src/metabase/querying/expressions/**"
|
||||
|
||||
jobs:
|
||||
fe-fuzz-lexifier:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 15
|
||||
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
|
||||
- run: yarn test-unit frontend/src/metabase/querying/expressions/pratt/fuzz.lexifier.unit.spec.ts
|
||||
env:
|
||||
MB_FUZZ: 1
|
||||
name: Run fuzz testing on the lexifier
|
||||
|
||||
fe-fuzz-compiler:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 15
|
||||
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
|
||||
- run: yarn test-unit frontend/src/metabase/querying/expressions/fuzz.compiler.unit.spec.ts
|
||||
env:
|
||||
MB_FUZZ: 1
|
||||
name: Run fuzz testing on the custom expression compiler
|
||||
|
||||
fe-fuzz-string-quoting:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 15
|
||||
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
|
||||
- run: yarn test-unit frontend/src/metabase/querying/expressions/fuzz.string.unit.spec.ts
|
||||
env:
|
||||
MB_FUZZ: 1
|
||||
name: Run fuzz testing on the custom expression string quoter
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user