Compare commits
5 Commits
v0.58.22
...
python-run
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64977bffa6 | ||
|
|
25dddd8f32 | ||
|
|
ee806be8b7 | ||
|
|
0ea6e387c0 | ||
|
|
f6f128b87d |
@@ -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 M/D/YY) -- description`
|
||||
|
||||
## Code Organization
|
||||
|
||||
**Visibility:**
|
||||
|
||||
- Make everything `^:private` unless it is used elsewhere
|
||||
- Try to organize namespaces to avoid `declare` (put public functions near the end)
|
||||
|
||||
**Size and Structure:**
|
||||
|
||||
- Break up functions > 20 lines
|
||||
- Lines ≤ 120 characters
|
||||
- No blank 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:** `yarn lint-eslint-pure`
|
||||
- Run ESLint on the codebase
|
||||
- **Format:** `yarn prettier`
|
||||
- Format code using Prettier
|
||||
- **Type Check:** `yarn type-check-pure`
|
||||
- Run TypeScript type checking
|
||||
|
||||
## Testing
|
||||
|
||||
### JavaScript/TypeScript Tests
|
||||
|
||||
- **Test a specific file:** `yarn test-unit-keep-cljs path/to/file.unit.spec.js`
|
||||
- **Test by pattern:** `yarn test-unit-keep-cljs -t "pattern"`
|
||||
- Runs tests matching the given pattern
|
||||
|
||||
### ClojureScript Tests
|
||||
|
||||
- **Test ClojureScript:** `yarn test-cljs`
|
||||
- Run ClojureScript tests
|
||||
@@ -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 1/1/25) -- description`
|
||||
|
||||
### Code Organization
|
||||
|
||||
- [ ] Everything `^:private` unless used elsewhere
|
||||
- [ ] No `declare` when avoidable (public functions near end)
|
||||
- [ ] Functions under 20 lines when possible
|
||||
- [ ] No blank, 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 1/1/25) -- description` |
|
||||
| `(defn foo ...)` in namespace used elsewhere | Should be `(defn ^:private foo ...)` |
|
||||
| Function > 20 lines | Consider breaking up into smaller functions |
|
||||
| `/api/dashboards/:id` | Use singular: `/api/dashboard/:id` |
|
||||
| Query params with `snake_case` | Use kebab-case for query params |
|
||||
| New API endpoint without tests | Add tests for the endpoint |
|
||||
|
||||
## Feedback format examples
|
||||
|
||||
**For style violations:**
|
||||
|
||||
> This pure function should be named as a noun describing its return value. Consider `user` instead of `get-user`.
|
||||
|
||||
**For missing documentation:**
|
||||
|
||||
> This public var needs a docstring explaining its purpose, inputs, and outputs.
|
||||
|
||||
**For organization issues:**
|
||||
|
||||
> This function is only used in this namespace, so it should be marked `^:private`.
|
||||
|
||||
**For API conventions:**
|
||||
|
||||
> Query parameters should use kebab-case. Change `user_id` to `user-id`.
|
||||
@@ -1,73 +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
|
||||
- After changing module boundaries (adding/removing/renaming a `src` namespace, a cross-module `require`
|
||||
or `:model/X` reference, or a new module), run `./bin/mage fix-modules-config` to regenerate
|
||||
`.clj-kondo/config/modules/config.edn` and keep `metabase.core.modules-test` green. No-op when nothing
|
||||
drifted; see "Module Boundaries" in the project `CLAUDE.md`.
|
||||
@@ -1,168 +0,0 @@
|
||||
---
|
||||
name: docs-review
|
||||
description: Review documentation changes for compliance with the Metabase writing style guide. Use when reviewing pull requests, files, or diffs containing documentation markdown files.
|
||||
allowed-tools: Read, Grep, Bash, Glob
|
||||
---
|
||||
|
||||
# Documentation Review Skill
|
||||
|
||||
@./../_shared/metabase-style-guide.md
|
||||
|
||||
## Review mode detection
|
||||
|
||||
**IMPORTANT: Before starting the review, determine which mode to use:**
|
||||
|
||||
1. **PR review mode**: If the `mcp__github__create_pending_pull_request_review` tool is available, you are reviewing a GitHub PR
|
||||
- Use the pending review workflow to post all issues as one cohesive review
|
||||
- Follow the workflow steps in "PR review mode format" below
|
||||
|
||||
2. **Local review mode**: If the MCP tool is NOT available, output issues in the conversation
|
||||
- Format all issues in a numbered markdown list (as described in "Feedback format" below)
|
||||
|
||||
## Review process
|
||||
|
||||
1. **Detect review mode** - Check if `mcp__github__create_pending_pull_request_review` is available
|
||||
2. Read the changes through once to understand intent
|
||||
3. Check all issues that violate style guide or significantly impact readability
|
||||
4. Only flag issues worth mentioning - if it won't make a material difference to the reader, skip it
|
||||
5. **REQUIRED: Number ALL feedback sequentially** - Start from Issue 1 and increment for each issue found
|
||||
|
||||
## Review checklist
|
||||
|
||||
Run through the diff looking for these issues:
|
||||
|
||||
**Tone and voice:**
|
||||
|
||||
- [ ] Formal/corporate language ("utilize" not "use", "offerings", etc.)
|
||||
- [ ] "Users" instead of "people" or "companies"
|
||||
- [ ] Excessive exclamation points or overly peppy tone
|
||||
- [ ] Telling readers something is cool instead of showing them
|
||||
|
||||
**Structure and clarity:**
|
||||
|
||||
- [ ] Important information buried instead of leading
|
||||
- [ ] Verbose text that adds little value
|
||||
- [ ] Paragraphs without clear purpose
|
||||
- [ ] Vague headings that don't convey the point
|
||||
- [ ] Instructions explain "why" before telling "what to do"
|
||||
- [ ] Tasks described as "easy" or "simple"
|
||||
|
||||
**Links and references:**
|
||||
|
||||
- [ ] Linking the word "here" instead of descriptive text
|
||||
- [ ] Links in headings (unless entire heading is a link)
|
||||
|
||||
**Formatting:**
|
||||
|
||||
- [ ] Backticks on UI elements (should use **bold**)
|
||||
- [ ] Backticks used as quotation marks
|
||||
- [ ] Ampersands as "and" substitute (except proper nouns)
|
||||
- [ ] Inconsistent list formatting
|
||||
|
||||
**Code and examples:**
|
||||
|
||||
- [ ] Code examples that don't work or would error
|
||||
- [ ] Commands not in execution order
|
||||
- [ ] Full-width screenshots instead of scoped UI elements
|
||||
- [ ] Excessive or unnecessary images
|
||||
|
||||
**Sentence construction:**
|
||||
|
||||
- [ ] Overuse of pronouns when introducing new terms
|
||||
|
||||
## Quick scan table
|
||||
|
||||
| Pattern | Issue |
|
||||
| ----------------------------- | --------------------------------------------- |
|
||||
| `Button name` or `UI element` | Should use **bold** not backticks |
|
||||
| we can do X, our feature | Should be "Metabase" or "it" |
|
||||
| click here, read more here | Need descriptive link text |
|
||||
| easy, simple, just | Remove condescending qualifiers |
|
||||
| users | Should be "people" or "companies" if possible |
|
||||
|
||||
## Feedback format
|
||||
|
||||
**MANDATORY REQUIREMENT: Every single issue MUST be numbered sequentially starting from Issue 1.**
|
||||
|
||||
This numbered format is NON-NEGOTIABLE. It allows users to efficiently reference specific issues (e.g., "fix issues 1, 3, and 5") and track which feedback has been addressed.
|
||||
|
||||
### Local review mode format
|
||||
|
||||
When outputting issues in the conversation (local mode), use this format:
|
||||
|
||||
```markdown
|
||||
## Issues
|
||||
|
||||
**Issue 1: [Brief title]**
|
||||
Line X: Succinct description of the issue
|
||||
[code or example]
|
||||
Suggested fix or succinct explanation
|
||||
|
||||
**Issue 2: [Brief title]**
|
||||
Line Y: Description of the issue
|
||||
Suggested fix or explanation
|
||||
|
||||
**Issue 3: [Brief title]**
|
||||
...
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
|
||||
> **Issue 1: Backticks on UI elements**
|
||||
> Line 42: This uses backticks for the UI element. Use **bold** instead: **Filter** not `Filter`.
|
||||
|
||||
> **Issue 2: Formal tone**
|
||||
> Line 15: This could be more conversational. Consider: "You can't..." instead of "You cannot..."
|
||||
|
||||
> **Issue 3: Vague heading**
|
||||
> Line 8: The heading could be more specific. Try stating the point directly: "Run migrations before upgrading" vs "Upgrade process"
|
||||
|
||||
### PR review mode format
|
||||
|
||||
When posting to GitHub (PR mode), use the **pending review workflow**:
|
||||
|
||||
**Workflow steps:**
|
||||
|
||||
1. **Start a review**: Use `mcp__github__create_pending_pull_request_review` to begin a pending review
|
||||
- This creates a draft review that won't be visible until submitted
|
||||
|
||||
2. **Get diff information**: Use `mcp__github__get_pull_request_diff` to understand the code changes and line numbers
|
||||
- This helps you determine the correct file paths and line numbers for comments
|
||||
|
||||
3. **Identify ALL issues**: Read through all changes and identify every issue worth mentioning
|
||||
- Collect all issues before posting any comments
|
||||
- Number them sequentially (Issue 1, Issue 2, Issue 3, etc.)
|
||||
|
||||
4. **Add review comments**: Use `mcp__github__add_pull_request_review_comment_to_pending_review` for each issue
|
||||
- **CRITICAL**: Post ALL comments in a SINGLE response using multiple tool calls in parallel
|
||||
- Each comment should reference a specific file path and line number from the diff
|
||||
- Start each comment body with `**Issue N: [Brief title]**`
|
||||
- Include the description and suggested fix
|
||||
|
||||
5. **Submit the review**: Use `mcp__github__submit_pending_pull_request_review` to publish all comments at once
|
||||
- Use event type `"COMMENT"` (NOT "REQUEST_CHANGES") to make it non-blocking
|
||||
- **Do NOT include a body message** - Leave the body empty or omit it entirely
|
||||
- All comments will appear together as one cohesive review
|
||||
|
||||
**Comment format example:**
|
||||
```
|
||||
**Issue 1: Backticks on UI elements**
|
||||
|
||||
This uses backticks for the UI element. Use **bold** instead: **Filter** not `Filter`.
|
||||
```
|
||||
|
||||
**IMPORTANT**:
|
||||
- Each issue gets its own review comment attached to the pending review
|
||||
- Number ALL comments sequentially (Issue 1, Issue 2, Issue 3, etc.)
|
||||
- Always start the comment body with `**Issue N: [Brief title]**`
|
||||
- **MUST add all comments in parallel in a single response** - Do NOT add them one after another in separate responses
|
||||
- Do NOT output a summary message to the conversation - only post GitHub review comments
|
||||
- When submitting the review, do NOT include a body parameter (or leave it empty) to avoid cluttering the PR with summary text
|
||||
- The review will appear as a single review with multiple comments when submitted
|
||||
|
||||
## Final check
|
||||
|
||||
1. Remove any issues from your assessment that won't make a material difference to the reader if addressed. Only flag issues worth the author's time to fix.
|
||||
2. **Verify all issues are numbered sequentially** starting from Issue 1 with no gaps in numbering.
|
||||
3. Confirm the format exactly matches: `**Issue N: [Brief title]**` where N is the issue number.
|
||||
4. **In PR mode**: Verify each issue was posted as a separate GitHub comment (not output to conversation).
|
||||
@@ -1,96 +0,0 @@
|
||||
---
|
||||
name: docs-write
|
||||
description: Write documentation following Metabase's conversational, clear, and user-focused style. Use when creating or editing documentation files (markdown, MDX, etc.).
|
||||
allowed-tools: Read, Write, Grep, Bash, Glob
|
||||
---
|
||||
|
||||
# Documentation Writing Skill
|
||||
|
||||
@./../_shared/metabase-style-guide.md
|
||||
|
||||
## When writing documentation
|
||||
|
||||
### Start here
|
||||
|
||||
1. **Who is this for?** Match complexity to audience. Don't oversimplify hard things or overcomplicate simple ones.
|
||||
2. **What do they need?** Get them to the answer fast. Nobody wants to be in docs longer than necessary.
|
||||
3. **What did you struggle with?** Those common questions you had when learning? Answer them (without literally including the question).
|
||||
|
||||
### Writing process
|
||||
|
||||
**Draft:**
|
||||
|
||||
- Write out the steps/explanation as you'd tell a colleague
|
||||
- Lead with what to do, then explain why
|
||||
- Use headings that state your point: "Set SAML before adding users" not "SAML configuration timing"
|
||||
|
||||
**Edit:**
|
||||
|
||||
- Read aloud. Does it sound like you talking? If it's too formal, simplify.
|
||||
- Cut anything that doesn't directly help the reader
|
||||
- Check each paragraph has one clear purpose
|
||||
- Verify examples actually work (don't give examples that error)
|
||||
|
||||
**Polish:**
|
||||
|
||||
- Make links descriptive (never "here")
|
||||
- Backticks only for code/variables, **bold** for UI elements
|
||||
- American spelling, serial commas
|
||||
- Keep images minimal and scoped tight
|
||||
|
||||
**Format:**
|
||||
|
||||
- Run prettier on the file after making edits: `yarn prettier --write <file-path>`
|
||||
- This ensures consistent formatting across all documentation
|
||||
|
||||
### Common patterns
|
||||
|
||||
**Instructions:**
|
||||
|
||||
```markdown
|
||||
Run:
|
||||
\`\`\`
|
||||
command-to-run
|
||||
\`\`\`
|
||||
|
||||
Then:
|
||||
\`\`\`
|
||||
next-command
|
||||
\`\`\`
|
||||
|
||||
This ensures you're getting the latest changes.
|
||||
```
|
||||
|
||||
Not: "(remember to run X before Y...)" buried in a paragraph.
|
||||
|
||||
**Headings:**
|
||||
|
||||
- "Use environment variables for configuration" ✅
|
||||
- "Environment variables" ❌ (too vague)
|
||||
- "How to use environment variables for configuration" ❌ (too wordy)
|
||||
|
||||
**Links:**
|
||||
|
||||
- "Check out the [SAML documentation](link)" ✅
|
||||
- "Read the docs [here](link)" ❌
|
||||
|
||||
### Watch out for
|
||||
|
||||
- Describing tasks as "easy" (you don't know the reader's context)
|
||||
- Using "we" when talking about Metabase features (use "Metabase" or "it")
|
||||
- Formal language: "utilize", "reference", "offerings"
|
||||
- Too peppy: multiple exclamation points
|
||||
- Burying the action in explanation
|
||||
- Code examples that don't work
|
||||
- Numbers that will become outdated
|
||||
|
||||
### Quick reference
|
||||
|
||||
| Write This | Not This |
|
||||
| -------------------------- | ------------------ |
|
||||
| people, companies | users |
|
||||
| summarize | aggregate |
|
||||
| take a look at | reference |
|
||||
| can't, don't | cannot, do not |
|
||||
| **Filter** button | \`Filter\` button |
|
||||
| Check out [the docs](link) | Click [here](link) |
|
||||
@@ -1,19 +0,0 @@
|
||||
---
|
||||
name: typescript-review
|
||||
description: Review TypeScript and JavaScript code changes for compliance with Metabase coding standards, style violations, and code quality issues. Use when reviewing pull requests or diffs containing TypeScript/JavaScript code.
|
||||
allowed-tools: Read, Grep, Bash, Glob
|
||||
---
|
||||
|
||||
# TypeScript/JavaScript Code Review Skill
|
||||
|
||||
@./../_shared/typescript-commands.md
|
||||
|
||||
## Code Review Guidelines
|
||||
|
||||
Review pull requests with a focus on:
|
||||
|
||||
- Compliance with project coding standards and conventions
|
||||
- Code quality and best practices
|
||||
- Clear and correct JSDoc comments
|
||||
- Type safety and proper TypeScript usage
|
||||
- React best practices (when applicable)
|
||||
@@ -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
|
||||
@@ -25,7 +25,6 @@
|
||||
:reduce-without-init {:level :warning}
|
||||
:redundant-call {:level :warning}
|
||||
:redundant-fn-wrapper {:level :warning}
|
||||
:redundant-str-call {:level :warning}
|
||||
:self-requiring-namespace {:level :warning}
|
||||
:single-key-in {:level :warning}
|
||||
:unsorted-imports {:level :warning}
|
||||
@@ -56,23 +55,20 @@
|
||||
;;
|
||||
;; globally-enabled custom linters
|
||||
;;
|
||||
:metabase/check-defmulti-arglists {:level :warning}
|
||||
:metabase/check-me-humanize {:level :warning}
|
||||
:metabase/defsetting-must-specify-export {:level :warning}
|
||||
:metabase/defsetting-namespace {:level :warning}
|
||||
:metabase/discourage-millis-duration {:level :warning}
|
||||
:metabase/mbql-query-first-arg {:level :warning}
|
||||
:metabase/missing-test-expr-requires-in-cljs {:level :warning}
|
||||
:metabase/modules {:level :warning} ; more config in `.clj-kondo/config/modules/config.edn`
|
||||
:metabase/namespace-name {:level :warning}
|
||||
:metabase/require-shape-checker {:level :warning}
|
||||
:metabase/test-helpers-use-non-thread-safe-functions {:level :warning} ; more config in `.clj-kondo/config/test-helpers/config.edn`
|
||||
:metabase/tests-must-live-in-known-modules {:level :warning}
|
||||
:metabase/validate-defendpoint-route-uses-kebab-case {:level :warning}
|
||||
:metabase/validate-defendpoint-query-params-use-kebab-case {:level :warning}
|
||||
:metabase/validate-logging {:level :warning}
|
||||
:metabase/validate-mb-driver-tests {:level :warning}
|
||||
:metabase/validate-mb-once {:level :warning}
|
||||
:metabase/check-defmulti-arglists {:level :warning}
|
||||
:metabase/check-me-humanize {:level :warning}
|
||||
:metabase/defsetting-must-specify-export {:level :warning}
|
||||
:metabase/defsetting-namespace {:level :warning}
|
||||
:metabase/mbql-query-first-arg {:level :warning}
|
||||
:metabase/missing-test-expr-requires-in-cljs {:level :warning}
|
||||
:metabase/modules {:level :warning} ; more config in `.clj-kondo/config/modules/config.edn`
|
||||
:metabase/namespace-name {:level :warning}
|
||||
:metabase/require-shape-checker {:level :warning}
|
||||
:metabase/test-helpers-use-non-thread-safe-functions {:level :warning} ; more config in `.clj-kondo/config/test-helpers/config.edn`
|
||||
:metabase/validate-logging {:level :warning}
|
||||
:metabase/validate-mb-driver-tests {:level :warning}
|
||||
:metabase/validate-mb-once {:level :warning}
|
||||
:metabase/discourage-millis-duration {:level :warning}
|
||||
|
||||
:metabase/validate-deftest
|
||||
{:level :warning
|
||||
@@ -174,7 +170,6 @@
|
||||
clojure.core.async/to-chan!
|
||||
clojure.core.async/to-chan!!
|
||||
metabase.channel.core/send!
|
||||
metabase.warehouses.models.database/assert-not-h2!
|
||||
metabase.driver.sql-jdbc.execute/execute-prepared-statement!
|
||||
metabase.notification.models/create-notification!
|
||||
metabase.pulse.send/send-pulse!
|
||||
@@ -220,8 +215,7 @@
|
||||
;; misc config for built-in linters
|
||||
;;
|
||||
|
||||
:invalid-arity {:skip-args [metabase.lib.util.match/match
|
||||
metabase.lib.util.match/match-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]}}
|
||||
|
||||
@@ -232,6 +226,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
|
||||
@@ -240,6 +237,7 @@
|
||||
(metabase.lib.util.match/match)
|
||||
(metabase.lib.util.match/match-one)
|
||||
(metabase.lib.util.match/match-lite)
|
||||
(metabase.lib.util.match/match-lite-recursive)
|
||||
(metabase.lib.util.match/replace)
|
||||
(metabase.lib.util.match/replace-in)
|
||||
(metabase.driver-api.core/match)
|
||||
@@ -275,7 +273,6 @@
|
||||
clojure.java.jdbc/with-db-connection {:message "Use sql-jdbc.execute/do-with-connection-with-options (for drivers) or t2/with-connection (for the app DB) instead of jdbc/with-db-connection"}
|
||||
clojure.java.jdbc/with-db-metadata {:message "Use sql-jdbc.execute/do-with-connection-with-options + .getMetaData instead of jdbc/with-db-metadata"}
|
||||
clojure.pprint/pprint {:message "Use metabase.util.log instead of clojure.pprint/pprint"}
|
||||
clojure.pprint/print-table {:message "Use metabase.util.log instead of printing directly to the console"}
|
||||
clojure.string/lower-case {:message "Use metabase.util/lower-case-en instead of clojure.string/lower-case"}
|
||||
clojure.string/upper-case {:message "Use metabase.util/upper-case-en instead of clojure.string/upper-case"}
|
||||
compojure.core/context {:message "Use metabase.api.util.handlers/route-map-handler instead of compojure.core/context, it is much faster and supports OpenAPI spec generation"}
|
||||
@@ -297,7 +294,6 @@
|
||||
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-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"}}
|
||||
@@ -410,6 +406,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
|
||||
@@ -451,8 +448,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
|
||||
@@ -491,9 +488,8 @@
|
||||
metabase.util.malli/defn- schema.core/defn
|
||||
metabase.util.malli/fn schema.core/fn
|
||||
metabase.util.namespaces/import-fns potemkin/import-vars
|
||||
metabase.util.performance/doseq clojure.core/doseq
|
||||
metabase.util.performance/for clojure.core/for
|
||||
metabase.util/with-timer-ms clojure.core/fn
|
||||
metabase.xrays.domain-entities.malli/defn schema.core/defn
|
||||
metabuild-common.core/with-open-jar-file-system clojure.core/let
|
||||
metabuild-common.files/with-open-jar-file-system clojure.core/let
|
||||
monger.operators/defoperator clojure.core/def
|
||||
@@ -523,7 +519,6 @@
|
||||
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.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.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
|
||||
@@ -535,25 +530,23 @@
|
||||
metabase.channel.template.handlebars-test/with-temp-template! hooks.common/with-vec-first-binding
|
||||
metabase.classloader.core/require hooks.clojure.core.require/lint-require
|
||||
metabase.classloader.impl/require hooks.clojure.core.require/lint-require
|
||||
metabase.collections-rest.api-test/with-french-user-and-personal-collection! hooks.common/with-two-top-level-bindings
|
||||
metabase.collections.api-test/with-french-user-and-personal-collection! hooks.common/with-two-top-level-bindings
|
||||
metabase.collections.models.collection-test/with-collection-hierarchy! hooks.common/let-one-with-optional-value
|
||||
metabase.collections.models.collection-test/with-personal-and-impersonal-collections hooks.common/with-two-bindings
|
||||
metabase.dashboards-rest.api-test/with-chain-filter-fixtures hooks.common/let-one-with-optional-value
|
||||
metabase.dashboards-rest.api-test/with-simple-dashboard-with-tabs hooks.common/with-one-binding
|
||||
metabase.dashboards.api-test/with-chain-filter-fixtures hooks.common/let-one-with-optional-value
|
||||
metabase.dashboards.api-test/with-simple-dashboard-with-tabs hooks.common/with-one-binding
|
||||
metabase.dashboards.models.dashboard-tab-test/with-dashtab-in-personal-collection hooks.common/with-one-top-level-binding
|
||||
metabase.dashboards.models.dashboard-test/with-dash-in-collection! hooks.common/with-three-bindings
|
||||
metabase.driver.bigquery-cloud-sdk-test/calculate-bird-scarcity hooks.metabase.query-processor.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!
|
||||
metabase.driver-api/defenterprise hooks.metabase.premium-features.defenterprise/defenterprise
|
||||
metabase.embedding-rest.api.embed-test/do-response-formats hooks.common/with-two-bindings
|
||||
metabase.embedding-rest.api.embed-test/with-chain-filter-fixtures! hooks.common/let-one-with-optional-value
|
||||
metabase.embedding-rest.api.embed-test/with-temp-card hooks.common/let-one-with-optional-value
|
||||
metabase.embedding-rest.api.embed-test/with-temp-dashcard hooks.common/let-one-with-optional-value
|
||||
metabase.embedding.api.embed-test/do-response-formats hooks.common/with-two-bindings
|
||||
metabase.embedding.api.embed-test/with-chain-filter-fixtures! hooks.common/let-one-with-optional-value
|
||||
metabase.embedding.api.embed-test/with-temp-card hooks.common/let-one-with-optional-value
|
||||
metabase.embedding.api.embed-test/with-temp-dashcard hooks.common/let-one-with-optional-value
|
||||
metabase.legacy-mbql.schema.macros/defclause hooks.metabase.legacy-mbql.schemas.macros/defclause
|
||||
metabase.legacy-mbql.schema.macros/defclause* hooks.metabase.legacy-mbql.schemas.macros/defclause*
|
||||
metabase.legacy-mbql.schema.macros/one-of hooks.metabase.legacy-mbql.schemas.macros/one-of
|
||||
metabase.lib.schema.mbql-clause/define-catn-mbql-clause hooks.metabase.lib.schema.mbql-clause/define-mbql-clause
|
||||
metabase.lib.schema.mbql-clause/define-mbql-clause hooks.metabase.lib.schema.mbql-clause/define-mbql-clause
|
||||
metabase.lib.schema.mbql-clause/define-tuple-mbql-clause hooks.metabase.lib.schema.mbql-clause/define-mbql-clause
|
||||
@@ -570,20 +563,20 @@
|
||||
metabase.permissions.core/disable-perms-cache hooks.common/do*
|
||||
metabase.premium-features.core/defenterprise hooks.metabase.premium-features.defenterprise/defenterprise
|
||||
metabase.premium-features.defenterprise/defenterprise hooks.metabase.premium-features.defenterprise/defenterprise
|
||||
metabase.public-sharing-rest.api-test/with-required-param-card! hooks.common/with-one-binding
|
||||
metabase.public-sharing-rest.api-test/with-temp-public-card hooks.common/let-one-with-optional-value
|
||||
metabase.public-sharing-rest.api-test/with-temp-public-dashboard hooks.common/let-one-with-optional-value
|
||||
metabase.public-sharing-rest.api-test/with-temp-public-dashboard-and-card hooks.common/with-three-bindings
|
||||
metabase.public-sharing.api-test/with-required-param-card! hooks.common/with-one-binding
|
||||
metabase.public-sharing.api-test/with-temp-public-card hooks.common/let-one-with-optional-value
|
||||
metabase.public-sharing.api-test/with-temp-public-dashboard hooks.common/let-one-with-optional-value
|
||||
metabase.public-sharing.api-test/with-temp-public-dashboard-and-card hooks.common/with-three-bindings
|
||||
metabase.pulse.api.alert-test/with-alert-in-collection! hooks.common/with-four-bindings
|
||||
metabase.pulse.dashboard-subscription-test/with-link-card-fixture-for-dashboard hooks.common/let-second
|
||||
metabase.pulse.models.pulse-test/with-dashboard-subscription-in-collection! hooks.common/with-four-bindings
|
||||
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
|
||||
@@ -635,7 +628,7 @@
|
||||
metabase.test/with-temp hooks.toucan2.tools.with-temp/with-temp
|
||||
metabase.test/with-temp-file hooks.metabase.test.util/with-temp-file
|
||||
metabase.test/with-temporary-setting-values hooks.metabase.test.util/with-temporary-setting-values
|
||||
metabase.users-rest.api-test/with-temp-user-email! hooks.common/with-one-binding
|
||||
metabase.users.api-test/with-temp-user-email! hooks.common/with-one-binding
|
||||
metabase.util.i18n/deferred-trs hooks.metabase.util.i18n/deferred-trs
|
||||
metabase.util.i18n/deferred-tru hooks.metabase.util.i18n/deferred-tru
|
||||
metabase.util.i18n/trs hooks.metabase.util.i18n/trs
|
||||
@@ -659,43 +652,44 @@
|
||||
metabase.xrays.api.automagic-dashboards-test/with-indexed-model! hooks.metabase.xrays.api.automagic-dashboards-test/with-indexed-model!}
|
||||
|
||||
:macroexpand
|
||||
{clojurewerkz.quartzite.jobs/build macros.quartz/build-job
|
||||
clojurewerkz.quartzite.schedule.calendar-interval/build macros.quartz/build-job
|
||||
clojurewerkz.quartzite.schedule.cron/schedule macros.quartz/schedule
|
||||
clojurewerkz.quartzite.schedule.simple/schedule macros.quartz/simple-schedule
|
||||
clojurewerkz.quartzite.triggers/build macros.quartz/build-trigger
|
||||
metabase-enterprise.database-routing.e2e-test/with-temp-dbs! macros.metabase-enterprise.database-routing.e2e-test/with-temp-dbs!
|
||||
metabase-enterprise.impersonation.util-test/with-impersonations! macros.metabase-enterprise.impersonation.util-test/with-impersonations!
|
||||
metabase-enterprise.query-reference-validation.api-test/with-test-setup! macros.metabase-enterprise.query-reference-validation.api-test/with-test-setup!
|
||||
metabase-enterprise.sandbox.test-util/with-gtaps! macros.metabase-enterprise.sandbox.test-util/with-gtaps!
|
||||
metabase-enterprise.sandbox.test-util/with-gtaps-for-user! macros.metabase-enterprise.sandbox.test-util/with-gtaps!
|
||||
metabase-enterprise.serialization.test-util/with-world macros.metabase-enterprise.serialization.test-util/with-world
|
||||
metabase-enterprise.test/with-gtaps! macros.metabase-enterprise.sandbox.test-util/with-gtaps!
|
||||
metabase-enterprise.test/with-gtaps-for-user! macros.metabase-enterprise.sandbox.test-util/with-gtaps!
|
||||
metabase.collections-rest.api-test/with-collection-hierarchy! macros.metabase.collections-rest.api-test/with-collection-hierarchy!
|
||||
metabase.collections-rest.api-test/with-some-children-of-collection! macros.metabase.collections-rest.api-test/with-some-children-of-collection!
|
||||
metabase.embedding-rest.api.embed-test/with-embedding-enabled-and-temp-card-referencing! macros.metabase.embedding-rest.api.embed-test/with-embedding-enabled-and-temp-card-referencing!
|
||||
metabase.embedding-rest.api.embed-test/with-embedding-enabled-and-temp-dashcard-referencing! macros.metabase.embedding-rest.api.embed-test/with-embedding-enabled-and-temp-dashcard-referencing!
|
||||
metabase.lib.common/defop macros.metabase.lib.common/defop
|
||||
metabase.lib.filter/deffilter macros.metabase.lib.filter/deffilter
|
||||
metabase.parameters.chain-filter-test/chain-filter macros.metabase.parameters.chain-filter-test/chain-filter
|
||||
metabase.parameters.chain-filter-test/chain-filter-search macros.metabase.parameters.chain-filter-test/chain-filter
|
||||
metabase.public-sharing-rest.api-test/with-sharing-enabled-and-temp-card-referencing! macros.metabase.public-sharing-rest.api-test/with-sharing-enabled-and-temp-card-referencing!
|
||||
metabase.public-sharing-rest.api-test/with-sharing-enabled-and-temp-dashcard-referencing! macros.metabase.public-sharing-rest.api-test/with-sharing-enabled-and-temp-dashcard-referencing!
|
||||
metabase.queries-rest.api.card-test/with-ordered-items macros.metabase.queries-rest.api.card-test/with-ordered-items
|
||||
metabase.query-analysis.models.query-analysis-test/with-test-setup macros.metabase.query-analysis.models.query-analysis-test/with-test-setup
|
||||
metabase.query-analysis.task.test-setup/with-test-setup! macros.metabase.query-analysis.task.test-setup/with-test-setup!
|
||||
metabase.query-processor.streaming/streaming-response macros.metabase.query-processor.streaming/streaming-response
|
||||
metabase.server.statistics-handler-test/with-server macros.metabase.server.statistics-handler-test/with-server
|
||||
metabase.test.data.users/with-group-for-user macros.metabase.test.data.users/with-group-for-user
|
||||
metabase.test.util/with-temp-env-var-value! macros.metabase.test.util/with-temp-env-var-value!
|
||||
metabase.test.util/with-temporary-raw-setting-values macros.metabase.test.util/with-temporary-raw-setting-values
|
||||
metabase.test/with-group-for-user macros.metabase.test.data.users/with-group-for-user
|
||||
metabase.test/with-temp-env-var-value! macros.metabase.test.util/with-temp-env-var-value!
|
||||
metabase.test/with-temporary-raw-setting-values macros.metabase.test.util/with-temporary-raw-setting-values
|
||||
metabase.users.models.user-test/with-groups! macros.metabase.users.models.user-test/with-groups!
|
||||
metabase.util.namespaces/import-fn macros.metabase.util.namespaces/import-fn
|
||||
metabase.xrays.related-test/with-world macros.metabase.xrays.related-test/with-world}}
|
||||
{clojurewerkz.quartzite.jobs/build macros.quartz/build-job
|
||||
clojurewerkz.quartzite.schedule.calendar-interval/build macros.quartz/build-job
|
||||
clojurewerkz.quartzite.schedule.cron/schedule macros.quartz/schedule
|
||||
clojurewerkz.quartzite.schedule.simple/schedule macros.quartz/simple-schedule
|
||||
clojurewerkz.quartzite.triggers/build macros.quartz/build-trigger
|
||||
metabase-enterprise.database-routing.e2e-test/with-temp-dbs! macros.metabase-enterprise.database-routing.e2e-test/with-temp-dbs!
|
||||
metabase-enterprise.impersonation.util-test/with-impersonations! macros.metabase-enterprise.impersonation.util-test/with-impersonations!
|
||||
metabase-enterprise.query-reference-validation.api-test/with-test-setup! macros.metabase-enterprise.query-reference-validation.api-test/with-test-setup!
|
||||
metabase-enterprise.sandbox.test-util/with-gtaps! macros.metabase-enterprise.sandbox.test-util/with-gtaps!
|
||||
metabase-enterprise.sandbox.test-util/with-gtaps-for-user! macros.metabase-enterprise.sandbox.test-util/with-gtaps!
|
||||
metabase-enterprise.serialization.test-util/with-world macros.metabase-enterprise.serialization.test-util/with-world
|
||||
metabase-enterprise.test/with-gtaps! macros.metabase-enterprise.sandbox.test-util/with-gtaps!
|
||||
metabase-enterprise.test/with-gtaps-for-user! macros.metabase-enterprise.sandbox.test-util/with-gtaps!
|
||||
metabase.collections.api-test/with-collection-hierarchy! macros.metabase.collections.api-test/with-collection-hierarchy!
|
||||
metabase.collections.api-test/with-some-children-of-collection! macros.metabase.collections.api-test/with-some-children-of-collection!
|
||||
metabase.embedding.api.embed-test/with-embedding-enabled-and-temp-card-referencing! macros.metabase.embedding.api.embed-test/with-embedding-enabled-and-temp-card-referencing!
|
||||
metabase.embedding.api.embed-test/with-embedding-enabled-and-temp-dashcard-referencing! macros.metabase.embedding.api.embed-test/with-embedding-enabled-and-temp-dashcard-referencing!
|
||||
metabase.lib.common/defop macros.metabase.lib.common/defop
|
||||
metabase.lib.filter/deffilter macros.metabase.lib.filter/deffilter
|
||||
metabase.parameters.chain-filter-test/chain-filter macros.metabase.parameters.chain-filter-test/chain-filter
|
||||
metabase.parameters.chain-filter-test/chain-filter-search macros.metabase.parameters.chain-filter-test/chain-filter
|
||||
metabase.public-sharing.api-test/with-sharing-enabled-and-temp-card-referencing! macros.metabase.public-sharing.api-test/with-sharing-enabled-and-temp-card-referencing!
|
||||
metabase.public-sharing.api-test/with-sharing-enabled-and-temp-dashcard-referencing! macros.metabase.public-sharing.api-test/with-sharing-enabled-and-temp-dashcard-referencing!
|
||||
metabase.queries.api.card-test/with-ordered-items macros.metabase.queries.api.card-test/with-ordered-items
|
||||
metabase.query-analysis.models.query-analysis-test/with-test-setup macros.metabase.query-analysis.models.query-analysis-test/with-test-setup
|
||||
metabase.query-analysis.task.test-setup/with-test-setup! macros.metabase.query-analysis.task.test-setup/with-test-setup!
|
||||
metabase.query-processor.streaming/streaming-response macros.metabase.query-processor.streaming/streaming-response
|
||||
metabase.server.statistics-handler-test/with-server macros.metabase.server.statistics-handler-test/with-server
|
||||
metabase.test.data.users/with-group-for-user macros.metabase.test.data.users/with-group-for-user
|
||||
metabase.test.util/with-temp-env-var-value! macros.metabase.test.util/with-temp-env-var-value!
|
||||
metabase.test.util/with-temporary-raw-setting-values macros.metabase.test.util/with-temporary-raw-setting-values
|
||||
metabase.test/with-group-for-user macros.metabase.test.data.users/with-group-for-user
|
||||
metabase.test/with-temp-env-var-value! macros.metabase.test.util/with-temp-env-var-value!
|
||||
metabase.test/with-temporary-raw-setting-values macros.metabase.test.util/with-temporary-raw-setting-values
|
||||
metabase.users.models.user-test/with-groups! macros.metabase.users.models.user-test/with-groups!
|
||||
metabase.util.namespaces/import-fn macros.metabase.util.namespaces/import-fn
|
||||
metabase.xrays.domain-entities.malli/define-getters-and-setters macros.metabase.xrays.domain-entities.malli/define-getters-and-setters
|
||||
metabase.xrays.related-test/with-world macros.metabase.xrays.related-test/with-world}}
|
||||
|
||||
:config-in-comment
|
||||
{:linters {:unresolved-symbol {:level :off}}}
|
||||
@@ -722,8 +716,6 @@
|
||||
:name metabase-lib}
|
||||
{:pattern "^metabase\\.lib\\..*"
|
||||
:name metabase-lib-excluding-legacy-mbql}
|
||||
{:pattern "^metabase\\.legacy-mbql\\..*"
|
||||
:name legacy-mbql-namespaces}
|
||||
;;
|
||||
;; QP and driver source namespaces: `metabase.query-processor.*`, `metabase-enterprise.sandbox.query-processor.*`,
|
||||
;; and `metabase.driver.*`, excluding `-test` namespaces.
|
||||
@@ -738,7 +730,7 @@
|
||||
;;
|
||||
;; driver namespaces, including tests
|
||||
;;
|
||||
{:pattern "metabase\\.driver(?:-api)?.*$"
|
||||
{:pattern "metabase\\.driver.*$"
|
||||
:name driver-namespaces}
|
||||
;;
|
||||
;; Perf-sensitive namespaces are currently the union of metabase.lib.*, driver namespaces, and QP (without tests).
|
||||
@@ -751,10 +743,7 @@
|
||||
;; `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
|
||||
@@ -778,24 +767,16 @@
|
||||
|
||||
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"}
|
||||
clojure.core/eval {:message "Don't use eval outside of tests"}}
|
||||
|
||||
:discouraged-namespace
|
||||
{metabase.legacy-mbql.jvm-util {:message "Avoid legacy MBQL utils, use Lib instead"}
|
||||
metabase.legacy-mbql.normalize {:message "Avoid legacy MBQL normalization, use MBQL 5 + lib/normalize instead"}
|
||||
metabase.legacy-mbql.predicates {:message "Avoid legacy MBQL utils, use Lib instead"}
|
||||
metabase.legacy-mbql.schema {:message "Avoid the legacy MBQL schema, use MBQL 5 + lib.schema instead"}
|
||||
metabase.legacy-mbql.schema.helpers {:message "Avoid the legacy MBQL schema, use MBQL 5 + lib.schema instead"}
|
||||
metabase.legacy-mbql.schema.macros {:message "Avoid the legacy MBQL schema, use MBQL 5 + lib.schema instead"}
|
||||
metabase.legacy-mbql.util {:message "Avoid legacy MBQL utils, use Lib instead"}
|
||||
metabase.query-processor.store {:message "Please do not use the QP Store outside of legacy QP or driver code. Use MBQL 5/Lib or lib.metadata.jvm/applicaton-database-metadata-provider instead."}}}}
|
||||
{metabase.query-processor.store {:message "Please do not use the QP Store outside of legacy QP or driver code. Use MBQL 5/Lib or lib.metadata.jvm/applicaton-database-metadata-provider instead."}}}}
|
||||
|
||||
printable-namespaces
|
||||
{:linters
|
||||
@@ -837,89 +818,34 @@
|
||||
toucan2.core {:message "Don't use application database inside Lib code"}}}}
|
||||
|
||||
metabase-lib-excluding-legacy-mbql
|
||||
{}
|
||||
|
||||
legacy-mbql-namespaces
|
||||
{:linters
|
||||
{:discouraged-namespace
|
||||
{metabase.legacy-mbql.jvm-util {:level :off}
|
||||
metabase.legacy-mbql.normalize {:level :off}
|
||||
metabase.legacy-mbql.predicates {:level :off}
|
||||
metabase.legacy-mbql.schema {:level :off}
|
||||
metabase.legacy-mbql.schema.helpers {:level :off}
|
||||
metabase.legacy-mbql.schema.macros {:level :off}
|
||||
metabase.legacy-mbql.util {:level :off}
|
||||
metabase.query-processor.store {:level :off}}
|
||||
{:discouraged-var
|
||||
{metabase.legacy-mbql.util/unique-name-generator {:message "Use metabase.lib.util/unique-name-generator or non-truncating-unique-name-generator"}}}
|
||||
|
||||
;; the `metabase.legacy-mbql` stuff is all deprecated, so we don't need to warn inside the module about other stuff
|
||||
;; in the module being marked deprecated or discouraged.
|
||||
:deprecated-namespace {:level :off}
|
||||
:deprecated-var {:level :off}}}
|
||||
:discouraged-namespace
|
||||
{metabase.legacy-mbql.js {:message "Avoid legacy MBQL utils in Lib; move things into Lib if needded."}
|
||||
metabase.legacy-mbql.jvm-util {:message "Avoid legacy MBQL utils in Lib; move things into Lib if needded."}
|
||||
metabase.legacy-mbql.predicates {:message "Avoid legacy MBQL utils in Lib; move things into Lib if needded."}
|
||||
metabase.legacy-mbql.util {:message "Avoid legacy MBQL utils in Lib; move things into Lib if needded."}}}
|
||||
|
||||
driver-namespaces
|
||||
{:linters
|
||||
{:discouraged-var
|
||||
{metabase.driver/database-supports? {:level :off}}
|
||||
|
||||
;; don't warn about the legacy MBQL namespaces being deprecated or discouraged in driver code now, we don't have a
|
||||
;; real alternative YET so it's not a helpful warning.
|
||||
:discouraged-namespace
|
||||
{metabase.legacy-mbql.jvm-util {:level :off}
|
||||
metabase.legacy-mbql.normalize {:level :off}
|
||||
metabase.legacy-mbql.predicates {:level :off}
|
||||
metabase.legacy-mbql.schema {:level :off}
|
||||
metabase.legacy-mbql.schema.helpers {:level :off}
|
||||
metabase.legacy-mbql.schema.macros {:level :off}
|
||||
metabase.legacy-mbql.util {:level :off}
|
||||
metabase.query-processor.store {:level :off}}
|
||||
|
||||
:deprecated-namespace
|
||||
{:exclude
|
||||
[metabase.driver.common.parameters
|
||||
metabase.driver.common.parameters.dates
|
||||
metabase.driver.common.parameters.operators
|
||||
metabase.driver.common.parameters.parse
|
||||
metabase.driver.common.parameters.values
|
||||
metabase.legacy-mbql.jvm-util
|
||||
metabase.legacy-mbql.normalize
|
||||
metabase.legacy-mbql.predicates
|
||||
metabase.legacy-mbql.schema
|
||||
metabase.legacy-mbql.schema.helpers
|
||||
metabase.legacy-mbql.schema.macros
|
||||
metabase.legacy-mbql.util
|
||||
metabase.query-processor.store]}
|
||||
|
||||
:deprecated-var
|
||||
{:exclude
|
||||
{metabase.legacy-mbql.schema.helpers/is-clause? {:namespaces ["metabase\\.driver.*"]}
|
||||
metabase.legacy-mbql.schema.helpers/mbql-clause? {:namespaces ["metabase\\.driver.*"]}
|
||||
metabase.legacy-mbql.util/query->source-table-id {:namespaces ["metabase\\.driver.*"]}
|
||||
metabase.legacy-mbql.util/aggregation-at-index {:namespaces ["metabase\\.driver.*"]}
|
||||
metabase.legacy-mbql.util/assoc-field-options {:namespaces ["metabase\\.driver.*"]}
|
||||
metabase.legacy-mbql.util/expression-with-name {:namespaces ["metabase\\.driver.*"]}
|
||||
metabase.legacy-mbql.util/field-options {:namespaces ["metabase\\.driver.*"]}
|
||||
metabase.legacy-mbql.util/negate-filter-clause {:namespaces ["metabase\\.driver.*"]}
|
||||
metabase.legacy-mbql.util/normalize-token {:namespaces ["metabase\\.driver.*"]}
|
||||
metabase.legacy-mbql.util/update-field-options {:namespaces ["metabase\\.driver.*"]}}}}}
|
||||
{metabase.driver/database-supports? {:level :off}}}}
|
||||
|
||||
performance-sensitive
|
||||
{:linters
|
||||
{:discouraged-var
|
||||
{clojure.core/empty? {:message "Use metabase.util.performance/empty?"}
|
||||
clojure.core/not-empty {:message "Use metabase.util.performance/not-empty"}
|
||||
clojure.core/every? {:message "Use metabase.util.performance/every?"}
|
||||
{clojure.core/every? {:message "Use metabase.util.performance/every?"}
|
||||
clojure.core/mapv {:message "Use metabase.util.performance/mapv"}
|
||||
clojure.core/run! {:message "Use metabase.util.performance/run!"}
|
||||
clojure.core/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"}
|
||||
clojure.walk/keywordize-keys {:message "Use metabase.util.performance/keywordize-keys"}
|
||||
clojure.walk/postwalk {:message "Use metabase.util.performance/postwalk"}
|
||||
clojure.walk/prewalk {:message "Use metabase.util.performance/prewalk"}
|
||||
clojure.walk/walk {:message "Use metabase.util.performance/walk"}
|
||||
clojure.walk/for {:message "Use metabase.util.performance/for"}
|
||||
clojure.walk/doseq {:message "Use metabase.util.performance/doseq"}}}}
|
||||
clojure.walk/walk {:message "Use metabase.util.performance/walk"}}}}
|
||||
|
||||
qp-and-driver-source-namespaces
|
||||
{:linters
|
||||
@@ -940,7 +866,16 @@
|
||||
metabase.lib.core/->legacy-MBQL {:level :off}}}}
|
||||
|
||||
qp-source-namespaces
|
||||
{}
|
||||
{:linters
|
||||
{:discouraged-namespace
|
||||
{metabase.legacy-mbql.js {:message "Avoid legacy MBQL namespaces in the QP, use Lib instead"}
|
||||
metabase.legacy-mbql.jvm-util {:message "Avoid legacy MBQL namespaces in the QP, use Lib instead"}
|
||||
metabase.legacy-mbql.normalize {:message "Avoid legacy MBQL namespaces in the QP, use Lib instead"}
|
||||
metabase.legacy-mbql.predicates {:message "Avoid legacy MBQL namespaces in the QP, use Lib instead"}
|
||||
metabase.legacy-mbql.schema {:message "Avoid legacy MBQL namespaces in the QP, use Lib instead"}
|
||||
metabase.legacy-mbql.schema.helpers {:message "Avoid legacy MBQL namespaces in the QP, use Lib instead"}
|
||||
metabase.legacy-mbql.schema.macros {:message "Avoid legacy MBQL namespaces in the QP, use Lib instead"}
|
||||
metabase.legacy-mbql.util {:message "Avoid legacy MBQL namespaces in the QP, use Lib instead"}}}}
|
||||
|
||||
general-qp-and-driver-test-namespaces
|
||||
{:linters
|
||||
@@ -951,11 +886,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
@@ -100,7 +100,7 @@
|
||||
metabase.permissions.models.permissions/update-data-perms-graph!
|
||||
metabase.permissions.models.permissions/update-group-permissions!
|
||||
metabase.permissions.test-util/with-restored-perms!
|
||||
metabase.public-sharing-rest.api-test/add-card-to-dashboard!
|
||||
metabase.public-sharing.api-test/add-card-to-dashboard!
|
||||
metabase.pulse.send/send-notifications!
|
||||
metabase.pulse.send/send-pulse!
|
||||
metabase.query-processor.streaming.interface/begin!
|
||||
|
||||
24
.clj-kondo/macros/metabase/collections/api_test.clj
Normal file
24
.clj-kondo/macros/metabase/collections/api_test.clj
Normal file
@@ -0,0 +1,24 @@
|
||||
(ns macros.metabase.collections.api-test)
|
||||
|
||||
(defmacro with-collection-hierarchy! [bindings & body]
|
||||
`(let [~'a nil
|
||||
~'b nil
|
||||
~'c nil
|
||||
~'d nil
|
||||
~'e nil
|
||||
~'f nil
|
||||
~'g nil]
|
||||
~'a
|
||||
~'b
|
||||
~'c
|
||||
~'d
|
||||
~'e
|
||||
~'f
|
||||
~'g
|
||||
~bindings
|
||||
~@body))
|
||||
|
||||
(defmacro with-some-children-of-collection! [collection-or-id-or-nil & body]
|
||||
`(let [~'&ids ~collection-or-id-or-nil]
|
||||
~'&ids
|
||||
~@body))
|
||||
@@ -1,24 +0,0 @@
|
||||
(ns macros.metabase.collections-rest.api-test)
|
||||
|
||||
(defmacro with-collection-hierarchy! [bindings & body]
|
||||
`(let [~'a nil
|
||||
~'b nil
|
||||
~'c nil
|
||||
~'d nil
|
||||
~'e nil
|
||||
~'f nil
|
||||
~'g nil]
|
||||
~'a
|
||||
~'b
|
||||
~'c
|
||||
~'d
|
||||
~'e
|
||||
~'f
|
||||
~'g
|
||||
~bindings
|
||||
~@body))
|
||||
|
||||
(defmacro with-some-children-of-collection! [collection-or-id-or-nil & body]
|
||||
`(let [~'&ids ~collection-or-id-or-nil]
|
||||
~'&ids
|
||||
~@body))
|
||||
13
.clj-kondo/macros/metabase/embedding/api/embed_test.clj
Normal file
13
.clj-kondo/macros/metabase/embedding/api/embed_test.clj
Normal file
@@ -0,0 +1,13 @@
|
||||
(ns macros.metabase.embedding.api.embed-test)
|
||||
|
||||
(defmacro with-embedding-enabled-and-temp-card-referencing!
|
||||
[table-kw field-kw [card-binding] & body]
|
||||
`(let [~card-binding [~table-kw ~field-kw]]
|
||||
~@body))
|
||||
|
||||
(defmacro with-embedding-enabled-and-temp-dashcard-referencing!
|
||||
[table-kw field-kw [dash-binding card-binding dashcard-binding] & body]
|
||||
`(let [~dash-binding [~table-kw ~field-kw]
|
||||
~(or card-binding '_) nil
|
||||
~(or dashcard-binding '_) nil]
|
||||
~@body))
|
||||
@@ -1,13 +0,0 @@
|
||||
(ns macros.metabase.embedding-rest.api.embed-test)
|
||||
|
||||
(defmacro with-embedding-enabled-and-temp-card-referencing!
|
||||
[table-kw field-kw [card-binding] & body]
|
||||
`(let [~card-binding [~table-kw ~field-kw]]
|
||||
~@body))
|
||||
|
||||
(defmacro with-embedding-enabled-and-temp-dashcard-referencing!
|
||||
[table-kw field-kw [dash-binding card-binding dashcard-binding] & body]
|
||||
`(let [~dash-binding [~table-kw ~field-kw]
|
||||
~(or card-binding '_) nil
|
||||
~(or dashcard-binding '_) nil]
|
||||
~@body))
|
||||
13
.clj-kondo/macros/metabase/public_sharing/api_test.clj
Normal file
13
.clj-kondo/macros/metabase/public_sharing/api_test.clj
Normal file
@@ -0,0 +1,13 @@
|
||||
(ns macros.metabase.public-sharing.api-test)
|
||||
|
||||
(defmacro with-sharing-enabled-and-temp-card-referencing!
|
||||
[table-kw field-kw [card-binding] & body]
|
||||
`(let [~card-binding [~table-kw ~field-kw]]
|
||||
~@body))
|
||||
|
||||
(defmacro with-sharing-enabled-and-temp-dashcard-referencing!
|
||||
[table-kw field-kw [dash-binding card-binding dashcard-binding] & body]
|
||||
`(let [~dash-binding [~table-kw ~field-kw]
|
||||
~(or card-binding '_) nil
|
||||
~(or dashcard-binding '_) nil]
|
||||
~@body))
|
||||
@@ -1,13 +0,0 @@
|
||||
(ns macros.metabase.public-sharing-rest.api-test)
|
||||
|
||||
(defmacro with-sharing-enabled-and-temp-card-referencing!
|
||||
[table-kw field-kw [card-binding] & body]
|
||||
`(let [~card-binding [~table-kw ~field-kw]]
|
||||
~@body))
|
||||
|
||||
(defmacro with-sharing-enabled-and-temp-dashcard-referencing!
|
||||
[table-kw field-kw [dash-binding card-binding dashcard-binding] & body]
|
||||
`(let [~dash-binding [~table-kw ~field-kw]
|
||||
~(or card-binding '_) nil
|
||||
~(or dashcard-binding '_) nil]
|
||||
~@body))
|
||||
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))
|
||||
16
.clj-kondo/macros/metabase/xrays/domain_entities/malli.clj
Normal file
16
.clj-kondo/macros/metabase/xrays/domain_entities/malli.clj
Normal file
@@ -0,0 +1,16 @@
|
||||
(ns macros.metabase.xrays.domain-entities.malli)
|
||||
|
||||
(defmacro define-getters-and-setters [schema & defs]
|
||||
`(do
|
||||
(comment ~schema) ;; Reference the schema
|
||||
~@(for [[sym _path] (partition 2 defs)]
|
||||
`(do
|
||||
(defn ~(vary-meta sym
|
||||
assoc :export true)
|
||||
"docs" [_x#] nil)
|
||||
(defn ~(vary-meta (symbol (str "with-" (name sym)))
|
||||
assoc :export true)
|
||||
"docs" [x# _new#] x#)
|
||||
(defn ~(vary-meta (symbol (str (name sym) "-js"))
|
||||
assoc :export true)
|
||||
"docs" [x#] x#)))))
|
||||
@@ -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,17 +17,12 @@
|
||||
(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
|
||||
"Set of API namespace symbols for a given module. `:any` means you can use anything, there are no API namespaces for
|
||||
|
||||
@@ -1,67 +1,7 @@
|
||||
(ns hooks.metabase.api.macros
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as api]
|
||||
[clojure.string :as str]))
|
||||
|
||||
(defn- validate-route!
|
||||
"Make sure the route uses kebab-case rather than snake_case."
|
||||
[route]
|
||||
(letfn [(validate-route-string! [route-string]
|
||||
(when (and (api/string-node? route-string)
|
||||
(str/includes? (api/sexpr route-string) "_"))
|
||||
(api/reg-finding! (assoc (meta route-string)
|
||||
:message "REST API routes and query params should use kebab-case, not snake_case"
|
||||
:type :metabase/validate-defendpoint-route-uses-kebab-case))))]
|
||||
(cond
|
||||
(api/string-node? route)
|
||||
(validate-route-string! route)
|
||||
;; otherwise route is a vector like ["/abc/:d/" :d <regex>]
|
||||
(api/vector-node? route)
|
||||
(validate-route-string! (first (:children route))))))
|
||||
|
||||
(defn- validate-query-params!
|
||||
"Make sure query params use kebab-case rather than snake_case.
|
||||
|
||||
This only works on inline schemas for now. If you define the schema elsewhere then we'd have to implement this
|
||||
validation in Clojure-land since we can't resolve the schemas in Kondo. It should however still work on complicated
|
||||
schemas like
|
||||
|
||||
[:merge <map-1> <map-2>]"
|
||||
[query-param-schema-node]
|
||||
(letfn [(map-schema? [node]
|
||||
(and (api/vector-node? node)
|
||||
(when-let [first-child (first (:children node))]
|
||||
(and (api/keyword-node? first-child)
|
||||
(= (api/sexpr first-child) :map)))))
|
||||
(map-schema-keys [{:keys [children], :as _node}]
|
||||
(let [child-schemas (if (api/map-node? (second children))
|
||||
(drop 2 children)
|
||||
(drop 1 children))]
|
||||
(keep (fn [child-schema]
|
||||
(when (api/vector-node? child-schema)
|
||||
(when-let [k (first (:children child-schema))]
|
||||
(when (api/keyword-node? k)
|
||||
k))))
|
||||
child-schemas)))
|
||||
(validate-map-schema! [form]
|
||||
;; only warn once per endpoint -- just once on the first bad node
|
||||
(when-let [bad-node (some (fn [key-node]
|
||||
(when (str/includes? (str (api/sexpr key-node)) "_")
|
||||
key-node))
|
||||
(map-schema-keys form))]
|
||||
(api/reg-finding! (assoc (meta bad-node)
|
||||
:message "REST API routes and query params should use kebab-case, not snake_case"
|
||||
:type :metabase/validate-defendpoint-query-params-use-kebab-case))))
|
||||
(validate! [node]
|
||||
(cond
|
||||
(and (api/vector-node? node)
|
||||
(map-schema? node))
|
||||
(validate-map-schema! node)
|
||||
|
||||
(api/vector-node? node)
|
||||
(doseq [child (:children node)]
|
||||
(validate! child))))]
|
||||
(validate! query-param-schema-node)))
|
||||
[hooks.common]))
|
||||
|
||||
(defn defendpoint
|
||||
[arg]
|
||||
@@ -78,25 +18,16 @@
|
||||
(cons nil more))
|
||||
[params & body] more
|
||||
[bindings schemas] (when (api/vector-node? params)
|
||||
(loop [bindings [], schemas [], [x y z & more] (:children params)]
|
||||
(loop [bindings [], schemas [], [x y & more] (:children params)]
|
||||
(cond
|
||||
(not x)
|
||||
[bindings schemas]
|
||||
|
||||
(and (api/keyword-node? y)
|
||||
(= (api/sexpr y) :-))
|
||||
(recur (conj bindings x) (conj schemas z) more)
|
||||
(= (api/sexpr x) :-)
|
||||
(recur bindings (conj schemas y) more)
|
||||
|
||||
:else
|
||||
(recur (conj bindings x) (conj schemas nil) (list* y z more)))))]
|
||||
(validate-route! route)
|
||||
(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)))
|
||||
(recur (conj bindings x) schemas (cons y more)))))]
|
||||
(-> (api/list-node
|
||||
(list
|
||||
(api/token-node 'do)
|
||||
@@ -123,27 +54,3 @@
|
||||
body))))
|
||||
(with-meta (meta node)))))]
|
||||
(update arg :node update-defendpoint)))
|
||||
|
||||
(comment
|
||||
(defn -defendpoint [form]
|
||||
(-> {:node (-> form pr-str api/parse-string)}
|
||||
defendpoint
|
||||
:node
|
||||
api/sexpr))
|
||||
|
||||
(defn x []
|
||||
(-defendpoint '(api.macros/defendpoint :get "/:id/syncable_schemas" ; snake_case route should trigger a warning
|
||||
"Returns a list of all syncable schemas found for the database `id`."
|
||||
[{:keys [id]} :- [:map
|
||||
[:id ms/PositiveInt]]
|
||||
;; snake_case query parameter, should trigger a warning
|
||||
{:keys [filter_by]} :- [:map
|
||||
[:filter_by {:optional true} :string]]]
|
||||
(let [db (get-database id)]
|
||||
(api/check-403 (or (:is_attached_dwh db)
|
||||
(and (mi/can-write? db)
|
||||
(mi/can-read? db))))
|
||||
(->> db
|
||||
(driver/syncable-schemas (:engine db))
|
||||
(vec)
|
||||
(sort)))))))
|
||||
|
||||
@@ -33,36 +33,18 @@
|
||||
(defclause [ag:var var] field-or-expression FieldOrExpressionDef)
|
||||
=>
|
||||
(def ag:var [FieldOrExpressionDef])"
|
||||
[{:keys [node], :as x}]
|
||||
(let [[_defclause clause-name & arg-specs] (:children node)]
|
||||
(assoc x :node (-> (hooks/list-node
|
||||
(list
|
||||
(hooks/token-node 'do)
|
||||
(-> (hooks/keyword-node (keyword "metabase.legacy-mbql.schema" (name (hooks/sexpr clause-name))))
|
||||
(with-meta (meta clause-name))
|
||||
(hooks/reg-keyword! 'metabase.legacy-mbql.schemas.macros/defclause))
|
||||
(collect-defclause-body-schemas arg-specs)))
|
||||
(with-meta (meta node))))))
|
||||
|
||||
(defn defclause*
|
||||
"e.g.
|
||||
|
||||
(defclause [ag:var var] field-or-expression FieldOrExpressionDef)
|
||||
=>
|
||||
(def ag:var [FieldOrExpressionDef])"
|
||||
[{:keys [node], :as x}]
|
||||
(let [[_defclause clause-name schema] (:children node)]
|
||||
(assoc x :node (-> (hooks/list-node
|
||||
(list
|
||||
(hooks/token-node 'do)
|
||||
(-> (hooks/keyword-node (keyword "metabase.legacy-mbql.schema" (name (hooks/sexpr clause-name))))
|
||||
(with-meta (meta clause-name))
|
||||
(hooks/reg-keyword! 'metabase.legacy-mbql.schemas.macros/defclause*))
|
||||
schema))
|
||||
(with-meta (meta node))))))
|
||||
[{:keys [node]}]
|
||||
(let [[_defclause clause-name-form & arg-specs] (:children node)]
|
||||
{:node (-> (hooks/list-node
|
||||
(list
|
||||
(hooks/token-node 'def)
|
||||
(unwrap-defclause-clause-name clause-name-form)
|
||||
(hooks/string-node "Docstring.")
|
||||
(collect-defclause-body-schemas arg-specs)))
|
||||
(with-meta (meta node)))}))
|
||||
|
||||
(comment
|
||||
(defn- expand-defclause [form]
|
||||
(defn- defclause* [form]
|
||||
(hooks/sexpr
|
||||
(:node
|
||||
(defclause
|
||||
@@ -73,35 +55,15 @@
|
||||
form)))}))))
|
||||
|
||||
(defn- x []
|
||||
(expand-defclause '(defclause var
|
||||
field-or-expression FieldOrExpressionDef)))
|
||||
(defclause* '(defclause [ag:var var]
|
||||
field-or-expression FieldOrExpressionDef)))
|
||||
;; =>
|
||||
(do :metabase.legacy-mbql.schema/var [FieldOrExpressionDef])
|
||||
(def ag:var "Docstring." [FieldOrExpressionDef])
|
||||
|
||||
(defn- y []
|
||||
(expand-defclause '(defclause var
|
||||
x (some-list-schema)
|
||||
y (optional FieldOrExpressionDef)
|
||||
z (rest (some-other-schema)))))
|
||||
(defclause* '(defclause var
|
||||
x (some-list-schema)
|
||||
y (optional FieldOrExpressionDef)
|
||||
z (rest (some-other-schema)))))
|
||||
;; =>
|
||||
(do :metabase.legacy-mbql.schema/var [(some-list-schema) FieldOrExpressionDef (some-other-schema)]))
|
||||
|
||||
(defn one-of [{:keys [node], :as x}]
|
||||
(let [[_one-of & schema-nodes] (:children node)
|
||||
node' (-> (hooks/vector-node
|
||||
(mapv (fn [schema-node]
|
||||
(if (and (hooks/token-node? schema-node)
|
||||
(simple-symbol? (hooks/sexpr schema-node)))
|
||||
(-> (hooks/keyword-node (keyword "mteabase.legacy-mbql.schema" (name (hooks/sexpr schema-node))))
|
||||
(with-meta (meta schema-node)))
|
||||
schema-node))
|
||||
schema-nodes))
|
||||
(with-meta (meta node)))]
|
||||
(assoc x :node node')))
|
||||
|
||||
(comment
|
||||
(defn- expand-one-of [form]
|
||||
(hooks/sexpr (:node (one-of {:node (hooks/parse-string (with-out-str (clojure.pprint/pprint form)))}))))
|
||||
|
||||
(defn- z []
|
||||
(expand-one-of '(one-of absolute-datetime relative-datetime time))))
|
||||
(def var "Docstring." [(some-list-schema) FieldOrExpressionDef (some-other-schema)]))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
(ns hooks.metabase.test.util
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]))
|
||||
(:require [clj-kondo.hooks-api :as hooks]
|
||||
[hooks.common :as common]))
|
||||
|
||||
(defn- namespaced-symbol-node? [node]
|
||||
(when (hooks/token-node? node)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
(ns hooks.metabase.api.macros-test
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as api]
|
||||
[clj-kondo.impl.utils]
|
||||
[clojure.test :refer :all]
|
||||
[hooks.metabase.api.macros]))
|
||||
|
||||
@@ -70,41 +69,3 @@
|
||||
(respond :x)
|
||||
(catch Throwable e
|
||||
(raise e))))))))
|
||||
|
||||
(defn- defendpoint [form]
|
||||
(hooks.metabase.api.macros/defendpoint
|
||||
{:node (-> form pr-str api/parse-string)}))
|
||||
|
||||
(defn- defendpoint-warnings
|
||||
[form]
|
||||
(binding [clj-kondo.impl.utils/*ctx* {:config {:linters {:metabase/validate-defendpoint-route-uses-kebab-case {:level :warning}
|
||||
:metabase/validate-defendpoint-query-params-use-kebab-case {:level :warning}}}
|
||||
:ignores (atom nil)
|
||||
:findings (atom [])
|
||||
:namespaces (atom {})}]
|
||||
(defendpoint form)
|
||||
@(:findings clj-kondo.impl.utils/*ctx*)))
|
||||
|
||||
(deftest ^:parallel validate-route-kebab-case-test
|
||||
(is (=? [{:message "REST API routes and query params should use kebab-case, not snake_case"
|
||||
:type :metabase/validate-defendpoint-route-uses-kebab-case
|
||||
:row pos-int?
|
||||
:col pos-int?}]
|
||||
(defendpoint-warnings
|
||||
'(api.macros/defendpoint :get "/:id/syncable_schemas" ; snake_case route should trigger a warning
|
||||
"Returns a list of all syncable schemas found for the database `id`."
|
||||
[{:keys [id]}]
|
||||
id)))))
|
||||
|
||||
(deftest ^:parallel validate-query-params-kebab-case-test
|
||||
(is (=? [{:message "REST API routes and query params should use kebab-case, not snake_case",
|
||||
:type :metabase/validate-defendpoint-query-params-use-kebab-case
|
||||
:row pos-int?
|
||||
:col pos-int?}]
|
||||
(defendpoint-warnings
|
||||
'(api.macros/defendpoint :get "/:id/syncable-schemas"
|
||||
"Returns a list of all syncable schemas found for the database `id`."
|
||||
[{:keys [id]}
|
||||
{:keys [filter_by]} :- [:map
|
||||
[:filter_by {:optional true} :string]]]
|
||||
id)))))
|
||||
|
||||
624
.cljfmt.edn
624
.cljfmt.edn
@@ -38,317 +38,321 @@
|
||||
;; zero get indented 2 spaces if not on the first line regardless of number of elements on the first line.
|
||||
:extra-indents
|
||||
{;; clojure.core stuff
|
||||
fn* [[:inner 0]]
|
||||
let* [[:block 1]]
|
||||
with-meta [[:default]]
|
||||
fn* [[:inner 0]]
|
||||
let* [[:block 1]]
|
||||
with-meta [[:default]]
|
||||
;; library stuff. Libraries that specify their own `:style/indent` specs are included in the section after this
|
||||
;; since it is generated automatically.
|
||||
clojure.core.match/match [[:block 1]]
|
||||
clojure.test.check.properties/for-all [[:block 1]]
|
||||
clojure.test.check.properties/for-all* [[:block 1]]
|
||||
clojure.test.check/quick-check [[:block 1]]
|
||||
clojure.tools.macro/macrolet [[:block 1] [:inner 2 0]]
|
||||
clojurewerkz.quartzite.jobs/build [[:default]]
|
||||
clojurewerkz.quartzite.jobs/with-description [[:default]]
|
||||
clojurewerkz.quartzite.jobs/with-identity [[:default]]
|
||||
clojurewerkz.quartzite.schedule.cron/schedule [[:default]]
|
||||
clojurewerkz.quartzite.schedule.cron/with-misfire-handling-instruction-do-nothing [[:default]]
|
||||
clojurewerkz.quartzite.triggers/build [[:default]]
|
||||
clojurewerkz.quartzite.triggers/with-description [[:default]]
|
||||
clojurewerkz.quartzite.triggers/with-identity [[:default]]
|
||||
clojurewerkz.quartzite.triggers/with-priority [[:default]]
|
||||
clojurewerkz.quartzite.triggers/with-schedule [[:default]]
|
||||
def-map-type [[:block 2] [:inner 1]]
|
||||
defprotocol+ [[:block 1] [:inner 1]]
|
||||
defrecord+ [[:block 2] [:inner 1]]
|
||||
deftype+ [[:block 2] [:inner 1]]
|
||||
net.cgrand.macrovich/case [[:block 0]]
|
||||
potemkin.namespaces/import-vars [[:default]]
|
||||
potemkin.types/def-map-type [[:block 2] [:inner 1]]
|
||||
potemkin.types/defprotocol+ [[:block 1] [:inner 1]]
|
||||
potemkin.types/defrecord+ [[:block 2] [:inner 1]]
|
||||
potemkin.types/deftype+ [[:block 2] [:inner 1]]
|
||||
potemkin/def-map-type [[:block 2] [:inner 1]]
|
||||
potemkin/defprotocol+ [[:block 1] [:inner 1]]
|
||||
potemkin/defrecord+ [[:block 2] [:inner 1]]
|
||||
potemkin/deftype+ [[:block 2] [:inner 1]]
|
||||
potemkin/import-vars [[:default]]
|
||||
taoensso.nippy/extend-freeze [[:inner 0]]
|
||||
taoensso.nippy/extend-thaw [[:inner 0]]
|
||||
throttle.core/with-throttling [[:block 1]]
|
||||
clojure.core.logic/fresh [[:block 1]]
|
||||
clojure.core.logic/matcha [[:block 1]]
|
||||
clojure.core.logic/run [[:block 2]]
|
||||
clojure.core.match/match [[:block 1]]
|
||||
clojure.test.check.properties/for-all [[:block 1]]
|
||||
clojure.test.check.properties/for-all* [[:block 1]]
|
||||
clojure.test.check/quick-check [[:block 1]]
|
||||
clojure.tools.macro/macrolet [[:block 1] [:inner 2 0]]
|
||||
clojurewerkz.quartzite.jobs/build [[:default]]
|
||||
clojurewerkz.quartzite.jobs/with-description [[:default]]
|
||||
clojurewerkz.quartzite.jobs/with-identity [[:default]]
|
||||
clojurewerkz.quartzite.schedule.cron/schedule [[:default]]
|
||||
clojurewerkz.quartzite.schedule.cron/with-misfire-handling-instruction-do-nothing [[:default]]
|
||||
clojurewerkz.quartzite.triggers/build [[:default]]
|
||||
clojurewerkz.quartzite.triggers/with-description [[:default]]
|
||||
clojurewerkz.quartzite.triggers/with-identity [[:default]]
|
||||
clojurewerkz.quartzite.triggers/with-priority [[:default]]
|
||||
clojurewerkz.quartzite.triggers/with-schedule [[:default]]
|
||||
def-map-type [[:block 2] [:inner 1]]
|
||||
defprotocol+ [[:block 1] [:inner 1]]
|
||||
defrecord+ [[:block 2] [:inner 1]]
|
||||
deftype+ [[:block 2] [:inner 1]]
|
||||
net.cgrand.macrovich/case [[:block 0]]
|
||||
potemkin.namespaces/import-vars [[:default]]
|
||||
potemkin.types/def-map-type [[:block 2] [:inner 1]]
|
||||
potemkin.types/defprotocol+ [[:block 1] [:inner 1]]
|
||||
potemkin.types/defrecord+ [[:block 2] [:inner 1]]
|
||||
potemkin.types/deftype+ [[:block 2] [:inner 1]]
|
||||
potemkin/def-map-type [[:block 2] [:inner 1]]
|
||||
potemkin/defprotocol+ [[:block 1] [:inner 1]]
|
||||
potemkin/defrecord+ [[:block 2] [:inner 1]]
|
||||
potemkin/deftype+ [[:block 2] [:inner 1]]
|
||||
potemkin/import-vars [[:default]]
|
||||
taoensso.nippy/extend-freeze [[:inner 0]]
|
||||
taoensso.nippy/extend-thaw [[:inner 0]]
|
||||
throttle.core/with-throttling [[:block 1]]
|
||||
;; the rest of the stuff in this list is generated automatically based on `:style/indent` specs
|
||||
;; by running [[dev.generate-cljfmt-config/specs]]... use that code to update this list
|
||||
metabase-enterprise.cache.config-test/with-temp-persist-models [[:block 1]]
|
||||
metabase-enterprise.impersonation.util-test/with-impersonations! [[:inner 0]]
|
||||
metabase-enterprise.sandbox.test-util/with-gtaps! [[:inner 0]]
|
||||
metabase-enterprise.sandbox.test-util/with-gtaps-for-user! [[:inner 0]]
|
||||
metabase-enterprise.sandbox.test-util/with-user-attributes [[:block 2]]
|
||||
metabase-enterprise.serialization.test-util/with-dbs [[:inner 0]]
|
||||
metabase-enterprise.serialization.test-util/with-random-dump-dir [[:block 1]]
|
||||
metabase-enterprise.serialization.test-util/with-world [[:block 0]]
|
||||
metabase-enterprise.sso.integrations.jwt-test/with-users-with-email-deleted [[:block 1]]
|
||||
metabase-enterprise.test/with-gtaps! [[:inner 0]]
|
||||
metabase-enterprise.test/with-gtaps-for-user! [[:inner 0]]
|
||||
metabase-enterprise.test/with-user-attributes [[:block 2]]
|
||||
metabase.actions.actions-test/with-actions-test-data-and-actions-permissively-enabled [[:block 0]]
|
||||
metabase.actions.test-util/with-actions [[:block 1]]
|
||||
metabase.actions.test-util/with-actions-disabled [[:block 0]]
|
||||
metabase.actions.test-util/with-actions-enabled [[:block 0]]
|
||||
metabase.actions.test-util/with-actions-test-data [[:inner 0]]
|
||||
metabase.actions.test-util/with-actions-test-data-and-actions-enabled [[:block 0]]
|
||||
metabase.actions.test-util/with-actions-test-data-tables [[:block 1]]
|
||||
metabase.actions.test-util/with-empty-db [[:inner 0]]
|
||||
metabase.actions.test-util/with-temp-test-data [[:inner 0]]
|
||||
metabase.api.common/check [[:default]]
|
||||
metabase.api.common/define-routes [[:block 0]]
|
||||
metabase.api.common/let-404 [[:block 1]]
|
||||
metabase.app-db.core/with-application-db [[:inner 0]]
|
||||
metabase.app-db.liquibase/with-liquibase [[:block 1]]
|
||||
metabase.app-db.liquibase/with-scope-locked [[:block 1]]
|
||||
metabase.app-db.schema-migrations-test.impl/test-migrations [[:block 2]]
|
||||
metabase.cmd.copy/step [[:block 1]]
|
||||
metabase.cmd.copy/with-connection-rollback-only [[:block 1]]
|
||||
metabase.cmd.copy/with-disabled-db-constraints [[:block 2]]
|
||||
metabase.collections-rest.api-test/with-collection-hierarchy [[:block 1]]
|
||||
metabase.collections-rest.api-test/with-french-user-and-personal-collection [[:block 2]]
|
||||
metabase.collections-rest.api-test/with-some-children-of-collection [[:block 1]]
|
||||
metabase.collections.models.collection-test/with-collection-hierarchy [[:block 1]]
|
||||
metabase.collections.models.collection-test/with-current-user-perms-for-collections [[:block 1]]
|
||||
metabase.collections.models.collection-test/with-personal-and-impersonal-collections [[:block 1]]
|
||||
metabase.config/build-type-case [[:block 0]]
|
||||
metabase.dashboards.models.dashboard-test/with-dash-in-collection [[:inner 0]]
|
||||
metabase.driver-api.core/match [[:inner 0]]
|
||||
metabase.driver-api.core/match-one [[:inner 0]]
|
||||
metabase.driver-api.core/replace [[:inner 0]]
|
||||
metabase.driver-api.core/replace-in [[:inner 0]]
|
||||
metabase.driver.bigquery-cloud-sdk.query-processor/with-temporal-type [[:default]]
|
||||
metabase.driver.druid.client/DELETE [[:default]]
|
||||
metabase.driver.druid.client/GET [[:default]]
|
||||
metabase.driver.druid.client/POST [[:default]]
|
||||
metabase.driver.druid.query-processor-test/druid-query [[:block 0]]
|
||||
metabase.driver.druid.query-processor-test/druid-query-returning-rows [[:block 0]]
|
||||
metabase.driver.mongo.query-processor/mongo-let [[:block 1]]
|
||||
metabase.driver.sql-jdbc.actions/with-jdbc-transaction [[:block 1]]
|
||||
metabase.driver.sql-jdbc.connection/with-connection-spec-for-testing-connection [[:block 1]]
|
||||
metabase.driver.sql-jdbc.execute.diagnostic/capturing-diagnostic-info [[:block 1]]
|
||||
metabase.driver.sql.query-processor/apply-top-level-clause [[:default]]
|
||||
metabase.driver/with-driver [[:block 1]]
|
||||
metabase.embedding-rest.api.embed-test/do-response-formats [[:block 1]]
|
||||
metabase.embedding-rest.api.embed-test/with-embedding-enabled-and-new-secret-key [[:block 0]]
|
||||
metabase.embedding-rest.api.embed-test/with-embedding-enabled-and-temp-card-referencing [[:block 3]]
|
||||
metabase.embedding-rest.api.embed-test/with-embedding-enabled-and-temp-dashcard-referencing [[:block 3]]
|
||||
metabase.embedding-rest.api.embed-test/with-new-secret-key [[:block 0]]
|
||||
metabase.embedding-rest.api.embed-test/with-temp-card [[:block 1]]
|
||||
metabase.embedding-rest.api.embed-test/with-temp-dashcard [[:block 1]]
|
||||
metabase.embedding-rest.api.preview-embed-test/with-embedding-enabled-and-new-secret-key [[:block 0]]
|
||||
metabase.embedding-rest.api.preview-embed-test/with-new-secret-key [[:block 0]]
|
||||
metabase.lib.binning/with-binning [[:default]]
|
||||
metabase.lib.core/with-binning [[:default]]
|
||||
metabase.lib.core/with-join-alias [[:default]]
|
||||
metabase.lib.core/with-join-conditions [[:default]]
|
||||
metabase.lib.join/with-join-alias [[:default]]
|
||||
metabase.lib.join/with-join-conditions [[:default]]
|
||||
metabase.lib.test-util.macros/$ids [[:inner 0]]
|
||||
metabase.lib.test-util.macros/mbql-5-query [[:inner 0]]
|
||||
metabase.lib.test-util.macros/mbql-query [[:inner 0]]
|
||||
metabase.lib.util.match/match [[:inner 0]]
|
||||
metabase.lib.util.match/match-one [[:inner 0]]
|
||||
metabase.lib.util.match/match-lite [[:inner 0]]
|
||||
metabase.lib.util.match/replace [[:inner 0]]
|
||||
metabase.lib.util.match/replace-in [[:inner 0]]
|
||||
metabase.models.interface/define-batched-hydration-method [[:inner 0]]
|
||||
metabase.models.interface/define-simple-hydration-method [[:inner 0]]
|
||||
metabase.parameters.chain-filter-test/with-clean-field-values-for-field [[:block 1]]
|
||||
metabase.parameters.chain-filter-test/with-fk-field-to-field-remapping! [[:block 0]]
|
||||
metabase.parameters.chain-filter-test/with-human-readable-values-remapping! [[:block 0]]
|
||||
metabase.permissions.core/allow-changing-all-users-group-members [[:block 0]]
|
||||
metabase.permissions.core/disable-perms-cache [[:block 0]]
|
||||
metabase.public-sharing-rest.api-test/with-sharing-enabled-and-temp-card-referencing [[:block 3]]
|
||||
metabase.public-sharing-rest.api-test/with-sharing-enabled-and-temp-dashcard-referencing [[:block 3]]
|
||||
metabase.public-sharing-rest.api-test/with-temp-public-card [[:block 1]]
|
||||
metabase.public-sharing-rest.api-test/with-temp-public-dashboard [[:block 1]]
|
||||
metabase.public-sharing-rest.api-test/with-temp-public-dashboard-and-card [[:block 1]]
|
||||
metabase.pulse.api.alert-test/with-alert-in-collection [[:block 1]]
|
||||
metabase.pulse.dashboard-subscription-test/with-link-card-fixture-for-dashboard [[:block 2]]
|
||||
metabase.pulse.models.pulse-test/with-dashboard-subscription-in-collection [[:block 1]]
|
||||
metabase.pulse.models.pulse-test/with-pulse-in-collection [[:inner 0]]
|
||||
metabase.queries-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.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]]
|
||||
metabase.request.core/with-limit-and-offset [[:inner 0]]
|
||||
metabase.request.session/as-admin [[:block 0]]
|
||||
metabase.request.session/with-current-user [[:inner 0]]
|
||||
metabase.sample-data.impl-test/with-temp-sample-database-db [[:block 1]]
|
||||
metabase.search.api-test/do-test-users [[:block 1]]
|
||||
metabase.server.middleware.session/as-admin [[:block 0]]
|
||||
metabase.server.middleware.session/with-current-user [[:inner 0]]
|
||||
metabase.server.streaming-response-test/with-streaming-response-thread-pool [[:block 0]]
|
||||
metabase.server.streaming-response-test/with-test-driver-db! [[:block 0]]
|
||||
metabase.server.streaming-response/streaming-response [[:block 2]]
|
||||
metabase.session.api/http-401-on-error [[:block 0]]
|
||||
metabase.settings.core/define-multi-setting [[:inner 0]]
|
||||
metabase.settings.core/defsetting [[:block 1]]
|
||||
metabase.settings.models.setting.multi-setting/define-multi-setting [[:inner 0]]
|
||||
metabase.settings.models.setting/defsetting [[:block 1]]
|
||||
metabase.sync.fetch-metadata/log-if-error [[:block 1]]
|
||||
metabase.sync.sync-metadata.fields-test/with-test-db [[:block 0]]
|
||||
metabase.sync.util/do-with-start-and-finish-debug-logging [[:default]]
|
||||
metabase.sync.util/sum-for [[:block 1]]
|
||||
metabase.sync.util/sync-operation [[:block 3]]
|
||||
metabase.sync.util/with-db-logging-disabled [[:default]]
|
||||
metabase.sync.util/with-duplicate-ops-prevented [[:default]]
|
||||
metabase.sync.util/with-emoji-progress-bar [[:block 1]]
|
||||
metabase.sync.util/with-error-handling [[:block 1]]
|
||||
metabase.sync.util/with-start-and-finish-logging [[:default]]
|
||||
metabase.sync.util/with-start-and-finish-logging* [[:default]]
|
||||
metabase.sync.util/with-sync-events [[:default]]
|
||||
metabase.task-history.models.task-history/with-task-history [[:block 1]]
|
||||
metabase.task.core/rerun-on-error [[:block 1]]
|
||||
metabase.task.impl/rerun-on-error [[:block 1]]
|
||||
metabase.test.data.datasets/test-driver [[:inner 0]]
|
||||
metabase.test.data.datasets/test-drivers [[:inner 0]]
|
||||
metabase.test.data.one-off-dbs/with-blank-db [[:block 0]]
|
||||
metabase.test.data.one-off-dbs/with-blueberries-db [[:block 0]]
|
||||
metabase.test.data.users/with-test-user [[:inner 0]]
|
||||
metabase.test.data/$ids [[:inner 0]]
|
||||
metabase.test.data/dataset [[:inner 0]]
|
||||
metabase.test.data/mbql-query [[:inner 0]]
|
||||
metabase.test.data/query [[:block 1]]
|
||||
metabase.test.data/run-mbql-query [[:inner 0]]
|
||||
metabase.test.data/with-empty-h2-app-db! [[:block 0]]
|
||||
metabase.test.data/with-temp-copy-of-db [[:block 0]]
|
||||
metabase.test.util.public-settings/with-additional-premium-features [[:block 1]]
|
||||
metabase.test.util.public-settings/with-premium-features [[:block 1]]
|
||||
metabase.test.util.thread-local/test-helpers-set-global-values! [[:block 0]]
|
||||
metabase.test.util/discard-setting-changes [[:block 1]]
|
||||
metabase.test.util/let-url [[:block 1]]
|
||||
metabase.test.util/throw-if-called! [[:block 1]]
|
||||
metabase.test.util/with-temp-scheduler [[:block 0]]
|
||||
metabase.test.util/with-temp-vals-in-db [[:block 3]]
|
||||
metabase.test.util/with-user-in-groups [[:inner 0]]
|
||||
metabase.test/$ids [[:inner 0]]
|
||||
metabase.test/as-admin [[:inner 0]]
|
||||
metabase.test/dataset [[:inner 0]]
|
||||
metabase.test/discard-setting-changes [[:block 1]]
|
||||
metabase.test/format-rows-by [[:default]]
|
||||
metabase.test/let-url [[:block 1]]
|
||||
metabase.test/mbql-query [[:inner 0]]
|
||||
metabase.test/query [[:block 1]]
|
||||
metabase.test/run-mbql-query [[:inner 0]]
|
||||
metabase.test/test-driver [[:inner 0]]
|
||||
metabase.test/test-drivers [[:inner 0]]
|
||||
metabase.test/test-helpers-set-global-values! [[:block 0]]
|
||||
metabase.test/throw-if-called! [[:block 1]]
|
||||
metabase.test/with-actions [[:block 1]]
|
||||
metabase.test/with-actions-disabled [[:block 0]]
|
||||
metabase.test/with-actions-enabled [[:block 0]]
|
||||
metabase.test/with-actions-test-data [[:inner 0]]
|
||||
metabase.test/with-actions-test-data-and-actions-enabled [[:block 0]]
|
||||
metabase.test/with-actions-test-data-tables [[:block 1]]
|
||||
metabase.test/with-additional-premium-features [[:block 1]]
|
||||
metabase.test/with-current-user [[:inner 0]]
|
||||
metabase.test/with-driver [[:block 1]]
|
||||
metabase.test/with-empty-db [[:inner 0]]
|
||||
metabase.test/with-empty-h2-app-db! [[:block 0]]
|
||||
metabase.test/with-metadata-provider [[:inner 0]]
|
||||
metabase.test/with-premium-features [[:block 1]]
|
||||
metabase.test/with-temp [[:inner 0]]
|
||||
metabase.test/with-temp-copy-of-db [[:block 0]]
|
||||
metabase.test/with-temp-scheduler [[:block 0]]
|
||||
metabase.test/with-temp-test-data [[:inner 0]]
|
||||
metabase.test/with-temp-vals-in-db [[:block 3]]
|
||||
metabase.test/with-test-user [[:inner 0]]
|
||||
metabase.test/with-user-in-groups [[:inner 0]]
|
||||
metabase.upload.impl-test/with-upload-table! [[:inner 0]]
|
||||
metabase.upload.types/with-currency [[:default]]
|
||||
metabase.upload.types/with-parens [[:default]]
|
||||
metabase.util.async/cancelable-thread [[:block 0]]
|
||||
metabase.util.date-2/with-time-zone-same-instant [[:default]]
|
||||
metabase.util.encryption-test/with-secret-key [[:block 1]]
|
||||
metabase.util.honey-sql-2/with-database-type-info [[:default]]
|
||||
metabase.util.i18n/deferred-trs [[:default]]
|
||||
metabase.util.i18n/deferred-tru [[:default]]
|
||||
metabase.util.i18n/trs [[:default]]
|
||||
metabase.util.i18n/trsn [[:default]]
|
||||
metabase.util.i18n/tru [[:default]]
|
||||
metabase.util.i18n/trun [[:default]]
|
||||
metabase.util.jvm/auto-retry [[:block 1]]
|
||||
metabase.util.jvm/varargs [[:block 1]]
|
||||
metabase.util.jvm/with-us-locale [[:block 0]]
|
||||
metabase.util.malli/disable-enforcement [[:block 0]]
|
||||
metabase.util.malli/with [[:default]]
|
||||
metabase.util.malli/with-api-error-message [[:default]]
|
||||
metabase.util.namespaces/import-fns [[:default]]
|
||||
metabase.util.queue-test/await-test-while [[:block 1]]
|
||||
metabase.util/auto-retry [[:block 1]]
|
||||
metabase.util/case-enum [[:block 1]]
|
||||
metabase.util/for-map [[:block 1]]
|
||||
metabase.util/ignore-exceptions [[:block 0]]
|
||||
metabase.util/or-with [[:block 1]]
|
||||
metabase.util/profile [[:block 1]]
|
||||
metabase.util/prog1 [[:inner 0]]
|
||||
metabase.util/varargs [[:inner 0]]
|
||||
metabase.util/with-us-locale [[:block 0]]
|
||||
metabuild-common.core/exit-when-finished-nonzero-on-exception [[:block 0]]
|
||||
metabuild-common.core/step [[:block 1]]
|
||||
metabuild-common.entrypoint/exit-when-finished-nonzero-on-exception [[:block 0]]
|
||||
metabuild-common.steps/step [[:block 1]]
|
||||
methodical.core/defmethod [[:inner 0]]
|
||||
methodical.core/defmulti [[:inner 0]]
|
||||
methodical.core/with-dispatcher [[:default]]
|
||||
methodical.core/with-method-table [[:default]]
|
||||
methodical.core/with-prefers [[:default]]
|
||||
methodical.interface/with-dispatcher [[:default]]
|
||||
methodical.interface/with-method-table [[:default]]
|
||||
methodical.interface/with-prefers [[:default]]
|
||||
methodical.macros/defmethod [[:inner 0]]
|
||||
methodical.macros/defmulti [[:inner 0]]
|
||||
saml20-clj.sp.response/validate-confirmation-datas [[:block 1]]
|
||||
toucan2.connection/with-transaction [[:block 1]]
|
||||
toucan2.core/build [[:block 0]]
|
||||
toucan2.core/compile [[:block 0]]
|
||||
toucan2.core/debug [[:block 0]]
|
||||
toucan2.core/define-after-insert [[:inner 0]]
|
||||
toucan2.core/define-after-select [[:inner 0]]
|
||||
toucan2.core/define-after-update [[:inner 0]]
|
||||
toucan2.core/define-before-delete [[:inner 0]]
|
||||
toucan2.core/define-before-insert [[:inner 0]]
|
||||
toucan2.core/define-before-select [[:inner 0]]
|
||||
toucan2.core/define-default-fields [[:inner 0]]
|
||||
toucan2.core/define-named-query [[:block 1]]
|
||||
toucan2.core/deftransforms [[:block 1]]
|
||||
toucan2.core/with-transaction [[:block 1]]
|
||||
toucan2.tools.after-insert/define-after-insert [[:inner 0]]
|
||||
toucan2.tools.after-select/define-after-select [[:inner 0]]
|
||||
toucan2.tools.after-update/define-after-update [[:inner 0]]
|
||||
toucan2.tools.after/define-after-impl [[:default]]
|
||||
toucan2.tools.before-delete/define-before-delete [[:inner 0]]
|
||||
toucan2.tools.before-insert/define-before-insert [[:inner 0]]
|
||||
toucan2.tools.before-select/define-before-select [[:inner 0]]
|
||||
toucan2.tools.compile/build [[:block 0]]
|
||||
toucan2.tools.compile/compile [[:block 0]]
|
||||
toucan2.tools.compile/resolved [[:block 0]]
|
||||
toucan2.tools.debug/debug [[:block 0]]
|
||||
toucan2.tools.default-fields/define-default-fields [[:inner 0]]
|
||||
toucan2.tools.named-query/define-named-query [[:block 1]]
|
||||
toucan2.tools.simple-out-transform/define-out-transform [[:inner 0]]
|
||||
toucan2.tools.transformed/deftransforms [[:block 1]]
|
||||
toucan2.tools.with-temp/with-temp [[:inner 0]]
|
||||
toucan2.util/try-with-error-context [[:inner 0]]}}
|
||||
metabase-enterprise.cache.config-test/with-temp-persist-models [[:block 1]]
|
||||
metabase-enterprise.impersonation.util-test/with-impersonations! [[:inner 0]]
|
||||
metabase-enterprise.sandbox.test-util/with-gtaps! [[:inner 0]]
|
||||
metabase-enterprise.sandbox.test-util/with-gtaps-for-user! [[:inner 0]]
|
||||
metabase-enterprise.sandbox.test-util/with-user-attributes [[:block 2]]
|
||||
metabase-enterprise.serialization.test-util/with-dbs [[:inner 0]]
|
||||
metabase-enterprise.serialization.test-util/with-random-dump-dir [[:block 1]]
|
||||
metabase-enterprise.serialization.test-util/with-world [[:block 0]]
|
||||
metabase-enterprise.sso.integrations.jwt-test/with-users-with-email-deleted [[:block 1]]
|
||||
metabase-enterprise.test/with-gtaps! [[:inner 0]]
|
||||
metabase-enterprise.test/with-gtaps-for-user! [[:inner 0]]
|
||||
metabase-enterprise.test/with-user-attributes [[:block 2]]
|
||||
metabase.actions.actions-test/with-actions-test-data-and-actions-permissively-enabled [[:block 0]]
|
||||
metabase.actions.test-util/with-actions [[:block 1]]
|
||||
metabase.actions.test-util/with-actions-disabled [[:block 0]]
|
||||
metabase.actions.test-util/with-actions-enabled [[:block 0]]
|
||||
metabase.actions.test-util/with-actions-test-data [[:inner 0]]
|
||||
metabase.actions.test-util/with-actions-test-data-and-actions-enabled [[:block 0]]
|
||||
metabase.actions.test-util/with-actions-test-data-tables [[:block 1]]
|
||||
metabase.actions.test-util/with-empty-db [[:inner 0]]
|
||||
metabase.actions.test-util/with-temp-test-data [[:inner 0]]
|
||||
metabase.api.common/check [[:default]]
|
||||
metabase.api.common/define-routes [[:block 0]]
|
||||
metabase.api.common/let-404 [[:block 1]]
|
||||
metabase.app-db.core/with-application-db [[:inner 0]]
|
||||
metabase.app-db.liquibase/with-liquibase [[:block 1]]
|
||||
metabase.app-db.liquibase/with-scope-locked [[:block 1]]
|
||||
metabase.app-db.schema-migrations-test.impl/test-migrations [[:block 2]]
|
||||
metabase.cmd.copy/step [[:block 1]]
|
||||
metabase.cmd.copy/with-connection-rollback-only [[:block 1]]
|
||||
metabase.cmd.copy/with-disabled-db-constraints [[:block 2]]
|
||||
metabase.collections.api-test/with-collection-hierarchy [[:block 1]]
|
||||
metabase.collections.api-test/with-french-user-and-personal-collection [[:block 2]]
|
||||
metabase.collections.api-test/with-some-children-of-collection [[:block 1]]
|
||||
metabase.collections.models.collection-test/with-collection-hierarchy [[:block 1]]
|
||||
metabase.collections.models.collection-test/with-current-user-perms-for-collections [[:block 1]]
|
||||
metabase.collections.models.collection-test/with-personal-and-impersonal-collections [[:block 1]]
|
||||
metabase.config/build-type-case [[:block 0]]
|
||||
metabase.dashboards.models.dashboard-test/with-dash-in-collection [[:inner 0]]
|
||||
metabase.driver-api.core/match [[:inner 0]]
|
||||
metabase.driver-api.core/match-one [[:inner 0]]
|
||||
metabase.driver-api.core/replace [[:inner 0]]
|
||||
metabase.driver-api.core/replace-in [[:inner 0]]
|
||||
metabase.driver.bigquery-cloud-sdk.query-processor/with-temporal-type [[:default]]
|
||||
metabase.driver.druid.client/DELETE [[:default]]
|
||||
metabase.driver.druid.client/GET [[:default]]
|
||||
metabase.driver.druid.client/POST [[:default]]
|
||||
metabase.driver.druid.query-processor-test/druid-query [[:block 0]]
|
||||
metabase.driver.druid.query-processor-test/druid-query-returning-rows [[:block 0]]
|
||||
metabase.driver.mongo.query-processor/mongo-let [[:block 1]]
|
||||
metabase.driver.sql-jdbc.actions/with-jdbc-transaction [[:block 1]]
|
||||
metabase.driver.sql-jdbc.connection/with-connection-spec-for-testing-connection [[:block 1]]
|
||||
metabase.driver.sql-jdbc.execute.diagnostic/capturing-diagnostic-info [[:block 1]]
|
||||
metabase.driver.sql.query-processor/apply-top-level-clause [[:default]]
|
||||
metabase.driver/with-driver [[:block 1]]
|
||||
metabase.embedding.api.embed-test/do-response-formats [[:block 1]]
|
||||
metabase.embedding.api.embed-test/with-embedding-enabled-and-new-secret-key [[:block 0]]
|
||||
metabase.embedding.api.embed-test/with-embedding-enabled-and-temp-card-referencing [[:block 3]]
|
||||
metabase.embedding.api.embed-test/with-embedding-enabled-and-temp-dashcard-referencing [[:block 3]]
|
||||
metabase.embedding.api.embed-test/with-new-secret-key [[:block 0]]
|
||||
metabase.embedding.api.embed-test/with-temp-card [[:block 1]]
|
||||
metabase.embedding.api.embed-test/with-temp-dashcard [[:block 1]]
|
||||
metabase.embedding.api.preview-embed-test/with-embedding-enabled-and-new-secret-key [[:block 0]]
|
||||
metabase.embedding.api.preview-embed-test/with-new-secret-key [[:block 0]]
|
||||
metabase.lib.binning/with-binning [[:default]]
|
||||
metabase.lib.core/with-binning [[:default]]
|
||||
metabase.lib.core/with-join-alias [[:default]]
|
||||
metabase.lib.core/with-join-conditions [[:default]]
|
||||
metabase.lib.join/with-join-alias [[:default]]
|
||||
metabase.lib.join/with-join-conditions [[:default]]
|
||||
metabase.lib.test-util.macros/$ids [[:inner 0]]
|
||||
metabase.lib.test-util.macros/mbql-5-query [[:inner 0]]
|
||||
metabase.lib.test-util.macros/mbql-query [[:inner 0]]
|
||||
metabase.lib.util.match/match [[:inner 0]]
|
||||
metabase.lib.util.match/match-one [[:inner 0]]
|
||||
metabase.lib.util.match/match-lite [[:inner 0]]
|
||||
metabase.lib.util.match/match-lite-recursive [[:inner 0]]
|
||||
metabase.lib.util.match/replace [[:inner 0]]
|
||||
metabase.lib.util.match/replace-in [[:inner 0]]
|
||||
metabase.models.interface/define-batched-hydration-method [[:inner 0]]
|
||||
metabase.models.interface/define-simple-hydration-method [[:inner 0]]
|
||||
metabase.parameters.chain-filter-test/with-clean-field-values-for-field [[:block 1]]
|
||||
metabase.parameters.chain-filter-test/with-fk-field-to-field-remapping [[:block 0]]
|
||||
metabase.parameters.chain-filter-test/with-human-readable-values-remapping [[:block 0]]
|
||||
metabase.permissions.core/allow-changing-all-users-group-members [[:block 0]]
|
||||
metabase.permissions.core/disable-perms-cache [[:block 0]]
|
||||
metabase.public-sharing.api-test/with-sharing-enabled-and-temp-card-referencing [[:block 3]]
|
||||
metabase.public-sharing.api-test/with-sharing-enabled-and-temp-dashcard-referencing [[:block 3]]
|
||||
metabase.public-sharing.api-test/with-temp-public-card [[:block 1]]
|
||||
metabase.public-sharing.api-test/with-temp-public-dashboard [[:block 1]]
|
||||
metabase.public-sharing.api-test/with-temp-public-dashboard-and-card [[:block 1]]
|
||||
metabase.pulse.api.alert-test/with-alert-in-collection [[:block 1]]
|
||||
metabase.pulse.dashboard-subscription-test/with-link-card-fixture-for-dashboard [[:block 2]]
|
||||
metabase.pulse.models.pulse-test/with-dashboard-subscription-in-collection [[:block 1]]
|
||||
metabase.pulse.models.pulse-test/with-pulse-in-collection [[:inner 0]]
|
||||
metabase.queries.api.card-test/with-cards-in-readable-collection [[:block 1]]
|
||||
metabase.queries.api.card-test/with-cards-in-writeable-collection [[:block 1]]
|
||||
metabase.queries.api.card-test/with-ordered-items [[:inner 0]]
|
||||
metabase.queries.api.card-test/with-temp-native-card! [[:block 1]]
|
||||
metabase.queries.api.card-test/with-temp-native-card-with-params! [[:block 1]]
|
||||
metabase.query-processor-test.pipeline-queries-test/pmbql-query [[:block 1]]
|
||||
metabase.query-processor-test.pipeline-queries-test/run-pmbql-query [[:block 1]]
|
||||
metabase.query-processor.debug/debug> [[:block 0]]
|
||||
metabase.query-processor.error-type/deferror [[:block 1]]
|
||||
metabase.query-processor.middleware.cache-backend.interface/with-cached-results [[:block 4]]
|
||||
metabase.query-processor.middleware.process-userland-query-test/with-query-execution [[:block 1]]
|
||||
metabase.query-processor.store/cached [[:block 1]]
|
||||
metabase.query-processor.store/with-metadata-provider [[:inner 0]]
|
||||
metabase.query-processor.streaming/streaming-response [[:block 1]]
|
||||
metabase.query-processor.test-util/format-rows-by [[:default]]
|
||||
metabase.request.core/as-admin [[:block 0]]
|
||||
metabase.request.core/with-current-request [[:inner 0]]
|
||||
metabase.request.core/with-current-user [[:inner 0]]
|
||||
metabase.request.core/with-limit-and-offset [[:inner 0]]
|
||||
metabase.request.session/as-admin [[:block 0]]
|
||||
metabase.request.session/with-current-user [[:inner 0]]
|
||||
metabase.sample-data.impl-test/with-temp-sample-database-db [[:block 1]]
|
||||
metabase.search.api-test/do-test-users [[:block 1]]
|
||||
metabase.server.middleware.session/as-admin [[:block 0]]
|
||||
metabase.server.middleware.session/with-current-user [[:inner 0]]
|
||||
metabase.server.streaming-response-test/with-streaming-response-thread-pool [[:block 0]]
|
||||
metabase.server.streaming-response-test/with-test-driver-db! [[:block 0]]
|
||||
metabase.server.streaming-response/streaming-response [[:block 2]]
|
||||
metabase.session.api/http-401-on-error [[:block 0]]
|
||||
metabase.settings.core/define-multi-setting [[:inner 0]]
|
||||
metabase.settings.core/defsetting [[:block 1]]
|
||||
metabase.settings.models.setting.multi-setting/define-multi-setting [[:inner 0]]
|
||||
metabase.settings.models.setting/defsetting [[:block 1]]
|
||||
metabase.sync.fetch-metadata/log-if-error [[:block 1]]
|
||||
metabase.sync.sync-metadata.fields-test/with-test-db [[:block 0]]
|
||||
metabase.sync.util/do-with-start-and-finish-debug-logging [[:default]]
|
||||
metabase.sync.util/sum-for [[:block 1]]
|
||||
metabase.sync.util/sync-operation [[:block 3]]
|
||||
metabase.sync.util/with-db-logging-disabled [[:default]]
|
||||
metabase.sync.util/with-duplicate-ops-prevented [[:default]]
|
||||
metabase.sync.util/with-emoji-progress-bar [[:block 1]]
|
||||
metabase.sync.util/with-error-handling [[:block 1]]
|
||||
metabase.sync.util/with-start-and-finish-logging [[:default]]
|
||||
metabase.sync.util/with-start-and-finish-logging* [[:default]]
|
||||
metabase.sync.util/with-sync-events [[:default]]
|
||||
metabase.task-history.models.task-history/with-task-history [[:block 1]]
|
||||
metabase.task.core/rerun-on-error [[:block 1]]
|
||||
metabase.task.impl/rerun-on-error [[:block 1]]
|
||||
metabase.test.data.datasets/test-driver [[:inner 0]]
|
||||
metabase.test.data.datasets/test-drivers [[:inner 0]]
|
||||
metabase.test.data.one-off-dbs/with-blank-db [[:block 0]]
|
||||
metabase.test.data.one-off-dbs/with-blueberries-db [[:block 0]]
|
||||
metabase.test.data.users/with-test-user [[:inner 0]]
|
||||
metabase.test.data/$ids [[:inner 0]]
|
||||
metabase.test.data/dataset [[:inner 0]]
|
||||
metabase.test.data/mbql-query [[:inner 0]]
|
||||
metabase.test.data/query [[:block 1]]
|
||||
metabase.test.data/run-mbql-query [[:inner 0]]
|
||||
metabase.test.data/with-empty-h2-app-db! [[:block 0]]
|
||||
metabase.test.data/with-temp-copy-of-db [[:block 0]]
|
||||
metabase.test.util.public-settings/with-additional-premium-features [[:block 1]]
|
||||
metabase.test.util.public-settings/with-premium-features [[:block 1]]
|
||||
metabase.test.util.thread-local/test-helpers-set-global-values! [[:block 0]]
|
||||
metabase.test.util/discard-setting-changes [[:block 1]]
|
||||
metabase.test.util/let-url [[:block 1]]
|
||||
metabase.test.util/throw-if-called! [[:block 1]]
|
||||
metabase.test.util/with-temp-scheduler [[:block 0]]
|
||||
metabase.test.util/with-temp-vals-in-db [[:block 3]]
|
||||
metabase.test.util/with-user-in-groups [[:inner 0]]
|
||||
metabase.test/$ids [[:inner 0]]
|
||||
metabase.test/as-admin [[:inner 0]]
|
||||
metabase.test/dataset [[:inner 0]]
|
||||
metabase.test/discard-setting-changes [[:block 1]]
|
||||
metabase.test/format-rows-by [[:default]]
|
||||
metabase.test/let-url [[:block 1]]
|
||||
metabase.test/mbql-query [[:inner 0]]
|
||||
metabase.test/query [[:block 1]]
|
||||
metabase.test/run-mbql-query [[:inner 0]]
|
||||
metabase.test/test-driver [[:inner 0]]
|
||||
metabase.test/test-drivers [[:inner 0]]
|
||||
metabase.test/test-helpers-set-global-values! [[:block 0]]
|
||||
metabase.test/throw-if-called! [[:block 1]]
|
||||
metabase.test/with-actions [[:block 1]]
|
||||
metabase.test/with-actions-disabled [[:block 0]]
|
||||
metabase.test/with-actions-enabled [[:block 0]]
|
||||
metabase.test/with-actions-test-data [[:inner 0]]
|
||||
metabase.test/with-actions-test-data-and-actions-enabled [[:block 0]]
|
||||
metabase.test/with-actions-test-data-tables [[:block 1]]
|
||||
metabase.test/with-additional-premium-features [[:block 1]]
|
||||
metabase.test/with-current-user [[:inner 0]]
|
||||
metabase.test/with-driver [[:block 1]]
|
||||
metabase.test/with-empty-db [[:inner 0]]
|
||||
metabase.test/with-empty-h2-app-db! [[:block 0]]
|
||||
metabase.test/with-metadata-provider [[:inner 0]]
|
||||
metabase.test/with-premium-features [[:block 1]]
|
||||
metabase.test/with-temp [[:inner 0]]
|
||||
metabase.test/with-temp-copy-of-db [[:block 0]]
|
||||
metabase.test/with-temp-scheduler [[:block 0]]
|
||||
metabase.test/with-temp-test-data [[:inner 0]]
|
||||
metabase.test/with-temp-vals-in-db [[:block 3]]
|
||||
metabase.test/with-test-user [[:inner 0]]
|
||||
metabase.test/with-user-in-groups [[:inner 0]]
|
||||
metabase.timeseries-query-processor-test.util/test-timeseries-drivers [[:block 0]]
|
||||
metabase.timeseries-query-processor-test/run-mbql-query [[:inner 0]]
|
||||
metabase.upload.impl-test/with-upload-table! [[:inner 0]]
|
||||
metabase.upload.types/with-currency [[:default]]
|
||||
metabase.upload.types/with-parens [[:default]]
|
||||
metabase.util/for-map [[:block 1]]
|
||||
metabase.util.async/cancelable-thread [[:block 0]]
|
||||
metabase.util.date-2/with-time-zone-same-instant [[:default]]
|
||||
metabase.util.encryption-test/with-secret-key [[:block 1]]
|
||||
metabase.util.honey-sql-2/with-database-type-info [[:default]]
|
||||
metabase.util.i18n/deferred-trs [[:default]]
|
||||
metabase.util.i18n/deferred-tru [[:default]]
|
||||
metabase.util.i18n/trs [[:default]]
|
||||
metabase.util.i18n/trsn [[:default]]
|
||||
metabase.util.i18n/tru [[:default]]
|
||||
metabase.util.i18n/trun [[:default]]
|
||||
metabase.util.jvm/auto-retry [[:block 1]]
|
||||
metabase.util.jvm/varargs [[:block 1]]
|
||||
metabase.util.jvm/with-us-locale [[:block 0]]
|
||||
metabase.util.malli/disable-enforcement [[:block 0]]
|
||||
metabase.util.malli/with [[:default]]
|
||||
metabase.util.malli/with-api-error-message [[:default]]
|
||||
metabase.util.namespaces/import-fns [[:default]]
|
||||
metabase.util.queue-test/await-test-while [[:block 1]]
|
||||
metabase.util/auto-retry [[:block 1]]
|
||||
metabase.util/case-enum [[:block 1]]
|
||||
metabase.util/ignore-exceptions [[:block 0]]
|
||||
metabase.util/or-with [[:block 1]]
|
||||
metabase.util/profile [[:block 1]]
|
||||
metabase.util/prog1 [[:inner 0]]
|
||||
metabase.util/varargs [[:inner 0]]
|
||||
metabase.util/with-us-locale [[:block 0]]
|
||||
metabuild-common.core/exit-when-finished-nonzero-on-exception [[:block 0]]
|
||||
metabuild-common.core/step [[:block 1]]
|
||||
metabuild-common.entrypoint/exit-when-finished-nonzero-on-exception [[:block 0]]
|
||||
metabuild-common.steps/step [[:block 1]]
|
||||
methodical.core/defmethod [[:inner 0]]
|
||||
methodical.core/defmulti [[:inner 0]]
|
||||
methodical.core/with-dispatcher [[:default]]
|
||||
methodical.core/with-method-table [[:default]]
|
||||
methodical.core/with-prefers [[:default]]
|
||||
methodical.interface/with-dispatcher [[:default]]
|
||||
methodical.interface/with-method-table [[:default]]
|
||||
methodical.interface/with-prefers [[:default]]
|
||||
methodical.macros/defmethod [[:inner 0]]
|
||||
methodical.macros/defmulti [[:inner 0]]
|
||||
saml20-clj.sp.response/validate-confirmation-datas [[:block 1]]
|
||||
toucan2.connection/with-transaction [[:block 1]]
|
||||
toucan2.core/build [[:block 0]]
|
||||
toucan2.core/compile [[:block 0]]
|
||||
toucan2.core/debug [[:block 0]]
|
||||
toucan2.core/define-after-insert [[:inner 0]]
|
||||
toucan2.core/define-after-select [[:inner 0]]
|
||||
toucan2.core/define-after-update [[:inner 0]]
|
||||
toucan2.core/define-before-delete [[:inner 0]]
|
||||
toucan2.core/define-before-insert [[:inner 0]]
|
||||
toucan2.core/define-before-select [[:inner 0]]
|
||||
toucan2.core/define-default-fields [[:inner 0]]
|
||||
toucan2.core/define-named-query [[:block 1]]
|
||||
toucan2.core/deftransforms [[:block 1]]
|
||||
toucan2.core/with-transaction [[:block 1]]
|
||||
toucan2.tools.after-insert/define-after-insert [[:inner 0]]
|
||||
toucan2.tools.after-select/define-after-select [[:inner 0]]
|
||||
toucan2.tools.after-update/define-after-update [[:inner 0]]
|
||||
toucan2.tools.after/define-after-impl [[:default]]
|
||||
toucan2.tools.before-delete/define-before-delete [[:inner 0]]
|
||||
toucan2.tools.before-insert/define-before-insert [[:inner 0]]
|
||||
toucan2.tools.before-select/define-before-select [[:inner 0]]
|
||||
toucan2.tools.compile/build [[:block 0]]
|
||||
toucan2.tools.compile/compile [[:block 0]]
|
||||
toucan2.tools.compile/resolved [[:block 0]]
|
||||
toucan2.tools.debug/debug [[:block 0]]
|
||||
toucan2.tools.default-fields/define-default-fields [[:inner 0]]
|
||||
toucan2.tools.named-query/define-named-query [[:block 1]]
|
||||
toucan2.tools.simple-out-transform/define-out-transform [[:inner 0]]
|
||||
toucan2.tools.transformed/deftransforms [[:block 1]]
|
||||
toucan2.tools.with-temp/with-temp [[:inner 0]]
|
||||
toucan2.util/try-with-error-context [[:inner 0]]}}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
|
||||
reviews:
|
||||
high_level_summary: false
|
||||
review_status: false
|
||||
|
||||
auto_review:
|
||||
enabled: false
|
||||
drafts: true
|
||||
labels:
|
||||
- "coderabbit"
|
||||
|
||||
path_filters:
|
||||
- "!**/*.snap"
|
||||
- "!**/*.po"
|
||||
- "!**/yarn.lock"
|
||||
- "!**/package-lock.json"
|
||||
|
||||
tools:
|
||||
eslint:
|
||||
enabled: false
|
||||
|
||||
knowledge_base:
|
||||
code_guidelines:
|
||||
filePatterns:
|
||||
- ".claude/skills/clojure-review/SKILL.md"
|
||||
- ".claude/skills/typescript-review/SKILL.md"
|
||||
- ".claude/skills/docs-review/SKILL.md"
|
||||
@@ -1,35 +0,0 @@
|
||||
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
|
||||
24
.codespellrc
24
.codespellrc
@@ -1,24 +0,0 @@
|
||||
[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,
|
||||
ignore-words = .codespellignore
|
||||
@@ -4,8 +4,8 @@ FROM mcr.microsoft.com/vscode/devcontainers/java:21
|
||||
|
||||
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 --global yarn
|
||||
&& apt-key adv --refresh-keys --keyserver keyserver.ubuntu.com \
|
||||
&& 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
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
.babel_cache
|
||||
docs/*
|
||||
target/*
|
||||
.circleci
|
||||
.cpcache
|
||||
|
||||
160
.env.example
160
.env.example
@@ -1,160 +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 `yarn 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
|
||||
|
||||
# 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
|
||||
@@ -2,4 +2,3 @@ frontend/src/cljs
|
||||
frontend/src/cljs_release
|
||||
e2e/support/cypress_sample_database.js
|
||||
e2e/support/cypress_sample_instance_data.js
|
||||
e2e/embedding-sdk-host-apps
|
||||
|
||||
17
.eslintrc.js
17
.eslintrc.js
@@ -94,7 +94,10 @@ module.exports = {
|
||||
"react/jsx-no-target-blank": 2,
|
||||
"react/jsx-key": 2,
|
||||
"react/forbid-component-props": [2, { forbid: ["sx"] }],
|
||||
"react-hooks/exhaustive-deps": ["warn"],
|
||||
"react-hooks/exhaustive-deps": [
|
||||
"warn",
|
||||
{ additionalHooks: "(useSafeAsyncFunction)" },
|
||||
],
|
||||
"prefer-const": [1, { destructuring: "all" }],
|
||||
"no-restricted-globals": ["error", "close"],
|
||||
"no-useless-escape": 0,
|
||||
@@ -115,6 +118,7 @@ module.exports = {
|
||||
"And",
|
||||
"When",
|
||||
"Then",
|
||||
"describeWithSnowplow",
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -132,7 +136,7 @@ module.exports = {
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
es2020: true,
|
||||
es6: true,
|
||||
commonjs: true,
|
||||
jest: true,
|
||||
"jest/globals": true,
|
||||
@@ -180,7 +184,6 @@ module.exports = {
|
||||
"no-unconditional-metabase-links-render": "error",
|
||||
"no-color-literals": "error",
|
||||
"no-literal-metabase-strings": "error",
|
||||
"no-oss-reinitialize-import": "error",
|
||||
"depend/ban-dependencies": [
|
||||
"error",
|
||||
{
|
||||
@@ -200,7 +203,6 @@ module.exports = {
|
||||
"frontend/src/metabase/admin/**/*",
|
||||
"frontend/src/metabase/setup/**/*",
|
||||
"enterprise/frontend/src/metabase-enterprise/whitelabel/**/*",
|
||||
"enterprise/frontend/src/metabase-enterprise/embedding/**/*",
|
||||
"frontend/lint/**/*",
|
||||
"*.stories.*",
|
||||
"**/.storybook/*",
|
||||
@@ -241,7 +243,6 @@ module.exports = {
|
||||
plugins: ["@typescript-eslint"],
|
||||
rules: {
|
||||
"prefer-rest-params": "off",
|
||||
"react/prop-types": "off", // TypeScript handles prop validation
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"@typescript-eslint/no-inferrable-types": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
@@ -346,11 +347,5 @@ module.exports = {
|
||||
"import/no-commonjs": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["**/*.stories.tsx", "**/preview.tsx"],
|
||||
rules: {
|
||||
"import/no-default-export": "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,7 +0,0 @@
|
||||
# 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
|
||||
# [merge "yaml-migrations"]
|
||||
# name = YAML migration changeset-aware merge
|
||||
# driver = mage -merge-yaml-migrations %O %A %B %L %P
|
||||
@@ -17,6 +17,8 @@ runs:
|
||||
|
||||
- name: Prepare backend environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: cross-branch-migrations
|
||||
|
||||
- name: Run backend on base branch
|
||||
run: |
|
||||
|
||||
25
.github/actions/fetch-artifact/action.yml
vendored
25
.github/actions/fetch-artifact/action.yml
vendored
@@ -38,25 +38,12 @@ runs:
|
||||
|
||||
const artifact_id = artifacts.data.artifacts[0].id;
|
||||
|
||||
let download;
|
||||
const maxAttempts = 3;
|
||||
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
||||
try {
|
||||
download = await github.rest.actions.downloadArtifact({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
artifact_id: artifact_id,
|
||||
archive_format: 'zip',
|
||||
});
|
||||
break;
|
||||
} catch (err) {
|
||||
console.log(`Attempt ${attempt}/${maxAttempts} failed: ${err.message}`);
|
||||
if (attempt === maxAttempts) throw err;
|
||||
const delay = attempt * 15000; // 15s, 30s
|
||||
console.log(`Retrying in ${delay / 1000}s...`);
|
||||
await new Promise(r => setTimeout(r, delay));
|
||||
}
|
||||
}
|
||||
const download = await github.rest.actions.downloadArtifact({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
artifact_id: artifact_id,
|
||||
archive_format: 'zip',
|
||||
});
|
||||
|
||||
fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/mb.zip`, Buffer.from(download.data));
|
||||
|
||||
|
||||
37
.github/actions/get-cache-keys/action.yml
vendored
37
.github/actions/get-cache-keys/action.yml
vendored
@@ -1,37 +0,0 @@
|
||||
name: Get cache keys
|
||||
description: |
|
||||
Get deterministic cache keys for Maven and Node.js caches based on the centrally controlled cache prefix,
|
||||
and the suffix based on the runner OS and the current week of the year.
|
||||
|
||||
outputs:
|
||||
m2-cache-key:
|
||||
description: "The cache key for the Maven cache."
|
||||
value: ${{ steps.keys-factory.outputs.m2-cache-key }}
|
||||
node-cache-key:
|
||||
description: "The cache key for the Node.js cache."
|
||||
value: ${{ steps.keys-factory.outputs.node-cache-key }}
|
||||
eslint-cache-key:
|
||||
description: "The cache key for Eslint cache"
|
||||
value: ${{ steps.keys-factory.outputs.eslint-cache-key }}
|
||||
cypress-cache-key:
|
||||
description: "The cache key for Cypress cache"
|
||||
value: ${{ steps.keys-factory.outputs.cypress-cache-key }}
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Generate and output cache keys
|
||||
id: keys-factory
|
||||
run: |
|
||||
M2_CACHE_PREFIX='M2'
|
||||
NODE_CACHE_PREFIX='node-modules'
|
||||
ESLINT_CACHE_PREFIX='eslint'
|
||||
CYPRESS_CACHE_PREFIX='cypress'
|
||||
WEEK_OF_YEAR=$(/bin/date "+%G-%V")
|
||||
CACHE_SUFFIX="${{ runner.os }}-$WEEK_OF_YEAR"
|
||||
|
||||
echo "m2-cache-key=$M2_CACHE_PREFIX-$CACHE_SUFFIX" >> $GITHUB_OUTPUT
|
||||
echo "node-cache-key=$NODE_CACHE_PREFIX-$CACHE_SUFFIX" >> $GITHUB_OUTPUT
|
||||
echo "eslint-cache-key=$ESLINT_CACHE_PREFIX-$CACHE_SUFFIX" >> $GITHUB_OUTPUT
|
||||
echo "cypress-cache-key=$CYPRESS_CACHE_PREFIX-$CACHE_SUFFIX" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
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
|
||||
30
.github/actions/prepare-backend/action.yml
vendored
30
.github/actions/prepare-backend/action.yml
vendored
@@ -1,7 +1,7 @@
|
||||
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.
|
||||
Prepare the backend. Installs jdk, Clojure, and fetches the cache for m2. The CI config fetchs `ci-test-config.json`
|
||||
from master and lets us ignore tests at the var level if they are flaking.
|
||||
inputs:
|
||||
java-version:
|
||||
required: true
|
||||
@@ -9,6 +9,10 @@ inputs:
|
||||
clojure-version:
|
||||
required: true
|
||||
default: '1.12.0.1488'
|
||||
m2-cache-key:
|
||||
description: 'Key to cache M2 packages from Maven Central'
|
||||
required: true
|
||||
default: 'm2'
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
@@ -24,15 +28,21 @@ runs:
|
||||
run: |
|
||||
curl -O https://download.clojure.org/install/linux-install-${{ inputs.clojure-version }}.sh &&
|
||||
sudo bash ./linux-install-${{ inputs.clojure-version }}.sh
|
||||
|
||||
- name: Get M2 cache key
|
||||
uses: ./.github/actions/get-cache-keys
|
||||
id: get-cache-keys
|
||||
|
||||
- name: Restore M2 cache
|
||||
uses: actions/cache/restore@v4
|
||||
- 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: Check to see if dependencies should be cached
|
||||
if: ${{ contains(github.event.head_commit.message, '[ci nocache]') }}
|
||||
run: echo "Commit message includes [ci nocache]; dependencies will NOT be cached"
|
||||
shell: bash
|
||||
- name: Get M2 cache
|
||||
uses: actions/cache@v4
|
||||
if: ${{ !contains(github.event.head_commit.message, '[ci nocache]') }}
|
||||
with:
|
||||
path: |
|
||||
~/.m2
|
||||
~/.gitlibs
|
||||
key: ${{ steps.get-cache-keys.outputs.m2-cache-key }}
|
||||
key: ${{ runner.os }}-${{ inputs.m2-cache-key }}-${{ hashFiles('**/deps.edn') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ inputs.m2-cache-key }}-
|
||||
|
||||
58
.github/actions/prepare-ci-conductor/action.yml
vendored
58
.github/actions/prepare-ci-conductor/action.yml
vendored
@@ -1,58 +0,0 @@
|
||||
name: Prepare CI Conductor
|
||||
description: >-
|
||||
Make the ci-conductor sources runnable in the workspace.
|
||||
Unless the checkout is headed for master, they are overlaid from master, so every
|
||||
branch runs the latest scripts while its workflow wiring stays branch-historical.
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Detect bun
|
||||
id: bun
|
||||
shell: bash
|
||||
run: |
|
||||
if command -v bun >/dev/null; then
|
||||
echo "present=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "present=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Install bun
|
||||
if: steps.bun.outputs.present != 'true'
|
||||
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
||||
with:
|
||||
bun-version: "1.3.14"
|
||||
token: ${{ github.token }}
|
||||
|
||||
# Anything headed for master runs master's own sources, so a PR can test its own changes.
|
||||
- name: Fetch ci-conductor sources from master
|
||||
if: ${{ github.base_ref != 'master' && github.ref_name != 'master' }}
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: metabase/metabase
|
||||
ref: master
|
||||
path: .ci-conductor-master
|
||||
persist-credentials: false
|
||||
sparse-checkout-cone-mode: false
|
||||
sparse-checkout: |
|
||||
release/ci-conductor
|
||||
e2e/support/ci_conductor.ts
|
||||
|
||||
# e2e/support/config.js and e2e/support/ci_conductor.ts resolve these by relative
|
||||
# path at bundle time, so the sources have to land where the branch expects them.
|
||||
- name: Overlay ci-conductor sources from master
|
||||
if: ${{ github.base_ref != 'master' && github.ref_name != 'master' }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
rm -rf release/ci-conductor
|
||||
mv .ci-conductor-master/release/ci-conductor release/ci-conductor
|
||||
mv .ci-conductor-master/e2e/support/ci_conductor.ts e2e/support/ci_conductor.ts
|
||||
rm -rf .ci-conductor-master
|
||||
# Cypress 14 (v58 pins ^14.5.3) loads its config through ts-node in CommonJS mode.
|
||||
# The overlaid ci-conductor package is ESM ("type": "module"), so ts-node's require()
|
||||
# of util.ts throws ERR_REQUIRE_ESM. Drop the field on the overlaid copy so ts-node
|
||||
# transpiles it as CommonJS. bun runs the quarantine scripts and treats .ts as ESM by
|
||||
# extension regardless, so it's unaffected. Remove once v58 moves off Cypress 14.
|
||||
node -e 'const fs=require("fs"),f="release/ci-conductor/package.json",p=JSON.parse(fs.readFileSync(f,"utf8"));delete p.type;fs.writeFileSync(f,JSON.stringify(p,null,2)+"\n")'
|
||||
echo "Overlaid ci-conductor sources from master."
|
||||
18
.github/actions/prepare-cypress/action.yml
vendored
18
.github/actions/prepare-cypress/action.yml
vendored
@@ -15,10 +15,6 @@ outputs:
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
# e2e/support/config.js imports the reporter, so it has to be in place before Cypress bundles.
|
||||
- name: Prepare CI Conductor
|
||||
uses: ./.github/actions/prepare-ci-conductor
|
||||
|
||||
- name: Install Chrome v111
|
||||
uses: browser-actions/setup-chrome@v1
|
||||
with:
|
||||
@@ -26,16 +22,16 @@ runs:
|
||||
chrome-version: 1097615
|
||||
id: setup-chrome
|
||||
|
||||
- name: Get cache keys
|
||||
uses: ./.github/actions/get-cache-keys
|
||||
id: get-cache-keys
|
||||
|
||||
- name: Check to see if dependencies should be cached
|
||||
if: ${{ contains(github.event.head_commit.message, '[ci nocache]') }}
|
||||
run: echo "Commit message includes [ci nocache]; dependencies will NOT be cached"
|
||||
shell: bash
|
||||
- name: Get Cypress cache
|
||||
uses: actions/cache/restore@v4
|
||||
uses: actions/cache@v4
|
||||
if: ${{ !contains(github.event.head_commit.message, '[ci nocache]') }}
|
||||
with:
|
||||
path: ~/.cache/Cypress
|
||||
key: ${{ steps.get-cache-keys.outputs.cypress-cache-key }}
|
||||
|
||||
key: ${{ runner.os }}-Cypress-${{ hashFiles('**/yarn.lock') }}
|
||||
- name: Ensure that Cypress executable is ready
|
||||
run: |
|
||||
yarn cypress install
|
||||
|
||||
56
.github/actions/prepare-frontend/action.yml
vendored
56
.github/actions/prepare-frontend/action.yml
vendored
@@ -3,10 +3,6 @@ inputs:
|
||||
node-version:
|
||||
description: "The version of Node.js to use, overriding .nvmrc"
|
||||
required: false
|
||||
apply-patches:
|
||||
description: "Whether to patch frontend dependencies with patch-package."
|
||||
required: true
|
||||
default: "true"
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
@@ -16,42 +12,26 @@ runs:
|
||||
node-version: ${{ inputs.node-version }} # this overrides, if present
|
||||
node-version-file: ".nvmrc"
|
||||
|
||||
- name: Get cache keys
|
||||
uses: ./.github/actions/get-cache-keys
|
||||
id: get-cache-keys
|
||||
|
||||
- name: Restore M2 cache
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
~/.m2
|
||||
~/.gitlibs
|
||||
key: ${{ steps.get-cache-keys.outputs.m2-cache-key }}
|
||||
|
||||
- name: Get yarn cache directory
|
||||
id: yarn-cache-dir
|
||||
- name: Check to see if dependencies should be cached
|
||||
if: ${{ contains(github.event.head_commit.message, '[ci nocache]') }}
|
||||
run: |
|
||||
YARN_CACHE_DIR=$(yarn cache dir)
|
||||
echo "Yarn cache directory: $YARN_CACHE_DIR"
|
||||
echo "dir=$YARN_CACHE_DIR" >> $GITHUB_OUTPUT
|
||||
echo "Commit message includes [ci nocache]; dependencies will NOT be cached"
|
||||
yarn cache clean
|
||||
shell: bash
|
||||
|
||||
- name: Restore node_modules cache
|
||||
uses: actions/cache/restore@v4
|
||||
id: node-modules-cache
|
||||
- name: Get M2 cache
|
||||
uses: actions/cache@v4
|
||||
if: ${{ !contains(github.event.head_commit.message, '[ci nocache]') }}
|
||||
with:
|
||||
path: |
|
||||
${{ steps.yarn-cache-dir.outputs.dir }}
|
||||
node_modules
|
||||
key: ${{ steps.get-cache-keys.outputs.node-cache-key }}
|
||||
path: ~/.m2
|
||||
key: ${{ runner.os }}-cljs-${{ hashFiles('**/shadow-cljs.edn') }}
|
||||
|
||||
# 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: yarn install --frozen-lockfile --prefer-offline
|
||||
shell: bash
|
||||
- name: Patch frontend dependencies
|
||||
if: inputs.apply-patches == 'true'
|
||||
run: yarn patch-package
|
||||
- name: Get node_modules cache
|
||||
uses: actions/cache@v4
|
||||
id: node-modules-cache
|
||||
if: ${{ !contains(github.event.head_commit.message, '[ci nocache]') }}
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-node-modules-${{ hashFiles('**/yarn.lock', '**/patches/*.patch') }}
|
||||
|
||||
- run: yarn install --frozen-lockfile --prefer-offline
|
||||
shell: bash
|
||||
|
||||
38
.github/actions/quarantine-gate/action.yml
vendored
38
.github/actions/quarantine-gate/action.yml
vendored
@@ -1,38 +0,0 @@
|
||||
name: CI Conductor quarantine gate
|
||||
description: >-
|
||||
Check this job's test failures against ci-conductor's quarantine list.
|
||||
Either enforces the quarantine or only prints a verdict (dry run).
|
||||
Fails closed when enforcing but the `CI_CONDUCTOR_BASE_URL` is missing or unreachable.
|
||||
|
||||
inputs:
|
||||
adapter:
|
||||
description: Which gate to run — one of `backend`, `frontend`, `e2e`.
|
||||
required: true
|
||||
test-suite:
|
||||
description: >-
|
||||
Suite label to gate (CI_CONDUCTOR_TEST_SUITE). MUST match the label this
|
||||
job reports under, or the gate checks the wrong quarantine list.
|
||||
required: true
|
||||
enforce-quarantine:
|
||||
description: >-
|
||||
Whether the gate actually gates the job. When "false", it only prints a verdict.
|
||||
required: false
|
||||
default: "false"
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Prepare CI Conductor
|
||||
uses: ./.github/actions/prepare-ci-conductor
|
||||
- name: Check failures against the quarantine list
|
||||
shell: bash
|
||||
env:
|
||||
CI_CONDUCTOR_TEST_SUITE: ${{ inputs.test-suite }}
|
||||
ADAPTER: ${{ inputs.adapter }}
|
||||
QUARANTINE_DRY_RUN: ${{ inputs.enforce-quarantine != 'true' }}
|
||||
run: |
|
||||
case "$ADAPTER" in
|
||||
backend|frontend|e2e) ;;
|
||||
*) echo "::error::quarantine-gate: unknown adapter '$ADAPTER' (expected backend|frontend|e2e)"; exit 1 ;;
|
||||
esac
|
||||
bun "release/ci-conductor/src/check-${ADAPTER}-quarantine.ts"
|
||||
@@ -1,34 +0,0 @@
|
||||
name: Report failures to CI Conductor
|
||||
description: >-
|
||||
Report this job's test failures to ci-conductor. Best-effort; no-ops unless
|
||||
CI_CONDUCTOR_BASE_URL is set. Gate it at the call site with continue-on-error
|
||||
and an `if:` on the test step's outcome.
|
||||
|
||||
inputs:
|
||||
test-suite:
|
||||
description: Per-job identity label (CI_CONDUCTOR_TEST_SUITE); unique per job/leg.
|
||||
required: true
|
||||
adapter:
|
||||
description: Which reporter to run; resolves to report-<adapter>.ts. backend or frontend.
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Prepare CI Conductor
|
||||
uses: ./.github/actions/prepare-ci-conductor
|
||||
- name: Report failed tests to ci-conductor
|
||||
shell: bash
|
||||
env:
|
||||
REPO_ID: ${{ github.event.repository.id }}
|
||||
COMMIT_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
TARGET_BRANCH: ${{ github.event.pull_request.base.ref || github.ref_name }}
|
||||
CI_CONDUCTOR_TEST_SUITE: ${{ inputs.test-suite }}
|
||||
ADAPTER: ${{ inputs.adapter }}
|
||||
# Keep the path constrained to known entrypoints.
|
||||
run: |
|
||||
case "$ADAPTER" in
|
||||
backend|frontend) ;;
|
||||
*) echo "::error::Unknown ci-conductor adapter '$ADAPTER'"; exit 1 ;;
|
||||
esac
|
||||
bun "release/ci-conductor/src/report-${ADAPTER}.ts"
|
||||
66
.github/actions/resolve-job-id/action.yml
vendored
66
.github/actions/resolve-job-id/action.yml
vendored
@@ -1,66 +0,0 @@
|
||||
name: Resolve current job id
|
||||
description: >
|
||||
Resolve this job's GitHub workflow_jobs.id via the Actions API and export it
|
||||
as JOB_ID (and JOB_HTML_URL) to $GITHUB_ENV, so any later step can read the
|
||||
numeric job id without making its own API call. Best-effort: warns and
|
||||
proceeds on failure so it can never break the run.
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Resolve JOB_ID
|
||||
shell: bash
|
||||
env:
|
||||
# Use the built-in per-job token, not a PAT: this lookup is best-effort
|
||||
# (a miss just leaves JOB_ID unset, never breaks the run) and other jobs
|
||||
# depend on the automation PAT's rate-limit budget — we don't want ~70
|
||||
# e2e shards drawing it down. Worst case under heavy CI load is the
|
||||
# occasional unresolved job_id, which is acceptable. DEV-2117.
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
RUNNER_NAME: ${{ runner.name }}
|
||||
run: |
|
||||
# GitHub doesn't expose the current job's numeric id, so we look it up
|
||||
# from the Actions API. A runner runs exactly one job at a time, so the
|
||||
# in-progress job with this runner_name within (run_id, run_attempt) is
|
||||
# unique. We require status == in_progress and no completed_at so a
|
||||
# reused runner can't match a job it already finished this attempt, with
|
||||
# max_by(.started_at) as a final tiebreaker.
|
||||
#
|
||||
# $GITHUB_RUN_ID is the whole CI run, whose total job count exceeds the
|
||||
# 100-per-page cap (~185 jobs), so this shard's job is frequently NOT on
|
||||
# the first page. --paginate --slurp pulls every page and slurps them
|
||||
# into one array so the filter sees all jobs, not just the first 100 —
|
||||
# without it the lookup returned null whenever this job sorted past
|
||||
# position 100 (DEV-2117).
|
||||
#
|
||||
# gh rejects `--slurp` together with `--jq` ("the --slurp option is not
|
||||
# supported with --jq"), so we slurp with gh and run the filter through a
|
||||
# standalone `jq`. The earlier `--slurp --jq` form errored on every
|
||||
# attempt — swallowed by 2>/dev/null — leaving JOB_ID permanently unset.
|
||||
set +e
|
||||
api_url="/repos/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID/attempts/$GITHUB_RUN_ATTEMPT/jobs?per_page=100"
|
||||
jq_filter="[.[].jobs[] | select(.runner_name == \"$RUNNER_NAME\" and .status == \"in_progress\" and .completed_at == null)] | max_by(.started_at) | \"\(.id) \(.html_url)\""
|
||||
|
||||
# The Actions jobs API is eventually consistent: after a runner picks
|
||||
# up a job, it can take a few seconds for runner_name to appear in the
|
||||
# response. Retry briefly to absorb that.
|
||||
out=""
|
||||
for attempt in 1 2 3 4 5; do
|
||||
out=$(gh api --paginate --slurp "$api_url" 2>/dev/null | jq -r "$jq_filter" 2>/dev/null)
|
||||
if [ -n "$out" ] && [ "$out" != "null null" ]; then
|
||||
break
|
||||
fi
|
||||
out=""
|
||||
echo "Attempt $attempt: job not yet visible in Actions API; retrying..."
|
||||
sleep 3
|
||||
done
|
||||
|
||||
if [ -n "$out" ]; then
|
||||
read -r job_id job_url <<<"$out"
|
||||
echo "JOB_ID=$job_id" >> "$GITHUB_ENV"
|
||||
echo "JOB_HTML_URL=$job_url" >> "$GITHUB_ENV"
|
||||
echo "Resolved JOB_ID=$job_id (runner=$RUNNER_NAME)"
|
||||
else
|
||||
echo "::warning::Could not resolve current job id after 5 attempts (runner=$RUNNER_NAME)"
|
||||
fi
|
||||
exit 0
|
||||
33
.github/actions/run-embedding-sdk-storybook/action.yml
vendored
Normal file
33
.github/actions/run-embedding-sdk-storybook/action.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: Run Embedding SDK Storybook
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Prepare frontend environment
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: "cljs"
|
||||
|
||||
- name: Compile CLJS
|
||||
run: yarn build-pure:cljs
|
||||
shell: bash
|
||||
|
||||
- name: Build Storybook
|
||||
run: yarn build-storybook-embedding-sdk
|
||||
shell: bash
|
||||
|
||||
- name: Serve Storybook
|
||||
run: yarn http-server storybook-static -p 6006 &
|
||||
shell: bash
|
||||
|
||||
- name: Wait for Storybook to become available
|
||||
run: |
|
||||
echo "Waiting for Storybook to be served..."
|
||||
while ! curl --silent --fail http://localhost:6006; do
|
||||
sleep 5
|
||||
echo "Retrying..."
|
||||
done
|
||||
echo "Storybook is being served!"
|
||||
shell: bash
|
||||
17
.github/actions/setup-python-runner/action.yml
vendored
17
.github/actions/setup-python-runner/action.yml
vendored
@@ -4,11 +4,24 @@ inputs:
|
||||
metabase-automation-token:
|
||||
description: Token for accessing the python-runner repository
|
||||
required: true
|
||||
python-runner-version:
|
||||
description: Git tag or commit of the python-runner repository to checkout
|
||||
required: false
|
||||
default: 'v0.0.1'
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Setup python-runner
|
||||
run: ./.github/scripts/setup-python-runner.sh
|
||||
- name: Clone and setup python-runner
|
||||
run: |
|
||||
echo "Cloning python-runner repository..."
|
||||
git clone --depth 1 --branch ${{ inputs.python-runner-version }} \
|
||||
https://${{ inputs.metabase-automation-token }}@github.com/metabase/python-runner-container.git python-runner
|
||||
|
||||
echo "Starting python-runner..."
|
||||
cd python-runner
|
||||
make run-ci 1> ../python-runner.out.log 2> ../python-runner.err.log
|
||||
make logs 1>> ../python-runner.out.log 2>> ../python-runner.err.log &
|
||||
cd ..
|
||||
shell: bash
|
||||
env:
|
||||
METABASE_AUTOMATION_USER_TOKEN: ${{ inputs.metabase-automation-token }}
|
||||
|
||||
94
.github/actions/test-driver/action.yml
vendored
94
.github/actions/test-driver/action.yml
vendored
@@ -9,93 +9,29 @@ 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)
|
||||
quarantine-engine:
|
||||
required: false
|
||||
|
||||
outputs:
|
||||
quarantine-skipped:
|
||||
description: Whether the driver test was skipped due to quarantine
|
||||
value: ${{ steps.check-driver.outputs.skip }}
|
||||
test-outcome:
|
||||
description: "The outcome of the test run (success, failure, cancelled, skipped)"
|
||||
value: ${{ steps.run-test.outcome }}
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Check if driver is ignored
|
||||
id: check-driver
|
||||
shell: bash
|
||||
run: |
|
||||
# If DRIVERS env var is not set, proceed with tests
|
||||
if [ -z "$DRIVERS" ]; then
|
||||
echo "No DRIVERS env var set. Proceeding with tests."
|
||||
echo "skip=false" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Fetch CI config and check if driver should be skipped
|
||||
curl -sSf -o ci-test-config.json https://raw.githubusercontent.com/metabase/ci-test-config/refs/heads/master/ci-test-config.json || true
|
||||
|
||||
# Check if the driver is in the ignored list (gracefully handle missing/invalid config)
|
||||
IS_IGNORED=$(jq --arg driver "$DRIVERS" '.ignored.drivers // [] | contains([$driver])' ci-test-config.json 2>/dev/null || echo "false")
|
||||
|
||||
if [ "$IS_IGNORED" = "true" ]; then
|
||||
echo "⚠️ Driver '$DRIVERS' is in the ignored list. Skipping tests."
|
||||
echo "View the ignored list here: https://github.com/metabase/ci-test-config/blob/master/ci-test-config.json"
|
||||
echo "skip=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "Driver '$DRIVERS' is not ignored. Proceeding with tests."
|
||||
echo "skip=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Prepare back-end environment
|
||||
if: steps.check-driver.outputs.skip != 'true'
|
||||
uses: ./.github/actions/prepare-backend
|
||||
- name: Resolve current job id
|
||||
# Resolve JOB_ID early (before the long test step) so the ci-conductor
|
||||
# reporter below can link failures to this exact job. Best-effort.
|
||||
uses: ./.github/actions/resolve-job-id
|
||||
with:
|
||||
m2-cache-key: driver
|
||||
- name: Prepare front-end environment
|
||||
if: ${{ steps.check-driver.outputs.skip != 'true' && inputs.build-static-viz == 'true' }}
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
if: ${{ inputs.build-static-viz == 'true' }}
|
||||
- name: Build static viz frontend
|
||||
if: ${{ steps.check-driver.outputs.skip != 'true' && inputs.build-static-viz == 'true' }}
|
||||
run: yarn build-static-viz
|
||||
shell: bash
|
||||
if: ${{ inputs.build-static-viz == 'true' }}
|
||||
env:
|
||||
MB_EDITION: ee
|
||||
|
||||
- name: Test database driver
|
||||
if: steps.check-driver.outputs.skip != 'true'
|
||||
run: clojure -X:dev:ci:${{ 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
|
||||
# Keep this step continue-on-error because the quarantine logic controls whether it ultimately fails.
|
||||
continue-on-error: true
|
||||
|
||||
- name: Report failed tests to ci-conductor
|
||||
if: ${{ !cancelled() && steps.run-test.outcome == 'failure' }}
|
||||
# This step should NEVER fail the job!
|
||||
continue-on-error: true
|
||||
uses: ./.github/actions/report-failures-to-ci-conductor
|
||||
with:
|
||||
test-suite: ${{ inputs.junit-name }}
|
||||
adapter: backend
|
||||
|
||||
- name: Publish Test Report (JUnit)
|
||||
uses: dorny/test-reporter@v1
|
||||
# As per the documentation: https://docs.github.com/en/actions/reference/workflows-and-actions/contexts#steps-context
|
||||
# test.outcome gives us access to the result before `continue-on-error` overrides it and sets it to 'success'.
|
||||
# We want test reports even if the test step gets cancelled (e.g. times out), hence we match anything that is not a 'success'.
|
||||
if: ${{ steps.run-test.outcome != 'success' && steps.check-driver.outputs.skip != 'true' }}
|
||||
if: failure()
|
||||
with:
|
||||
path: "target/junit/**/*_test.xml"
|
||||
name: JUnit Test Report ${{ inputs.junit-name }}
|
||||
@@ -103,24 +39,10 @@ runs:
|
||||
list-suites: failed
|
||||
list-tests: failed
|
||||
fail-on-error: false
|
||||
|
||||
- name: Upload Logs on Failure
|
||||
if: always() && steps.run-test.outcome != 'success' && steps.check-driver.outputs.skip != 'true'
|
||||
if: always() && !success()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ format('test-logs-{0}{1}', github.job, (inputs.logs-artifact-suffix == '' || inputs.logs-artifact-suffix == null) && '' || 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.
|
||||
continue-on-error: true
|
||||
|
||||
# Last, because this is the only step allowed to fail the job: everything above it reports the
|
||||
# failure (JUnit report, logs, ci-conductor) and must run regardless of the verdict below.
|
||||
- name: Quarantine gate
|
||||
if: ${{ !cancelled() && steps.run-test.outcome == 'failure' }}
|
||||
continue-on-error: ${{ inputs.quarantine-engine != 'ci-conductor' }}
|
||||
uses: ./.github/actions/quarantine-gate
|
||||
with:
|
||||
adapter: backend
|
||||
test-suite: ${{ inputs.junit-name }}
|
||||
enforce-quarantine: ${{ inputs.quarantine-engine == 'ci-conductor' }}
|
||||
|
||||
41
.github/actions/update-cache/action.yml
vendored
41
.github/actions/update-cache/action.yml
vendored
@@ -1,41 +0,0 @@
|
||||
name: Update cache
|
||||
description: |
|
||||
Update a GitHub Actions cache based on a provided path and key.
|
||||
If a cache entry already exists for the given key, it is first removed.
|
||||
This ensures immutability of cache entries, and prevents runtime errors.
|
||||
|
||||
inputs:
|
||||
path:
|
||||
description: "The path(s) to cache, separated by newlines."
|
||||
required: true
|
||||
key:
|
||||
description: "The cache key to use."
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Check if the cache entry exists
|
||||
id: cache-lookup
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: ${{ inputs.path }}
|
||||
key: ${{ inputs.key }}
|
||||
lookup-only: true # Important bit: the action will not fetch anything, just check for the cache existence.
|
||||
- name: Delete the cache entry if it exists
|
||||
if: ${{ steps.cache-lookup.outputs.cache-hit == 'true' }}
|
||||
run: |
|
||||
echo "Cache entry found for key '${{ inputs.key }}'. Deleting it to ensure immutability."
|
||||
gh api \
|
||||
--method DELETE \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
/repos/${{ github.repository }}/actions/caches?key=${{ inputs.key }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
shell: bash
|
||||
- name: Save the new cache entry
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: ${{ inputs.path }}
|
||||
key: ${{ inputs.key }}
|
||||
28
.github/actions/upload-test-results/action.yml
vendored
28
.github/actions/upload-test-results/action.yml
vendored
@@ -20,32 +20,18 @@ inputs:
|
||||
required: true
|
||||
variant:
|
||||
required: false
|
||||
default: ""
|
||||
previous-step-outcome:
|
||||
required: true
|
||||
quarantine-skipped:
|
||||
required: false
|
||||
default: ""
|
||||
description: >-
|
||||
Set to 'true' when the driver was skipped via the ci-test-config ignored list,
|
||||
so this action becomes a no-op (no JUnit XMLs exist to upload).
|
||||
run-trunk:
|
||||
required: false
|
||||
default: "true"
|
||||
description: Whether to run the Trunk step at all.
|
||||
default: ''
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Configure AWS credentials
|
||||
if: inputs.quarantine-skipped != 'true'
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
aws-access-key-id: ${{ inputs.aws-access-key-id }}
|
||||
aws-secret-access-key: ${{ inputs.aws-secret-access-key }}
|
||||
aws-region: ${{ inputs.aws-region }}
|
||||
- name: zip test results
|
||||
if: inputs.quarantine-skipped != 'true'
|
||||
env:
|
||||
INPUT_DIR: ${{ inputs.input-path }}
|
||||
OUTPUT_FILE: ${{ inputs.output-name }}
|
||||
@@ -53,7 +39,6 @@ runs:
|
||||
run: | # sh
|
||||
zip ${OUTPUT_FILE}.zip $INPUT_DIR/*.xml
|
||||
- name: Upload test results to S3
|
||||
if: inputs.quarantine-skipped != 'true'
|
||||
env:
|
||||
BUCKET: ${{ inputs.bucket }}
|
||||
OUTPUT_FILE: ${{ inputs.output-name }}
|
||||
@@ -63,18 +48,11 @@ runs:
|
||||
aws s3 cp ${OUTPUT_FILE}.zip s3://$BUCKET/$DATE/$GITHUB_RUN_ID/$GITHUB_RUN_ATTEMPT/
|
||||
|
||||
- name: Upload results to Trunk
|
||||
if: ${{ always() && github.repository_owner == 'metabase' && inputs.run-trunk == 'true' && inputs.quarantine-skipped != 'true' }}
|
||||
# Important!
|
||||
# 1. This step must always run because it controls job's exit code.
|
||||
# 2. There's no point in running this outside the organization
|
||||
if: ${{ always() && !(github.event_name == 'pull_request' && github.event.pull_request.draft == true) }}
|
||||
uses: trunk-io/analytics-uploader@main
|
||||
env:
|
||||
TRUNK_REPO_URL: git@github.com:metabase/metabase.git
|
||||
with:
|
||||
junit-paths: ${{ inputs.input-path }}
|
||||
variant: ${{ inputs.variant || inputs.output-name }}
|
||||
org-slug: metabase
|
||||
dry-run: ${{ (github.event_name == 'pull_request' && github.event.pull_request.draft == true) || github.repository != 'metabase/metabase' }} # quarantine, but do not upload results
|
||||
token: ${{ inputs.trunk-api-token }}
|
||||
use-cache: true
|
||||
previous-step-outcome: ${{ inputs.previous-step-outcome }}
|
||||
continue-on-error: true
|
||||
|
||||
2
.github/codeql/codeql-config.yml
vendored
2
.github/codeql/codeql-config.yml
vendored
@@ -1,5 +1,3 @@
|
||||
version: 1
|
||||
|
||||
name: "Metabase CodeQL config"
|
||||
|
||||
paths-ignore:
|
||||
|
||||
55
.github/file-paths.yaml
vendored
55
.github/file-paths.yaml
vendored
@@ -11,20 +11,16 @@ 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
|
||||
- "yarn.lock"
|
||||
- "**/tsconfig*.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,22 @@ 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"
|
||||
e2e_cross_version: &e2e_cross_version
|
||||
- ".github/workflows/e2e-cross-version.yml"
|
||||
- "e2e/test/scenarios/cross-version/**"
|
||||
|
||||
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
|
||||
@@ -178,7 +170,6 @@ yaml:
|
||||
- "**/*.yaml"
|
||||
|
||||
codeql:
|
||||
- ".github/workflows/codeql.yml"
|
||||
- "frontend/src/**"
|
||||
- "enterprise/frontend/src/**"
|
||||
|
||||
|
||||
1
.github/scripts/average-e2e-timings.js
vendored
1
.github/scripts/average-e2e-timings.js
vendored
@@ -21,6 +21,7 @@ const collectAndAverageTimings = async ({
|
||||
|
||||
const timingData = await downloadAndExtractArtifacts(
|
||||
artifacts,
|
||||
token,
|
||||
github,
|
||||
context,
|
||||
);
|
||||
|
||||
35
.github/scripts/build-e2e-matrix.js
vendored
35
.github/scripts/build-e2e-matrix.js
vendored
@@ -3,16 +3,21 @@
|
||||
// grouping some specific tests together, other tests are split into chunks
|
||||
|
||||
const DEFAULT_SPEC_PATTERN = "./e2e/test/scenarios/**/*.cy.spec.*";
|
||||
const EMBEDDING_SDK_SPEC_PATTERN =
|
||||
"./e2e/test/scenarios/embedding-sdk/**.cy.spec.*";
|
||||
|
||||
const specialTestConfigs = [
|
||||
{
|
||||
name: "embedding-sdk",
|
||||
specs: EMBEDDING_SDK_SPEC_PATTERN,
|
||||
},
|
||||
{
|
||||
name: "oss-subset",
|
||||
edition: "oss",
|
||||
tags: "@OSS @prerelease+-@EE",
|
||||
tags: "@OSS @smoke+-@EE",
|
||||
specs: DEFAULT_SPEC_PATTERN,
|
||||
},
|
||||
{ name: "mongo", tags: "@mongo", specs: DEFAULT_SPEC_PATTERN },
|
||||
{ name: "python", tags: "@python", specs: DEFAULT_SPEC_PATTERN },
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -52,21 +57,17 @@ function buildMatrix(options, inputSpecs, inputChunks) {
|
||||
);
|
||||
}
|
||||
|
||||
const regularTests = Array.from({ length: regularChunks }, (_, index) => {
|
||||
const paddedIndex = String(index + 1).padStart(2, "0");
|
||||
|
||||
return {
|
||||
name: `e2e-group-${paddedIndex}`,
|
||||
// works when specs less than 5, otherwise seems all chunks will contain
|
||||
// same specs
|
||||
...(!isDefaultSpecPattern && {
|
||||
specs: inputSpecs
|
||||
.split(",")
|
||||
.slice(SPECS_PER_CHUNK * index, SPECS_PER_CHUNK * (index + 1))
|
||||
.join(","),
|
||||
}),
|
||||
};
|
||||
});
|
||||
const regularTests = new Array(regularChunks).fill(1).map((files, index) => ({
|
||||
name: `e2e-group-${index + 1}`,
|
||||
// works when specs less than 5, otherwise seems all chunks will contain
|
||||
// same specs
|
||||
...(!isDefaultSpecPattern && {
|
||||
specs: inputSpecs
|
||||
.split(",")
|
||||
.slice(SPECS_PER_CHUNK * index, SPECS_PER_CHUNK * (index + 1))
|
||||
.join(","),
|
||||
}),
|
||||
}));
|
||||
|
||||
const testSets = isDefaultSpecPattern
|
||||
? regularTests.concat(specialTestConfigs)
|
||||
|
||||
26
.github/scripts/build-e2e-matrix.unit.spec.js
vendored
26
.github/scripts/build-e2e-matrix.unit.spec.js
vendored
@@ -3,7 +3,7 @@ const { buildMatrix } = require("./build-e2e-matrix");
|
||||
describe("buildMatrix", () => {
|
||||
const options = {
|
||||
java: "11",
|
||||
defaultRunner: "${{ vars.DEFAULT_RUNNER_KEY }}",
|
||||
defaultRunner: "ubuntu-22.04",
|
||||
};
|
||||
const testBuildMatrix = (entry, chunks) =>
|
||||
buildMatrix(options, entry, chunks);
|
||||
@@ -21,10 +21,10 @@ describe("buildMatrix", () => {
|
||||
});
|
||||
|
||||
it("should apply default specs path when empty entry is provided", () => {
|
||||
const result = testBuildMatrix("", 6);
|
||||
const result = testBuildMatrix("", 5);
|
||||
|
||||
expect(result.regularChunks).toBe(3);
|
||||
expect(result.config.length).toBe(6);
|
||||
expect(result.regularChunks).toBe(2);
|
||||
expect(result.config.length).toBe(5);
|
||||
});
|
||||
|
||||
it("should name regular test groups sequentially", () => {
|
||||
@@ -34,9 +34,7 @@ describe("buildMatrix", () => {
|
||||
);
|
||||
|
||||
regularTests.forEach((test, index) => {
|
||||
expect(test.name).toBe(
|
||||
`e2e-group-${String(index + 1).padStart(2, "0")}`,
|
||||
);
|
||||
expect(test.name).toBe(`e2e-group-${index + 1}`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -45,9 +43,9 @@ describe("buildMatrix", () => {
|
||||
|
||||
expect(result.regularChunks).toBe(0);
|
||||
expect(result.config.length).toBe(3);
|
||||
expect(result.config[0].name).toBe("oss-subset");
|
||||
expect(result.config[1].name).toBe("mongo");
|
||||
expect(result.config[2].name).toBe("python");
|
||||
expect(result.config[0].name).toBe("embedding-sdk");
|
||||
expect(result.config[1].name).toBe("oss-subset");
|
||||
expect(result.config[2].name).toBe("mongo");
|
||||
});
|
||||
|
||||
it("should correctly calculate regularChunks for non-default spec pattern", () => {
|
||||
@@ -85,15 +83,15 @@ describe("buildMatrix", () => {
|
||||
it("should return special test configs when default spec pattern is provided", () => {
|
||||
const result = testBuildMatrix("./e2e/test/scenarios/**/*.cy.spec.*", 50);
|
||||
|
||||
expect(result.config[47].name).toBe("oss-subset");
|
||||
expect(result.config[48].name).toBe("mongo");
|
||||
expect(result.config[49].name).toBe("python");
|
||||
expect(result.config[47].name).toBe("embedding-sdk");
|
||||
expect(result.config[48].name).toBe("oss-subset");
|
||||
expect(result.config[49].name).toBe("mongo");
|
||||
});
|
||||
|
||||
it("should not return special test configs when non-default spec pattern is provided", () => {
|
||||
const result = testBuildMatrix("test1.cy.spec.js,test2.cy.spec.js", 50);
|
||||
|
||||
expect(result.config.length).toBe(1);
|
||||
expect(result.config[0].name).toBe("e2e-group-01");
|
||||
expect(result.config[0].name).toBe("e2e-group-1");
|
||||
});
|
||||
});
|
||||
|
||||
53
.github/team.json
vendored
53
.github/team.json
vendored
@@ -32,6 +32,20 @@
|
||||
"lorem--ipsum"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Workflows",
|
||||
"label": ".Team/Workflows",
|
||||
"members": [
|
||||
"qnkhuat",
|
||||
"piranha",
|
||||
"crisptrutski",
|
||||
"spalt08",
|
||||
"stasgavrylov",
|
||||
"uladzimirdev",
|
||||
"kulyk",
|
||||
"kamilmielnik"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Admin Webapp",
|
||||
"label": ".Team/AdminWebapp",
|
||||
@@ -44,13 +58,10 @@
|
||||
"label": ".Team/DevEx",
|
||||
"members": [
|
||||
"camsaul",
|
||||
"dpsutton",
|
||||
"escherize",
|
||||
"fraserdrops",
|
||||
"nemanjaglumac"
|
||||
"escherize"
|
||||
]
|
||||
},
|
||||
{
|
||||
{
|
||||
"name": "Metabot",
|
||||
"label": ".Team/Metabot",
|
||||
"members": [
|
||||
@@ -59,10 +70,19 @@
|
||||
"retro",
|
||||
"sloansparger",
|
||||
"somtom",
|
||||
"wotbrew",
|
||||
"tsplude",
|
||||
"lbrdnk",
|
||||
"piranha",
|
||||
"crisptrutski"
|
||||
"bronsa"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Product Growth",
|
||||
"label": ".Team/ProductGrowth",
|
||||
"members": [
|
||||
"nemanjaglumac",
|
||||
"chodorowicz",
|
||||
"andsalves"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -91,24 +111,7 @@
|
||||
"edpaget",
|
||||
"deniskaber",
|
||||
"alxnddr",
|
||||
"bpander",
|
||||
"andsalves",
|
||||
"gantoreno"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Gadget",
|
||||
"label": ".Team/Gadget",
|
||||
"members": [
|
||||
"nmezzopera",
|
||||
"bronsa",
|
||||
"wotbrew",
|
||||
"qnkhuat",
|
||||
"chodorowicz",
|
||||
"kamilmielnik",
|
||||
"stasgavrylov",
|
||||
"uladzimirdev",
|
||||
"egorgrushin"
|
||||
"bpander"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
64
.github/workflows/app-db.yml
vendored
64
.github/workflows/app-db.yml
vendored
@@ -11,17 +11,10 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}-app-db
|
||||
cancel-in-progress: true
|
||||
|
||||
# Exposed to every job so the test-driver action can report failures to
|
||||
# ci-conductor (secrets: inherit is already set on the app-db call in
|
||||
# run-tests.yml). See DEV-2224.
|
||||
env:
|
||||
CI_CONDUCTOR_BASE_URL: ${{ secrets.CI_CONDUCTOR_BASE_URL }}
|
||||
CI_CONDUCTOR_WEBHOOK_SECRET: ${{ secrets.CI_CONDUCTOR_WEBHOOK_SECRET }}
|
||||
|
||||
jobs:
|
||||
be-tests-mariadb:
|
||||
if: ${{ !inputs.skip }}
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -75,8 +68,8 @@ jobs:
|
||||
(
|
||||
github.event_name == 'push' &&
|
||||
(
|
||||
github.ref_name == 'master' ||
|
||||
startsWith(github.ref_name, 'release-x')
|
||||
github.ref == 'master' ||
|
||||
startsWith(github.ref, 'release-')
|
||||
) &&
|
||||
''
|
||||
) ||
|
||||
@@ -112,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 }}
|
||||
@@ -120,7 +112,6 @@ jobs:
|
||||
test-args: >-
|
||||
${{ matrix.job.test-args }}
|
||||
:exclude-tags '[ ${{ matrix.job.exclude-tag }} ${{ env.__ADDITIONAL_EXCLUDED_TAG__ }}]'
|
||||
quarantine-engine: ${{ vars.QUARANTINE_ENGINE }}
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -132,12 +123,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 }}
|
||||
run-trunk: ${{ vars.QUARANTINE_ENGINE != 'ci-conductor' }}
|
||||
|
||||
be-tests-mysql:
|
||||
if: ${{ !inputs.skip }}
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -148,13 +137,11 @@ jobs:
|
||||
image: cimg/mysql:8.0
|
||||
env:
|
||||
enable-ssl-tests: 'false'
|
||||
enable-aws-iam-tests: 'false'
|
||||
- name: MySQL Latest
|
||||
junit-name: be-tests-mysql-latest-ee
|
||||
image: mysql:latest
|
||||
env:
|
||||
enable-ssl-tests: 'true'
|
||||
enable-aws-iam-tests: 'true'
|
||||
job:
|
||||
- name: Enterprise Tests
|
||||
build-static-viz: false
|
||||
@@ -193,8 +180,8 @@ jobs:
|
||||
(
|
||||
github.event_name == 'push' &&
|
||||
(
|
||||
github.ref_name == 'master' ||
|
||||
startsWith(github.ref_name, 'release-x')
|
||||
github.ref == 'master' ||
|
||||
startsWith(github.ref, 'release-')
|
||||
) &&
|
||||
''
|
||||
) ||
|
||||
@@ -224,21 +211,12 @@ jobs:
|
||||
MB_MYSQL_SSL_TEST_SSL_CERT: ${{ secrets.MB_MYSQL_SSL_TEST_SSL_CERT }}
|
||||
MB_MYSQL_SSL_TEST_USER: metabase
|
||||
MB_MYSQL_SSL_TEST_PASSWORD: ${{ secrets.MYSQL_RDS_SSL_INSTANCE_PASSWORD }}
|
||||
MB_MYSQL_AWS_IAM_TEST: ${{ matrix.version.env.enable-aws-iam-tests }}
|
||||
MB_MYSQL_AWS_IAM_TEST_HOST: ${{ secrets.MYSQL_AURORA_IAM_INSTANCE_HOST }}
|
||||
MB_MYSQL_AWS_IAM_TEST_PORT: 3306
|
||||
MB_MYSQL_AWS_IAM_TEST_USER: my_iam_user
|
||||
MB_MYSQL_AWS_IAM_TEST_DBNAME: test_db
|
||||
MB_MYSQL_AWS_IAM_TEST_SSL_CERT: trust
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_AURORA_IAM_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_AURORA_IAM_SECRET_ACCESS_KEY }}
|
||||
# for historic reasons (I don't want to mess around with required jobs) the job name should be something like
|
||||
# "be-tests-mariadb-10-2-ee (0)"
|
||||
name: "${{ matrix.version.name }} ${{ matrix.job.name }}"
|
||||
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 }}
|
||||
@@ -246,7 +224,6 @@ jobs:
|
||||
test-args: >-
|
||||
${{ matrix.job.test-args }}
|
||||
:exclude-tags '[ ${{ matrix.job.exclude-tag }} ${{ env.__ADDITIONAL_EXCLUDED_TAG__ }}]'
|
||||
quarantine-engine: ${{ vars.QUARANTINE_ENGINE }}
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -258,12 +235,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 }}
|
||||
run-trunk: ${{ vars.QUARANTINE_ENGINE != 'ci-conductor' }}
|
||||
|
||||
be-tests-postgres:
|
||||
if: ${{ !inputs.skip }}
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -274,14 +249,18 @@ jobs:
|
||||
docker-image: postgres:12-alpine
|
||||
env:
|
||||
enable-ssl-tests: 'false'
|
||||
enable-aws-iam-tests: 'false'
|
||||
- name: Postgres Latest
|
||||
junit-name: postgres-latest-ee
|
||||
docker-image: postgres:latest
|
||||
env:
|
||||
enable-ssl-tests: 'true'
|
||||
enable-aws-iam-tests: 'true'
|
||||
job:
|
||||
- name: Driver Tests
|
||||
build-static-viz: false
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
:exclude-tags [:mb/transforms-python-test]
|
||||
exclude-tag: ':mb/transforms-python-test'
|
||||
- name: Enterprise Tests
|
||||
build-static-viz: false
|
||||
test-args: >-
|
||||
@@ -312,8 +291,8 @@ jobs:
|
||||
(
|
||||
github.event_name == 'push' &&
|
||||
(
|
||||
github.ref_name == 'master' ||
|
||||
startsWith(github.ref_name, 'release-x')
|
||||
github.ref == 'master' ||
|
||||
startsWith(github.ref, 'release-')
|
||||
) &&
|
||||
''
|
||||
) ||
|
||||
@@ -332,13 +311,6 @@ jobs:
|
||||
MB_POSTGRES_SSL_TEST_SSL: ${{ matrix.version.env.enable-ssl-tests }}
|
||||
MB_POSTGRES_SSL_TEST_SSL_MODE: verify-full
|
||||
MB_POSTGRES_SSL_TEST_SSL_ROOT_CERT_PATH: 'test-resources/certificates/us-east-2-bundle.pem'
|
||||
MB_POSTGRES_AWS_IAM_TEST: ${{ matrix.version.env.enable-aws-iam-tests }}
|
||||
MB_POSTGRES_AWS_IAM_TEST_HOST: ${{ secrets.POSTGRES_AURORA_IAM_INSTANCE_HOST }}
|
||||
MB_POSTGRES_AWS_IAM_TEST_PORT: 5432
|
||||
MB_POSTGRES_AWS_IAM_TEST_USER: my_iam_user
|
||||
MB_POSTGRES_AWS_IAM_TEST_DBNAME: test_db
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_AURORA_IAM_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_AURORA_IAM_SECRET_ACCESS_KEY }}
|
||||
services:
|
||||
postgres:
|
||||
image: ${{ matrix.version.docker-image }}
|
||||
@@ -351,7 +323,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 +330,6 @@ jobs:
|
||||
test-args: >-
|
||||
${{ matrix.job.test-args }}
|
||||
:exclude-tags '[ ${{ matrix.job.exclude-tag }} ${{ env.__ADDITIONAL_EXCLUDED_TAG__ }}]'
|
||||
quarantine-engine: ${{ vars.QUARANTINE_ENGINE }}
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -371,15 +341,13 @@ 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 }}
|
||||
run-trunk: ${{ vars.QUARANTINE_ENGINE != 'ci-conductor' }}
|
||||
|
||||
app-db-tests-result:
|
||||
needs:
|
||||
- be-tests-mariadb
|
||||
- be-tests-mysql
|
||||
- be-tests-postgres
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
name: app-db-tests-result
|
||||
if: always() && !cancelled()
|
||||
|
||||
2
.github/workflows/athena.yml
vendored
2
.github/workflows/athena.yml
vendored
@@ -11,7 +11,7 @@ on:
|
||||
jobs:
|
||||
deploy:
|
||||
name: Publish Athena JDBC Driver
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Check out the code
|
||||
|
||||
11
.github/workflows/auto-assign.yml
vendored
11
.github/workflows/auto-assign.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
update_pr:
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: tibdex/github-app-token@v2.1.0
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
script: | #js
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const teamConfig = JSON.parse(fs.readFileSync('.github/team.json', 'utf-8'));
|
||||
const prAuthor = context.payload.pull_request.user.login;
|
||||
@@ -65,3 +65,10 @@ jobs:
|
||||
issue_number: prNumber,
|
||||
labels: [team.label]
|
||||
});
|
||||
|
||||
return team.projectUrl || null;
|
||||
- uses: actions/add-to-project@v0.5.0
|
||||
if: ${{ steps.auto-assign.outputs.result != 'null' }}
|
||||
with:
|
||||
project-url: ${{ steps.auto-assign.outputs.result }}
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
|
||||
2
.github/workflows/auto-backport.yml
vendored
2
.github/workflows/auto-backport.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
github.event.label.name == 'triple-backport'
|
||||
)
|
||||
)
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
|
||||
193
.github/workflows/backend-cloverage.yml
vendored
193
.github/workflows/backend-cloverage.yml
vendored
@@ -9,203 +9,22 @@ jobs:
|
||||
be-linter-cloverage:
|
||||
# don't run this workflow on a cron for forks
|
||||
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
modules:
|
||||
- actions
|
||||
- activity-feed
|
||||
- analytics
|
||||
- analyze
|
||||
- api
|
||||
- api-keys
|
||||
- api-routes
|
||||
- app-db
|
||||
- appearance
|
||||
- audit-app
|
||||
- auth-provider
|
||||
- batch-processing
|
||||
- bookmarks
|
||||
- bug-reporting
|
||||
- cache
|
||||
- channel
|
||||
- classloader
|
||||
- cloud-migration
|
||||
- cmd
|
||||
- collections
|
||||
- config
|
||||
- connection-pool
|
||||
- content-translation
|
||||
- content-verification
|
||||
- core
|
||||
- dashboards
|
||||
- database-routing
|
||||
- driver
|
||||
- driver-api
|
||||
- eid-translation
|
||||
- embedding
|
||||
- events
|
||||
- formatter
|
||||
- geojson
|
||||
- glossary
|
||||
- graph
|
||||
- indexed-entities
|
||||
- internal-stats
|
||||
- legacy-mbql
|
||||
- lib
|
||||
- lib-be
|
||||
- logger
|
||||
- login-history
|
||||
- model-persistence
|
||||
- models
|
||||
- native-query-snippets
|
||||
- notification
|
||||
- parameters
|
||||
- permissions
|
||||
- pivot
|
||||
- plugins
|
||||
- premium-features
|
||||
- product-feedback
|
||||
- public-sharing
|
||||
- pulse
|
||||
- queries
|
||||
- query-permissions
|
||||
- query-processor
|
||||
- remote-sync
|
||||
- request
|
||||
- revisions
|
||||
- sample-data
|
||||
- search
|
||||
- secrets
|
||||
- segments
|
||||
- server
|
||||
- session
|
||||
- settings
|
||||
- setup
|
||||
- sso
|
||||
- startup
|
||||
- store-api
|
||||
- sync
|
||||
- system
|
||||
- task
|
||||
- task-history
|
||||
- testing-api
|
||||
- tiles
|
||||
- timeline
|
||||
- types
|
||||
- upload
|
||||
- user-key-value
|
||||
- users
|
||||
- util
|
||||
- version
|
||||
- view-log
|
||||
- warehouse-schema
|
||||
- warehouses
|
||||
- xrays
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Prepare front-end environment
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
|
||||
with:
|
||||
m2-cache-key: "cloverage"
|
||||
- name: Build static viz frontend
|
||||
run: yarn build-static-viz
|
||||
env:
|
||||
MB_EDITION: ee
|
||||
- name: Collect the test coverage
|
||||
run: |
|
||||
clojure -X:dev:ci:ee:ee-dev:test:cloverage \
|
||||
:ns-regex '["^metabase\\.${{ matrix.modules }}\\..*"]' \
|
||||
:test-ns-path '["test/metabase/${{ matrix.modules }}"]'
|
||||
continue-on-error: true
|
||||
|
||||
run: clojure -X:dev:ci:ee:ee-dev:test:cloverage
|
||||
- name: Upload coverage to codecov.io
|
||||
if: always()
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./target/coverage/codecov.json
|
||||
flags: back-end
|
||||
|
||||
be-linter-cloverage-enterprise:
|
||||
# don't run this workflow on a cron for forks
|
||||
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
modules:
|
||||
- action-v2
|
||||
- advanced-config
|
||||
- advanced-permissions
|
||||
- ai-entity-analysis
|
||||
- ai-sql-fixer
|
||||
- ai-sql-generation
|
||||
- analytics
|
||||
- api
|
||||
- audit-app
|
||||
- auth-provider
|
||||
- billing
|
||||
- bookmarks
|
||||
- cache
|
||||
- cloud-add-ons
|
||||
- comments
|
||||
- content-translation
|
||||
- content-verification
|
||||
- core
|
||||
- dashboard-subscription-filters
|
||||
- database-replication
|
||||
- database-routing
|
||||
- dependencies
|
||||
- documents
|
||||
- email
|
||||
- embedding-hub
|
||||
- gsheets
|
||||
- harbormaster
|
||||
- impersonation
|
||||
- internal-stats
|
||||
- llm
|
||||
- metabot-v3
|
||||
- permission-debug
|
||||
- premium-features
|
||||
- public-sharing
|
||||
- remote-sync
|
||||
- sandbox
|
||||
- scim
|
||||
- search
|
||||
- semantic-search
|
||||
- serialization
|
||||
- snippet-collections
|
||||
- sso
|
||||
- stale
|
||||
- transforms
|
||||
- transforms-python
|
||||
- upload-management
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Prepare front-end environment
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
|
||||
- name: Build static viz frontend
|
||||
run: yarn build-static-viz
|
||||
env:
|
||||
MB_EDITION: ee
|
||||
- name: Collect the test coverage
|
||||
run: |
|
||||
clojure -X:dev:ci:ee:ee-dev:test:cloverage \
|
||||
:ns-regex '["^metabase-enterprise\\.${{ matrix.modules }}\\..*"]' \
|
||||
:test-ns-path '["enterprise/backend/test/metabase/${{ matrix.modules }}"]'
|
||||
continue-on-error: true
|
||||
|
||||
- name: Upload coverage to codecov.io
|
||||
if: always()
|
||||
uses: codecov/codecov-action@v5
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./target/coverage/codecov.json
|
||||
|
||||
82
.github/workflows/backend.yml
vendored
82
.github/workflows/backend.yml
vendored
@@ -11,41 +11,38 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}-backend
|
||||
cancel-in-progress: true
|
||||
|
||||
# Exposed to the test jobs so they can report failures to ci-conductor
|
||||
# (secrets: inherit is already set on the backend call in run-tests.yml).
|
||||
# See DEV-2224.
|
||||
env:
|
||||
CI_CONDUCTOR_BASE_URL: ${{ secrets.CI_CONDUCTOR_BASE_URL }}
|
||||
CI_CONDUCTOR_WEBHOOK_SECRET: ${{ secrets.CI_CONDUCTOR_WEBHOOK_SECRET }}
|
||||
|
||||
jobs:
|
||||
be-linter-clj-kondo:
|
||||
if: ${{ !inputs.skip }}
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
name: Clj-Kondo
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: 'kondo'
|
||||
- name: Run clj-kondo
|
||||
run: ./bin/mage kondo
|
||||
|
||||
be-linter-eastwood:
|
||||
if: ${{ !inputs.skip }}
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 20
|
||||
name: Eastwood
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: "eastwood"
|
||||
- run: clojure -X:dev:ee:ee-dev:drivers:drivers-dev:test:eastwood
|
||||
name: Run Eastwood linter
|
||||
|
||||
be-tests:
|
||||
if: ${{ !inputs.skip }}
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -54,33 +51,33 @@ jobs:
|
||||
job:
|
||||
- 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
|
||||
:partition/index 0
|
||||
- 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
|
||||
:partition/index 1
|
||||
- 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
|
||||
:partition/index 0
|
||||
- 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
|
||||
@@ -95,8 +92,8 @@ jobs:
|
||||
(
|
||||
github.event_name == 'push' &&
|
||||
(
|
||||
github.ref_name == 'master' ||
|
||||
startsWith(github.ref_name, 'release-x')
|
||||
github.ref == 'master' ||
|
||||
startsWith(github.ref, 'release-')
|
||||
) &&
|
||||
''
|
||||
) ||
|
||||
@@ -105,20 +102,26 @@ 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:
|
||||
m2-cache-key: be-tests
|
||||
java-version: "${{ matrix.java-version }}"
|
||||
# Depending on which files changed, we might either have to build static-viz
|
||||
# from scratch or download the previously built artifact
|
||||
- name: Build static-viz frontend
|
||||
if: ${{ matrix.job.build-static-viz == 'true' && !inputs.skip }}
|
||||
run: yarn build-static-viz
|
||||
env:
|
||||
MB_EDITION: ${{ matrix.job.edition }}
|
||||
|
||||
- name: Resolve current job id
|
||||
# Resolve JOB_ID before the long test step so the ci-conductor reporter
|
||||
# below can link failures to this exact job. Best-effort.
|
||||
uses: ./.github/actions/resolve-job-id
|
||||
- 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
|
||||
@@ -126,27 +129,6 @@ jobs:
|
||||
clojure -X:dev:ci:test:${{ matrix.job.edition }}:${{ matrix.job.edition }}-dev
|
||||
:exclude-tags '[:mb/driver-tests ${{ env.__ADDITIONAL_EXCLUDED_TAG__ }}]'
|
||||
${{ matrix.job.test-args }}
|
||||
# Keep this step continue-on-error because the quarantine logic controls whether it ultimately fails.
|
||||
continue-on-error: true
|
||||
|
||||
- name: Report failed tests to ci-conductor
|
||||
if: ${{ !cancelled() && steps.run-java-tests.outcome == 'failure' }}
|
||||
# This step should NEVER fail the job!
|
||||
continue-on-error: true
|
||||
uses: ./.github/actions/report-failures-to-ci-conductor
|
||||
with:
|
||||
test-suite: be-tests-java-${{ matrix.java-version }}-${{ matrix.job.test-group-name }}
|
||||
adapter: backend
|
||||
|
||||
- name: Quarantine gate
|
||||
if: ${{ !cancelled() && steps.run-java-tests.outcome == 'failure' }}
|
||||
continue-on-error: ${{ vars.QUARANTINE_ENGINE != 'ci-conductor' }}
|
||||
timeout-minutes: 3
|
||||
uses: ./.github/actions/quarantine-gate
|
||||
with:
|
||||
adapter: backend
|
||||
test-suite: be-tests-java-${{ matrix.java-version }}-${{ matrix.job.test-group-name }}
|
||||
enforce-quarantine: ${{ vars.QUARANTINE_ENGINE == 'ci-conductor' }}
|
||||
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
@@ -159,8 +141,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 }}
|
||||
run-trunk: ${{ vars.QUARANTINE_ENGINE != 'ci-conductor' }}
|
||||
|
||||
- name: Publish Test Report (JUnit)
|
||||
uses: dorny/test-reporter@v1
|
||||
@@ -177,7 +157,7 @@ jobs:
|
||||
if: always() && !success()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-logs-${{ matrix.java-version }}-${{ matrix.job.edition }}-${{ github.job }}-${{ matrix.job.test-group-name }}
|
||||
name: test-logs-${{ matrix.java-version }}-${{ matrix.job.edition }}-${{ github.job }}
|
||||
path: logs/test-log.json
|
||||
retention-days: 1
|
||||
|
||||
@@ -185,7 +165,7 @@ jobs:
|
||||
# as `:dev` dependencies). See #27009 for more context.
|
||||
be-check:
|
||||
if: ${{ !inputs.skip }}
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -195,19 +175,23 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Prepare backend
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: be-check
|
||||
# TODO -- we should probably also check WITHOUT ee -- do a check with only OSS namespaces on the classpath
|
||||
- name: Check namespaces
|
||||
run: clojure -M:ee:drivers:check
|
||||
|
||||
be-cljfmt:
|
||||
if: ${{ !inputs.skip }}
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
name: Cljfmt
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: 'cljfmt'
|
||||
- name: Run cljfmt
|
||||
run: ./bin/mage cljfmt-all --force-check
|
||||
|
||||
@@ -219,7 +203,7 @@ jobs:
|
||||
- be-check
|
||||
- be-tests
|
||||
if: always() && !cancelled()
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
env:
|
||||
needs: ${{ toJson(needs) }}
|
||||
|
||||
2
.github/workflows/backport-reminder.yml
vendored
2
.github/workflows/backport-reminder.yml
vendored
@@ -10,7 +10,7 @@ on:
|
||||
jobs:
|
||||
backport-reminder:
|
||||
name: Decide whether to backport or not
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: jesusvasquez333/verify-pr-label-action@v1.4.0
|
||||
|
||||
@@ -5,7 +5,7 @@ on:
|
||||
|
||||
jobs:
|
||||
check-source-branch:
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Block master branch PRs
|
||||
if: github.head_ref == 'master'
|
||||
|
||||
52
.github/workflows/bugs-triage-slack-notification.yml
vendored
Normal file
52
.github/workflows/bugs-triage-slack-notification.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
name: Bugs Slack Notification
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- labeled
|
||||
jobs:
|
||||
on-issue-labeled:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
if: |
|
||||
github.event.label.name == '.Needs Triage' && contains(github.event.issue.labels.*.name, 'Type:Bug')
|
||||
steps:
|
||||
- name: Setting title
|
||||
uses: actions/github-script@v7
|
||||
id: vars
|
||||
with:
|
||||
script: |
|
||||
core.setOutput('issue_title', ${{ toJson(github.event.issue.title) }}.replaceAll(/"/g, '\\"'));
|
||||
- name: Check for labels containing team name
|
||||
id: check_labels
|
||||
run: |
|
||||
labels_json=$(sed 's/&/\$/g' <<< '${{ toJson(github.event.issue.labels) }}')
|
||||
echo "has_team_label=false" >> "$GITHUB_OUTPUT"
|
||||
for label in "$labels_json"
|
||||
do
|
||||
if [[ $label == *".Team"* ]]; then
|
||||
echo "Label contains the team name."
|
||||
echo "has_team_label=true" >> "$GITHUB_OUTPUT"
|
||||
break
|
||||
fi
|
||||
done
|
||||
- name: Send the issue ${{ github.event.issue.number }} to Slack
|
||||
id: slack
|
||||
if: steps.check_labels.outputs.has_team_label == 'false'
|
||||
uses: slackapi/slack-github-action@v1.26.0
|
||||
with:
|
||||
payload: |
|
||||
{
|
||||
"blocks": [
|
||||
{
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": "${{ steps.vars.outputs.issue_title }}\n${{ github.event.issue.html_url }}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_BUG_TRIAGE_WEBHOOK_URL }}
|
||||
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
|
||||
12
.github/workflows/build-for-release.yml
vendored
12
.github/workflows/build-for-release.yml
vendored
@@ -14,7 +14,7 @@ on:
|
||||
|
||||
jobs:
|
||||
start-message:
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
}).catch(console.error);
|
||||
|
||||
trigger-milestone-check:
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
needs: start-message
|
||||
steps:
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
|
||||
|
||||
check-version:
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
outputs:
|
||||
ee: ${{ fromJson(steps.canonical_version.outputs.result).ee }}
|
||||
@@ -114,7 +114,7 @@ jobs:
|
||||
return versions;
|
||||
|
||||
check-commit:
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Check out the code to verify the release branch
|
||||
@@ -143,7 +143,7 @@ jobs:
|
||||
|| (echo "Commit not found in correct release branch" && exit 1)
|
||||
|
||||
get-build-requirements:
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
outputs:
|
||||
java_version: ${{ fromJson(steps.dependencies.outputs.result).java_version }}
|
||||
@@ -176,7 +176,7 @@ jobs:
|
||||
|
||||
build-uberjar-for-release:
|
||||
needs: [check-version, check-commit, get-build-requirements]
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 50
|
||||
strategy:
|
||||
matrix:
|
||||
|
||||
4
.github/workflows/build-scripts.yml
vendored
4
.github/workflows/build-scripts.yml
vendored
@@ -10,12 +10,14 @@ on:
|
||||
jobs:
|
||||
|
||||
test-build-scripts:
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Prepare back-end environment
|
||||
timeout-minutes: 5
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: 'build-scripts'
|
||||
|
||||
- name: Run build and release script tests
|
||||
run: clojure -X:dev:drivers:build:build-dev:build-test:ci
|
||||
|
||||
135
.github/workflows/cache-generator.yml
vendored
135
.github/workflows/cache-generator.yml
vendored
@@ -1,135 +0,0 @@
|
||||
# This workflow is the single source of truth for generating and updating various caches used by our CI pipeline.
|
||||
# It sets the fresh cache each Monday but provides a manual trigger to update it on-demand.
|
||||
#
|
||||
# All other jobs only restore from this cache; *they never update it*!
|
||||
# They should always use `actions/cache/restore@v4` because `actions/cache@v4` would attempt to save the cache.
|
||||
#
|
||||
# Our agreed-upon strategy is to use a deterministic cache key based on the week of the year.
|
||||
# Jobs can utilize the prior week's cache if the current week's cache is not found.
|
||||
#
|
||||
#
|
||||
# PRINCIPLES:
|
||||
# - The cache is never wrong!
|
||||
# - Caches are immutable once created.
|
||||
# - Caches are for speeding up builds, not for correctness.
|
||||
# - It is faster to download a large cache than to re-resolve missing dependencies.
|
||||
#
|
||||
#
|
||||
# DESIGN:
|
||||
#
|
||||
# 1. Prepare
|
||||
# - A job first prepares the local environment (e.g., installs JDK, Clojure, Node.js, etc.)
|
||||
# - This fetches the dependencies from the cache (if it exists) and warms up the local cache for this workflow.
|
||||
# 2. Resolve
|
||||
# - The next step pre-resolves all dependencies, ensuring that the local cache is fully populated and up-to-date.
|
||||
# 3. Update
|
||||
# - The final step saves the local cache back to GitHub Actions cache with a deterministic key.
|
||||
# - If a cache entry already exists for that key, it is deleted first to ensure immutability.
|
||||
#
|
||||
name: Cache Generator
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 3 * * 1" # Mondays at 03:00 UTC
|
||||
workflow_dispatch: # manually update the cache
|
||||
|
||||
concurrency:
|
||||
group: cache-generator
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
get-cache-keys:
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Get cache keys
|
||||
uses: ./.github/actions/get-cache-keys
|
||||
id: get-cache-keys
|
||||
outputs:
|
||||
m2-cache-key: ${{ steps.get-cache-keys.outputs.m2-cache-key }}
|
||||
node-cache-key: ${{ steps.get-cache-keys.outputs.node-cache-key }}
|
||||
eslint-cache-key: ${{ steps.get-cache-keys.outputs.eslint-cache-key }}
|
||||
cypress-cache-key: ${{ steps.get-cache-keys.outputs.cypress-cache-key }}
|
||||
|
||||
generate-caches:
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
timeout-minutes: 30
|
||||
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
|
||||
run: clojure -A:build:dev:ee:ee-dev:drivers:drivers-dev:cljs -P
|
||||
|
||||
- name: Update M2 cache
|
||||
uses: ./.github/actions/update-cache
|
||||
with:
|
||||
path: |
|
||||
~/.m2
|
||||
~/.gitlibs
|
||||
key: ${{ env.M2_CACHE_KEY }}
|
||||
|
||||
# Frontend preparation and caches
|
||||
# `prepare-frontend` action (1) prepares Node.js and (2) fetches the node_modules cache if it exists.
|
||||
# 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
|
||||
# Hence why we explicitly exclude patches from the cache (controlled by the `apply-patches` action input).
|
||||
- name: Install front-end dependencies
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
with:
|
||||
apply-patches: "false"
|
||||
|
||||
- name: Get yarn cache directory
|
||||
id: yarn-cache-dir
|
||||
run: |
|
||||
YARN_CACHE_DIR=$(yarn cache dir)
|
||||
echo "Yarn cache directory: $YARN_CACHE_DIR"
|
||||
echo "dir=$YARN_CACHE_DIR" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Ensure that Cypress executable is ready
|
||||
run: |
|
||||
yarn cypress install
|
||||
yarn cypress cache path
|
||||
yarn cypress cache list
|
||||
yarn cypress verify
|
||||
shell: bash
|
||||
|
||||
- name: Update node_modules cache
|
||||
uses: ./.github/actions/update-cache
|
||||
with:
|
||||
path: |
|
||||
${{ steps.yarn-cache-dir.outputs.dir }}
|
||||
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: yarn build-pure:cljs
|
||||
|
||||
- name: Run ESLint to populate cache
|
||||
run: yarn lint-eslint-pure
|
||||
|
||||
- name: Update eslint cache
|
||||
uses: ./.github/actions/update-cache
|
||||
with:
|
||||
path: ${{ github.workspace}}/.eslintcache
|
||||
key: ${{ env.ESLINT_CACHE_KEY }}
|
||||
2
.github/workflows/check-backport-script.yml
vendored
2
.github/workflows/check-backport-script.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
check-backport-script:
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
13
.github/workflows/check-milestone.yml
vendored
13
.github/workflows/check-milestone.yml
vendored
@@ -8,14 +8,14 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Major version number (e.g. 52, 78, 109)"
|
||||
description: 'Major version number (e.g. 52, 78, 109)'
|
||||
type: number
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
set-milestone:
|
||||
name: Automatically set milestone
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
isNewReleaseBranch: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
@@ -26,14 +26,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
# .github folder is needed for the prepare-release-scripts action in master
|
||||
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 # this action sets up bun, which is used in master, but not on versions prior to 59
|
||||
- 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
|
||||
|
||||
6
.github/workflows/chromatic.yml
vendored
6
.github/workflows/chromatic.yml
vendored
@@ -14,7 +14,7 @@ concurrency:
|
||||
jobs:
|
||||
files-changed:
|
||||
name: Check which files changed
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 3
|
||||
outputs:
|
||||
frontend_all: ${{ steps.changes.outputs.frontend_all }}
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
fe-chromatic:
|
||||
needs: files-changed
|
||||
if: contains(github.event.pull_request.labels.*.name, 'chromatic') || (github.ref_name == 'master' && needs.files-changed.outputs.frontend_all == 'true')
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
@@ -40,6 +40,8 @@ jobs:
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: "cljs"
|
||||
- name: Compile CLJS
|
||||
run: NODE_ENV=development yarn build-pure:cljs
|
||||
- name: Publish to Chromatic
|
||||
|
||||
25
.github/workflows/claude-code-bug-fix.yml
vendored
25
.github/workflows/claude-code-bug-fix.yml
vendored
@@ -21,7 +21,7 @@ on:
|
||||
|
||||
jobs:
|
||||
auto-fix-bug:
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -159,15 +159,15 @@ jobs:
|
||||
1. **Analyze the issue description and comments** - Understand what the issue is describing
|
||||
a. Pay particular attention to reproduction steps and if there's an agreed upon fix.
|
||||
2. **Search the codebase** - Use Grep and Glob tools to find relevant files
|
||||
3. **Implement a fix** - Use the appropriate write skill based on the file types:
|
||||
- Use clojure-write for Clojure/ClojureScript files
|
||||
- Use typescript-write for TypeScript/JavaScript files
|
||||
- Follow the coding standards and patterns in each skill
|
||||
4. **Test the fix** - Run appropriate tests as specified in the skills
|
||||
3. **Implement a fix** - Follow existing code patterns and conventions
|
||||
4. **Test the fix** - Run appropriate tests using the commands in CLAUDE.md:
|
||||
- JavaScript/TypeScript: `yarn test-unit path/to/file.unit.spec.js`
|
||||
- Clojure: `clojure -X:dev:test :only namespace/test-name`
|
||||
- Lint: `yarn lint-eslint-pure` for JS/TS, `./bin/mage kondo <file>` for Clojure
|
||||
5. **Verify the fix** - Ensure tests pass and code follows conventions
|
||||
|
||||
## Important Guidelines
|
||||
- Use the appropriate write skill for the language you're working in
|
||||
- Follow the development patterns in CLAUDE.md
|
||||
- Write failing tests first, then fix them
|
||||
- Work in small, testable increments
|
||||
- Don't commit changes - leave that for the workflow
|
||||
@@ -213,14 +213,15 @@ jobs:
|
||||
private-key: ${{ secrets.METABASE_BOT_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Generate Bug Fix with Claude Code
|
||||
uses: anthropics/claude-code-action@v1
|
||||
uses: anthropics/claude-code-action@beta
|
||||
with:
|
||||
mode: agent
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
github_token: ${{ steps.claude-app-token.outputs.token }}
|
||||
prompt: ${{ env.BUG_FIX_PROMPT }}
|
||||
claude_args: |
|
||||
--allowedTools "Bash(yarn test-unit*),Bash(yarn lint-eslint-pure),Bash(./bin/mage*),Bash(clojure*),Bash(find*),Bash(ls*),Bash(grep*),Write,Read,Glob,Grep,Edit,MultiEdit"
|
||||
--disallowedTools "Bash(git commit*),Bash(git push*),Bash(gh*)"
|
||||
direct_prompt: ${{ env.BUG_FIX_PROMPT }}
|
||||
timeout_minutes: "25"
|
||||
allowed_tools: "Bash(yarn test-unit*),Bash(yarn lint-eslint-pure),Bash(./bin/mage*),Bash(clojure*),Bash(find*),Bash(ls*),Bash(grep*),Write,Read,Glob,Grep,Edit,MultiEdit"
|
||||
disallowed_tools: "Bash(git commit*),Bash(git push*),Bash(gh*)"
|
||||
|
||||
- name: Clean up scratchpad directory
|
||||
run: |
|
||||
|
||||
98
.github/workflows/claude-code-review.yaml
vendored
98
.github/workflows/claude-code-review.yaml
vendored
@@ -1,98 +0,0 @@
|
||||
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: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
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 for compliance with project coding standards and conventions.
|
||||
|
||||
**Use the appropriate review skill based on the file types changed:**
|
||||
- Use clojure-review for Clojure/ClojureScript files
|
||||
- Use typescript-review for TypeScript/JavaScript files
|
||||
- Follow the review guidelines and checklists in each skill
|
||||
|
||||
DO NOT post any comments confirming that something is correct - only post comments for style violations or concerns.
|
||||
|
||||
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)"
|
||||
101
.github/workflows/claude-docs-review.yml
vendored
101
.github/workflows/claude-docs-review.yml
vendored
@@ -1,101 +0,0 @@
|
||||
name: "Claude Docs Review"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- reopened
|
||||
paths:
|
||||
- 'docs/**/*.md'
|
||||
- 'docs/**/*.mdx'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr_number:
|
||||
description: 'PR number to review'
|
||||
required: true
|
||||
type: number
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}-claude-docs-review
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
review:
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
if: github.event.pull_request.draft == false
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
# Make sure the value of GITHUB_TOKEN will not be persisted in repo's config
|
||||
persist-credentials: false
|
||||
|
||||
- name: Generate GitHub App Token for Claude
|
||||
uses: actions/create-github-app-token@v1
|
||||
id: claude-app-token
|
||||
with:
|
||||
app-id: ${{ secrets.METABASE_BOT_APP_ID }}
|
||||
private-key: ${{ secrets.METABASE_BOT_APP_PRIVATE_KEY }}
|
||||
permission-contents: read # Required for reading repository files and PR diffs
|
||||
permission-pull-requests: write # Required for creating inline comments and PR comments
|
||||
|
||||
- name: Delete old Claude docs review comments
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.claude-app-token.outputs.token }}
|
||||
REPO: ${{ github.repository }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number || github.event.inputs.pr_number }}
|
||||
shell: bash
|
||||
run: |
|
||||
# Delete pending reviews first (these can be deleted entirely)
|
||||
pending_review_ids=$(gh api "/repos/$REPO/pulls/$PR_NUMBER/reviews" --jq '.[] | select(.user.login=="metabase-bot[bot]" and .state=="PENDING") | .id')
|
||||
if [ -n "$pending_review_ids" ]; then
|
||||
echo "Deleting pending reviews: $pending_review_ids"
|
||||
for id in $pending_review_ids; do
|
||||
gh api -X DELETE "/repos/$REPO/pulls/$PR_NUMBER/reviews/$id"
|
||||
echo "Deleted pending review $id"
|
||||
done
|
||||
fi
|
||||
|
||||
# Delete individual inline comments from submitted reviews (can't delete the review itself)
|
||||
old_line_comment_ids=$(gh api "/repos/$REPO/pulls/$PR_NUMBER/comments" --jq '.[] | select(.user.login=="metabase-bot[bot]") | .id')
|
||||
if [ -n "$old_line_comment_ids" ]; then
|
||||
echo "Deleting line comments: $old_line_comment_ids"
|
||||
for id in $old_line_comment_ids; do
|
||||
gh api -X DELETE "/repos/$REPO/pulls/comments/$id"
|
||||
echo "Deleted line comment $id"
|
||||
done
|
||||
fi
|
||||
|
||||
# Delete top-level review comments
|
||||
old_top_level_comment_ids=$(gh api "/repos/$REPO/issues/$PR_NUMBER/comments" --jq '.[] | select(.user.login=="metabase-bot[bot]") | .id')
|
||||
if [ -n "$old_top_level_comment_ids" ]; then
|
||||
echo "Deleting top-level comments: $old_top_level_comment_ids"
|
||||
for id in $old_top_level_comment_ids; do
|
||||
gh api -X DELETE "/repos/$REPO/issues/comments/$id"
|
||||
echo "Deleted top-level comment $id"
|
||||
done
|
||||
fi
|
||||
|
||||
- uses: anthropics/claude-code-action@v1
|
||||
timeout-minutes: 10
|
||||
with:
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
github_token: ${{ steps.claude-app-token.outputs.token }}
|
||||
show_full_output: true
|
||||
prompt: |
|
||||
REPO: ${{ github.repository }}
|
||||
PR NUMBER: ${{ github.event.pull_request.number || github.event.inputs.pr_number }}
|
||||
|
||||
Review the documentation changes in this pull request.
|
||||
|
||||
Use the docs-review skill to review all changed documentation files for compliance with the Metabase writing style guide.
|
||||
|
||||
DO NOT post any comments confirming that something is correct - only post comments for style violations or concerns.
|
||||
|
||||
claude_args: |
|
||||
--allowedTools "Skill,mcp__github__get_pull_request,mcp__github__get_pull_request_files,mcp__github__create_pending_pull_request_review,mcp__github__get_pull_request_diff,mcp__github__add_comment_to_pending_review,mcp__github__add_pull_request_review_comment_to_pending_review,mcp__github__delete_pending_pull_request_review,mcp__github__submit_pending_pull_request_review"
|
||||
2
.github/workflows/clean-cache.yml
vendored
2
.github/workflows/clean-cache.yml
vendored
@@ -6,7 +6,7 @@ on:
|
||||
|
||||
jobs:
|
||||
cleanup:
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
6
.github/workflows/cljs.yml
vendored
6
.github/workflows/cljs.yml
vendored
@@ -14,7 +14,7 @@ concurrency:
|
||||
jobs:
|
||||
files-changed:
|
||||
name: Check which files changed
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 3
|
||||
outputs:
|
||||
cljs: ${{ steps.changes.outputs.cljs }}
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
shared-tests-cljs:
|
||||
needs: files-changed
|
||||
if: needs.files-changed.outputs.cljs == 'true'
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -38,5 +38,7 @@ jobs:
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: 'cljs'
|
||||
- name: Run Cljs tests
|
||||
run: yarn run test-cljs
|
||||
|
||||
9
.github/workflows/codeql.yml
vendored
9
.github/workflows/codeql.yml
vendored
@@ -10,7 +10,7 @@ on:
|
||||
jobs:
|
||||
files-changed:
|
||||
name: Check which files changed
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 3
|
||||
outputs:
|
||||
codeql: ${{ steps.changes.outputs.codeql }}
|
||||
@@ -25,15 +25,14 @@ jobs:
|
||||
analyze:
|
||||
needs: files-changed
|
||||
if: needs.files-changed.outputs.codeql == 'true'
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v4
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: javascript
|
||||
config-file: ./.github/codeql/codeql-config.yml
|
||||
trap-caching: false
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v4
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
||||
110
.github/workflows/codespell.yml
vendored
110
.github/workflows/codespell.yml
vendored
@@ -1,110 +0,0 @@
|
||||
name: Codespell
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run nightly at 4:00 AM UTC
|
||||
- cron: "0 4 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
codespell:
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
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
|
||||
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
|
||||
4
.github/workflows/containerize-jar.yml
vendored
4
.github/workflows/containerize-jar.yml
vendored
@@ -35,7 +35,7 @@ on:
|
||||
|
||||
jobs:
|
||||
containerize:
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
outputs:
|
||||
platforms: ${{ steps.platforms.outputs.result }}
|
||||
@@ -102,7 +102,7 @@ jobs:
|
||||
bin/docker/.
|
||||
|
||||
verify-docker-pull:
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: containerize
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
|
||||
9
.github/workflows/containerize-uberjar.yml
vendored
9
.github/workflows/containerize-uberjar.yml
vendored
@@ -6,7 +6,7 @@ on:
|
||||
|
||||
jobs:
|
||||
get-container-info:
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
name: Get container info
|
||||
outputs:
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
edition: ${{ fromJson(needs.get-container-info.outputs.editions) }}
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
name: Run Trivy vulnerability scanner
|
||||
steps:
|
||||
- name: Check out the code
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
with:
|
||||
ref: ${{ github.event.inputs.commit || github.sha }}
|
||||
- name: Run Trivy vulnerability scanner
|
||||
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1
|
||||
uses: aquasecurity/trivy-action@0.29.0
|
||||
env:
|
||||
TRIVY_OFFLINE_SCAN: true
|
||||
repo: ${{ fromJson(needs.get-container-info.outputs[matrix.edition]).repo }}
|
||||
@@ -85,6 +85,7 @@ jobs:
|
||||
image-ref: docker.io/${{ env.repo }}:${{ env.tag }}
|
||||
format: sarif
|
||||
output: trivy-results.sarif
|
||||
version: "v0.57.1"
|
||||
- name: Upload Trivy scan results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
@@ -93,7 +94,7 @@ jobs:
|
||||
post-comment:
|
||||
needs: [get-container-info, containerize]
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
name: Post PR comment
|
||||
steps:
|
||||
- name: Check out the code
|
||||
|
||||
@@ -8,7 +8,7 @@ on:
|
||||
jobs:
|
||||
files-changed:
|
||||
name: Check which files changed
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 3
|
||||
outputs:
|
||||
frontend_embedding_sources: ${{ steps.changes.outputs.frontend_embedding_sources }}
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
filters: .github/file-paths.yaml
|
||||
|
||||
pr-title-reminder:
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
17
.github/workflows/coverage.yml
vendored
17
.github/workflows/coverage.yml
vendored
@@ -1,28 +1,25 @@
|
||||
name: Frontend Test Coverage
|
||||
name: Test coverage
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '40 1 * * *' # every day at 1:40
|
||||
|
||||
jobs:
|
||||
fe-test-coverage:
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 30
|
||||
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
|
||||
with:
|
||||
m2-cache-key: "cljs"
|
||||
- name: Run frontend unit tests
|
||||
run: |
|
||||
yarn run test-unit \
|
||||
--silent \
|
||||
--coverage \
|
||||
--testTimeout=60000
|
||||
run: yarn run test-unit --silent --coverage
|
||||
- name: Upload coverage to codecov.io
|
||||
uses: codecov/codecov-action@v5
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
files: ./coverage/lcov.info
|
||||
flags: front-end
|
||||
|
||||
14
.github/workflows/cross-branch-migrations.yml
vendored
14
.github/workflows/cross-branch-migrations.yml
vendored
@@ -25,7 +25,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
migrations-h2-ee:
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
CI: 'true'
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
head-ref: ${{ env.HEAD_REF }}
|
||||
|
||||
migrations-postgres-12-ee:
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
CI: 'true'
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
head-ref: ${{ env.HEAD_REF }}
|
||||
|
||||
migrations-postgres-latest-ee:
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
CI: 'true'
|
||||
@@ -91,7 +91,7 @@ jobs:
|
||||
head-ref: ${{ env.HEAD_REF }}
|
||||
|
||||
migrations-mysql-8-0-ee:
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
CI: 'true'
|
||||
@@ -113,7 +113,7 @@ jobs:
|
||||
head-ref: ${{ env.HEAD_REF }}
|
||||
|
||||
migrations-mysql-latest-ee:
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
CI: 'true'
|
||||
@@ -139,7 +139,7 @@ jobs:
|
||||
head-ref: ${{ env.HEAD_REF }}
|
||||
|
||||
migrations-mariadb-10-2-ee:
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
CI: 'true'
|
||||
@@ -161,7 +161,7 @@ jobs:
|
||||
head-ref: ${{ env.HEAD_REF }}
|
||||
|
||||
migrations-mariadb-latest-ee:
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
CI: 'true'
|
||||
|
||||
16
.github/workflows/docs-generate-base.yml
vendored
16
.github/workflows/docs-generate-base.yml
vendored
@@ -9,6 +9,10 @@ on:
|
||||
description: "The branch to check out and create a PR against"
|
||||
required: true
|
||||
type: string
|
||||
m2_cache_key_suffix:
|
||||
description: "A unique suffix for the Maven cache key"
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
automation_token:
|
||||
description: "A token with permissions to create branches and PRs"
|
||||
@@ -21,7 +25,7 @@ permissions:
|
||||
jobs:
|
||||
update-docs:
|
||||
name: Update documentation for ${{ inputs.target_branch }}
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@@ -33,6 +37,8 @@ jobs:
|
||||
|
||||
- name: Prepare backend environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: "auto-update-docs-${{ inputs.m2_cache_key_suffix }}"
|
||||
|
||||
- name: Generate environment variables documentation
|
||||
id: generate_env_docs
|
||||
@@ -60,14 +66,6 @@ jobs:
|
||||
clojure -M:ee:doc api-documentation
|
||||
continue-on-error: false
|
||||
|
||||
- name: Generate command documentation
|
||||
id: generate_command_docs
|
||||
run: |
|
||||
set -e # Exit on any error
|
||||
echo "Generating API documentation..."
|
||||
clojure -M:ee:doc command-documentation
|
||||
continue-on-error: false
|
||||
|
||||
- name: Check for changes
|
||||
id: check_changes
|
||||
run: |
|
||||
|
||||
8
.github/workflows/docs-links.yml
vendored
8
.github/workflows/docs-links.yml
vendored
@@ -2,18 +2,20 @@ name: Check doc links
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "docs/**"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
link-checker:
|
||||
name: Check documentation links
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
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
|
||||
|
||||
1
.github/workflows/docs-master-generate.yml
vendored
1
.github/workflows/docs-master-generate.yml
vendored
@@ -11,5 +11,6 @@ jobs:
|
||||
uses: ./.github/workflows/docs-generate-base.yml
|
||||
with:
|
||||
target_branch: "master"
|
||||
m2_cache_key_suffix: "master"
|
||||
secrets:
|
||||
automation_token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
|
||||
9
.github/workflows/docs-release-generate.yml
vendored
9
.github/workflows/docs-release-generate.yml
vendored
@@ -4,18 +4,13 @@ on:
|
||||
schedule:
|
||||
- cron: "0 2 * * 1-5" # Every Monday through Friday at 2 AM UTC
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
target_branch:
|
||||
description: "Target branch for documentation update"
|
||||
required: false
|
||||
default: "release-x.57.x"
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
update-release-docs:
|
||||
name: Call documentation update for release branch
|
||||
uses: ./.github/workflows/docs-generate-base.yml
|
||||
with:
|
||||
target_branch: ${{ inputs.target_branch || 'release-x.57.x' }}
|
||||
target_branch: "release-x.56.x"
|
||||
m2_cache_key_suffix: "release"
|
||||
secrets:
|
||||
automation_token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
|
||||
44
.github/workflows/docs_bump_detected.yml
vendored
Normal file
44
.github/workflows/docs_bump_detected.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
name: Docs Bump Detected
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- reopened
|
||||
branches:
|
||||
- 'master'
|
||||
- 'release-x.[0-9]+.x'
|
||||
paths:
|
||||
- 'docs/**'
|
||||
|
||||
jobs:
|
||||
push_workflow_to_docs_metabase_github_io_job:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SOURCE_BRANCH: ${{ github.head_ref || github.ref }}
|
||||
TARGET_BRANCH: ${{ github.event.pull_request.base.ref }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
steps:
|
||||
- name: Report changed docs files exist
|
||||
run: |
|
||||
echo "A PR with changes in the docs directory was detected."
|
||||
echo "Sending workflow trigger to create/update the corresponding PR in docs repo."
|
||||
echo "source branch: $SOURCE_BRANCH"
|
||||
echo "target branch: $TARGET_BRANCH"
|
||||
echo "pr number: $PR_NUMBER"
|
||||
|
||||
- name: Trigger repo dispatch
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
script: | #js
|
||||
github.rest.repos.createDispatchEvent({
|
||||
owner: "${{ github.repository_owner }}",
|
||||
repo: "docs.metabase.github.io",
|
||||
event_type: "docs_update",
|
||||
client_payload: {
|
||||
dispatch_type: "docs_update",
|
||||
source_branch: process.env.SOURCE_BRANCH,
|
||||
target_branch: process.env.TARGET_BRANCH,
|
||||
pr_number: process.env.PR_NUMBER}});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user