Compare commits
163 Commits
copilot/re
...
openapi-sp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42e3a98805 | ||
|
|
245825e10e | ||
|
|
b0b2b597ab | ||
|
|
3eaf107895 | ||
|
|
d54e6b64e2 | ||
|
|
763d5e1777 | ||
|
|
b746306e12 | ||
|
|
f0c90980ff | ||
|
|
e53f226b40 | ||
|
|
40cbf2ffb5 | ||
|
|
55a0afbd2a | ||
|
|
f759d9f473 | ||
|
|
39951a570a | ||
|
|
6b369ef818 | ||
|
|
f3c352d920 | ||
|
|
9c0576cc5c | ||
|
|
fc00027fca | ||
|
|
3be69174e6 | ||
|
|
11a73d8621 | ||
|
|
d1e7b483dd | ||
|
|
aa66ab25d6 | ||
|
|
8207fc3181 | ||
|
|
b429839f0a | ||
|
|
451f080ede | ||
|
|
4f00289729 | ||
|
|
369d410d4d | ||
|
|
b8c13482df | ||
|
|
126eb6b1b1 | ||
|
|
4ab19dce4d | ||
|
|
39b4a21830 | ||
|
|
3ba0f90cf0 | ||
|
|
6e799b2ef6 | ||
|
|
dcd2d7394d | ||
|
|
3c693179e4 | ||
|
|
b01732fd69 | ||
|
|
a75ee49fd1 | ||
|
|
dbfe313b4a | ||
|
|
088bd180d5 | ||
|
|
0226eaa5d2 | ||
|
|
3d68aa1ac4 | ||
|
|
c18c08b02c | ||
|
|
851bedf5bb | ||
|
|
9981cd5323 | ||
|
|
264604b61e | ||
|
|
a5a0773eaf | ||
|
|
c463756c60 | ||
|
|
0f7e8983ca | ||
|
|
3401c9e902 | ||
|
|
0aeda87a3d | ||
|
|
b1ece80c0d | ||
|
|
486a6fc826 | ||
|
|
29f75c1251 | ||
|
|
66dc6b6cdc | ||
|
|
7433f38a22 | ||
|
|
71606de98c | ||
|
|
d2e8fcac5b | ||
|
|
e4d1706837 | ||
|
|
cc1361936a | ||
|
|
69c0a2ca90 | ||
|
|
3d3bbe22db | ||
|
|
ba0ddc9a32 | ||
|
|
da223ea0a1 | ||
|
|
f53c8d1513 | ||
|
|
51d7669d7d | ||
|
|
eb20991259 | ||
|
|
ec4874cfe5 | ||
|
|
8f442ca39b | ||
|
|
8c6e4aa60b | ||
|
|
3d3d49dfca | ||
|
|
bba60088cb | ||
|
|
981113ef47 | ||
|
|
9c8e505c9f | ||
|
|
f4bbf2984f | ||
|
|
273f1677a1 | ||
|
|
12e6b46118 | ||
|
|
f27597b904 | ||
|
|
b2889824b3 | ||
|
|
28cfae2e67 | ||
|
|
60707e20ec | ||
|
|
a4e418f280 | ||
|
|
c20bf2a943 | ||
|
|
c66d31fec1 | ||
|
|
93cb3bc327 | ||
|
|
52325a7503 | ||
|
|
dadf042982 | ||
|
|
18e1fb02aa | ||
|
|
5dd8b26770 | ||
|
|
b6c1a51630 | ||
|
|
09ee4178ca | ||
|
|
a68b644ec8 | ||
|
|
ca002fd953 | ||
|
|
ba5de0f2fa | ||
|
|
b4f717609e | ||
|
|
3f898be45a | ||
|
|
071fc345b4 | ||
|
|
08c4a89f91 | ||
|
|
7d2d6571cb | ||
|
|
f6f2a31b6a | ||
|
|
1342901722 | ||
|
|
a554da88bd | ||
|
|
7fa10713ea | ||
|
|
43e3fa694d | ||
|
|
370a4dff14 | ||
|
|
dc319ca76a | ||
|
|
b08d62e316 | ||
|
|
ba302fc0ef | ||
|
|
716a26ef75 | ||
|
|
f1de74d0d2 | ||
|
|
54bd11c5b9 | ||
|
|
2e3efef978 | ||
|
|
9d9433b5b0 | ||
|
|
f56c4e39cb | ||
|
|
f3fa8226cf | ||
|
|
4376ecc379 | ||
|
|
2c04482d27 | ||
|
|
be492dc637 | ||
|
|
b2fb97a98a | ||
|
|
347f3f11b4 | ||
|
|
d59d58e61a | ||
|
|
c8d8369fec | ||
|
|
ecaff38ac0 | ||
|
|
951f74665d | ||
|
|
acf4530cc6 | ||
|
|
8eccd8a0f9 | ||
|
|
83fdafad01 | ||
|
|
71cfad67bb | ||
|
|
103e9056e1 | ||
|
|
984cf44eca | ||
|
|
d2f7234d7f | ||
|
|
4ab6f9df02 | ||
|
|
4c0c9aa08f | ||
|
|
32a00c99e5 | ||
|
|
559c2f27de | ||
|
|
fd775c440d | ||
|
|
4a79872533 | ||
|
|
127132f8a6 | ||
|
|
140d1adcb7 | ||
|
|
639c8debc9 | ||
|
|
795fa21b9d | ||
|
|
2f043ea9d2 | ||
|
|
0e113a5c68 | ||
|
|
e3ec99133a | ||
|
|
cd8c4b1e98 | ||
|
|
ead4e7d4d9 | ||
|
|
f3fe43bb86 | ||
|
|
e385103a21 | ||
|
|
13681a004a | ||
|
|
a8715223ae | ||
|
|
3cbf8919a7 | ||
|
|
d4702e10e0 | ||
|
|
c8cd828410 | ||
|
|
aa5c870dc4 | ||
|
|
f4aa0cc722 | ||
|
|
764befaffb | ||
|
|
dbced47022 | ||
|
|
224d17965d | ||
|
|
0b644a038f | ||
|
|
65c102daf2 | ||
|
|
5da65b6c52 | ||
|
|
5f66a26a53 | ||
|
|
75db6b5973 | ||
|
|
085fc52dca | ||
|
|
b404c13d0c |
@@ -5,227 +5,110 @@ description: Run mutation testing on a Clojure namespace, generate tests to kill
|
||||
|
||||
# Mutation Testing Skill
|
||||
|
||||
This skill runs mutation testing on a target namespace, writes tests to kill surviving mutations, and creates draft PRs with Linear issues for each function.
|
||||
This skill runs mutation testing end-to-end: generates a coverage report, groups functions, shells out to `claude -p` to write tests, verifies mutations are killed, and creates draft PRs with Linear issues.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A running nREPL connected to the Metabase dev environment
|
||||
- `LINEAR_API_KEY` environment variable set with a valid Linear personal API key
|
||||
- `gh` CLI authenticated with GitHub
|
||||
- `claude` CLI available on PATH
|
||||
|
||||
## Reference Files
|
||||
|
||||
- `dev/src/dev/coverage.clj` — mutation testing tool (generates reports, runs mutations)
|
||||
- `dev/src/dev/mutation_testing.clj` — Linear API client and PR template helpers
|
||||
- `mutation-testing-report.lib.card.md` — example report from the pilot
|
||||
- `dev/src/dev/coverage.clj` — mutation testing engine (generates reports, runs mutations)
|
||||
- `dev/src/dev/mutation_testing.clj` — orchestration, Linear API, PR helpers
|
||||
|
||||
## Invocation
|
||||
|
||||
The argument is a Clojure namespace, e.g.:
|
||||
The argument is a Clojure namespace, optionally followed by a base branch:
|
||||
|
||||
```
|
||||
/mutation-testing metabase.lib.order-by
|
||||
/mutation-testing metabase.lib.order-by --base-branch release-x.52.x
|
||||
/mutation-testing metabase.lib.order-by --project-id abc-123
|
||||
/mutation-testing metabase.lib.order-by --base-branch release-x.52.x --project-id abc-123
|
||||
```
|
||||
|
||||
## Workflow
|
||||
- `--base-branch`: defaults to the value in config (set via `set-config!`), or `"master"` if not configured.
|
||||
- `--project-id`: if provided, issues are added to this existing Linear project instead of creating a new one.
|
||||
|
||||
### Step 1: Parse the namespace
|
||||
## Steps
|
||||
|
||||
From the argument (e.g., `metabase.lib.order-by`), derive:
|
||||
- **Test namespace**: append `-test` (e.g., `metabase.lib.order-by-test`)
|
||||
- **Short name**: the last segment (e.g., `order-by`)
|
||||
- **Source path**: `src/metabase/lib/order_by.cljc` (replace `-` with `_` in path segments)
|
||||
- **Test path**: `test/metabase/lib/order_by_test.cljc`
|
||||
### 1. Parse arguments
|
||||
|
||||
### Step 2: Set up Linear and load tools
|
||||
Parse `$ARGUMENTS` to extract:
|
||||
- **namespace** (required) — the first positional argument
|
||||
- **`--base-branch`** (optional) — if present, the next argument is the base branch name
|
||||
- **`--project-id`** (optional) — if present, the next argument is an existing Linear project ID
|
||||
|
||||
### 2. Load and configure
|
||||
|
||||
```clojure
|
||||
(require '[dev.coverage :as cov] :reload)
|
||||
(require '[dev.mutation-testing :as mut-test] :reload)
|
||||
(require '[dev.coverage :as cov] :reload)
|
||||
```
|
||||
|
||||
If this is the first invocation (or the REPL was restarted), set up the Linear team:
|
||||
|
||||
```clojure
|
||||
;; Find the team
|
||||
(mut-test/list-teams!)
|
||||
;; Set team ID from the output
|
||||
(mut-test/set-config! {:team-id "<team-id>"})
|
||||
(mut-test/set-config! {:team-id "<id>"})
|
||||
```
|
||||
|
||||
If config is already set from a previous invocation, skip this.
|
||||
|
||||
### Step 3: Generate baseline report and create Linear project
|
||||
### 3. Run
|
||||
|
||||
```clojure
|
||||
(cov/generate-report
|
||||
'<target-ns>
|
||||
['<test-ns>]
|
||||
"mutation-testing-report.lib.<short-name>.before.md")
|
||||
(mut-test/run! '<target-ns>)
|
||||
;; Or with options:
|
||||
(mut-test/run! '<target-ns> {:base-branch "release-x.52.x"})
|
||||
(mut-test/run! '<target-ns> {:project-id "abc-123"})
|
||||
(mut-test/run! '<target-ns> {:base-branch "release-x.52.x" :project-id "abc-123"})
|
||||
```
|
||||
|
||||
Read the generated report file. It has three sections:
|
||||
- **Uncovered Functions** — never called by any test
|
||||
- **Partially Covered Functions** — called but with surviving mutations
|
||||
- **Fully Covered Functions** — all mutations killed
|
||||
Pass the opts map with `:base-branch` and/or `:project-id` if those flags were provided in the arguments.
|
||||
|
||||
Then create a Linear project for this namespace (uses report stats in the description):
|
||||
This will:
|
||||
1. Generate a baseline mutation testing report
|
||||
2. Create a Linear project for the namespace
|
||||
3. Group functions by coverage relationships
|
||||
4. For each group: create branch → invoke Claude to write tests → verify → retry if needed → commit & push → create Linear issue → create draft PR
|
||||
5. Print a summary with PR links
|
||||
|
||||
### 4. Handle failures
|
||||
|
||||
If a group fails mid-processing, `run!` catches the error and continues to the next group. To retry a single group manually:
|
||||
|
||||
```clojure
|
||||
(mut-test/create-project-for-namespace!
|
||||
"<target-ns>"
|
||||
"mutation-testing-report.lib.<short-name>.before.md")
|
||||
;; => {:project-id "...", :name "Mutation Testing: metabase.lib.order-by"}
|
||||
;; Automatically sets :project-id in config for subsequent create-issue! calls
|
||||
;; Get the parsed namespace info
|
||||
(def parsed (mut-test/parse-namespace '<target-ns>))
|
||||
|
||||
;; Run coverage to get the data
|
||||
(def coverage-results (cov/test-namespace (:target-ns parsed) [(:test-ns parsed)]))
|
||||
|
||||
;; Group and find the one you want
|
||||
(def groups (mut-test/group-functions coverage-results))
|
||||
|
||||
;; Process just that group
|
||||
(mut-test/process-group! parsed (nth groups <index>))
|
||||
```
|
||||
|
||||
### Step 4: Plan the work
|
||||
### 5. Verify the prompt (dry run)
|
||||
|
||||
Before creating branches, analyze the report to plan how to group the work:
|
||||
|
||||
1. **Identify killable functions.** For each function with surviving mutations, assess whether any mutations are killable. Run `cov/test-mutations` to see the full mutation list. If ALL mutations for a function are unkillable (schema-only, semantically equivalent, etc.), skip that function — no branch or PR needed.
|
||||
|
||||
2. **Group private functions by public entry point.** Private functions cannot be tested directly. Group them with the public function that exercises them. Create one branch/PR per group, not per private function.
|
||||
|
||||
3. **Track mutations per group.** The `:mutations-before` and `:not-killed` fields in the PR should only reflect mutations for functions covered by that specific PR — never mutations from other functions.
|
||||
|
||||
### Step 5: Process each group
|
||||
|
||||
For each group of functions that has killable mutations:
|
||||
|
||||
#### 5a. Create a branch
|
||||
To inspect what Claude will see without invoking it:
|
||||
|
||||
```clojure
|
||||
(mut-test/create-branch! "<target-ns>" "<primary-fn-name>")
|
||||
;; => "mutation-testing-lib-order-by-orderable-columns"
|
||||
(def parsed (mut-test/parse-namespace '<target-ns>))
|
||||
(def coverage-results (cov/test-namespace (:target-ns parsed) [(:test-ns parsed)]))
|
||||
(def groups (mut-test/group-functions coverage-results))
|
||||
(println (mut-test/build-test-prompt
|
||||
(merge (select-keys parsed [:target-ns :test-ns :source-path :test-path])
|
||||
(select-keys (first groups) [:fn-names :mutations]))))
|
||||
```
|
||||
|
||||
Use the primary public function name for the branch. If the group covers private functions too, name the branch after the public entry point.
|
||||
|
||||
#### 5b. Read context
|
||||
|
||||
- Read the source function(s) from the source file
|
||||
- Read the full test file to understand existing test patterns, helpers, and metadata providers used
|
||||
- If the function is private, identify which public function(s) call it
|
||||
|
||||
#### 5c. Write tests
|
||||
|
||||
Generate the **simplest tests** that kill the surviving mutations while still making semantic sense. Guidelines:
|
||||
|
||||
- **Never call private functions directly.** Always test through public API functions. Use the coverage data to identify which public functions exercise the private function.
|
||||
- **Keep tests simple.** Each test should verify one meaningful behavior. Don't over-engineer tests just to chase mutation kills.
|
||||
- **Follow existing patterns.** Match the style of the existing tests: same metadata providers, same helper functions, same assertion patterns.
|
||||
- **Insert tests near related existing tests** for the same function, not at the end of the file. This minimizes merge conflicts between branches. If there are no existing tests for the function, find the most logical location based on the order of functions in the source file.
|
||||
|
||||
**IMPORTANT: Use `file_edit` (not `clojure_edit`) when editing test files.** The `clojure_edit` tool reformats the entire file, introducing unnecessary whitespace changes that pollute the diff. Use `file_edit` with exact string matching to make surgical insertions that only touch the lines you intend to change.
|
||||
|
||||
#### 5d. Verify mutations are killed
|
||||
|
||||
Use the REPL to check that the new tests kill the targeted mutations:
|
||||
|
||||
```clojure
|
||||
;; Reload the test namespace to pick up new tests
|
||||
(require '<test-ns> :reload)
|
||||
|
||||
;; Test mutations for the specific function
|
||||
(cov/test-mutations
|
||||
'<target-ns>/<fn-name>
|
||||
;; Set of test names that cover this function
|
||||
#{'<test-ns>/<test-name-1> '<test-ns>/<test-name-2>})
|
||||
```
|
||||
|
||||
Check the `:survived` key in the result. If mutations still survive:
|
||||
- Try writing additional tests
|
||||
- If a mutation is truly unkillable (semantically equivalent), note it for the PR description
|
||||
- If a mutation reveals dead/unreachable code, consider suggesting removal as a code improvement
|
||||
|
||||
#### 5e. Handle unkillable mutations and improvements
|
||||
|
||||
**Do NOT edit the source namespace just for documentation.** Instead:
|
||||
|
||||
- **Unkillable mutations**: Note them in the PR description with rationale
|
||||
- **Code improvements** (dead code removal, expression simplification) that directly relate to surviving mutants:
|
||||
1. Make the change in the source file
|
||||
2. Verify all tests still pass with the change
|
||||
3. Note the file path, line range, and the improved code
|
||||
4. **Reset the change**: `git checkout -- <source-file>` — do NOT commit it
|
||||
5. After creating the PR, use `add-suggested-change!` to post the improvement as a GitHub suggested change (Step 5h)
|
||||
6. The reviewer decides whether to accept it
|
||||
|
||||
#### 5f. Commit and push
|
||||
|
||||
```clojure
|
||||
;; Only commit the test file — never commit source changes for improvements
|
||||
(mut-test/commit-and-push! "<target-ns>" "<primary-fn-name>"
|
||||
["test/metabase/lib/<short_name>_test.cljc"])
|
||||
```
|
||||
|
||||
#### 5g. Create Linear issue and draft PR
|
||||
|
||||
```clojure
|
||||
;; Create a Linear issue (use the primary function name)
|
||||
(def issue (mut-test/create-issue-for-function! "<target-ns>" "<primary-fn-name>"))
|
||||
;; => {:identifier "QUE-1234", :url "https://linear.app/...", ...}
|
||||
|
||||
;; Create draft PR linked to the Linear issue
|
||||
;; :fn-names lists ALL functions covered by this PR
|
||||
;; :mutations-before counts only mutations for functions in this PR
|
||||
;; :not-killed lists only unkillable mutations for functions in this PR
|
||||
(def pr-url
|
||||
(mut-test/create-draft-pr!
|
||||
{:target-ns "<target-ns>"
|
||||
:fn-name "<primary-fn-name>"
|
||||
:linear-identifier (:identifier issue)
|
||||
:mutations-before 9 ;; surviving mutations for these functions only
|
||||
:tests-added 2 ;; number of new test functions
|
||||
:killed ["Replace :segment-id with :segment-id__"
|
||||
"Replace cycle-path with nil"]
|
||||
:not-killed [{:description "Replace acc with nil"
|
||||
:rationale "reduce always has single iteration"}]
|
||||
:suggested-changes []}))
|
||||
;; => "https://github.com/metabase/metabase/pull/12345"
|
||||
```
|
||||
|
||||
#### 5h. Add code improvement suggestions (if any)
|
||||
|
||||
If you identified code improvements in step 5e (and reset them), post each as a GitHub suggested change:
|
||||
|
||||
```clojure
|
||||
(mut-test/add-suggested-change!
|
||||
{:pr-url pr-url
|
||||
:path "src/metabase/lib/<short_name>.cljc"
|
||||
:start-line 42 ;; first line of the code to replace
|
||||
:end-line 45 ;; last line of the code to replace
|
||||
:suggestion "(improved-code-here)" ;; the replacement code
|
||||
:comment "**Suggested improvement:** <description of the change and why it's related to the surviving mutant>"})
|
||||
```
|
||||
|
||||
### Step 6: Return to master and repeat
|
||||
|
||||
```clojure
|
||||
(mut-test/return-to-master!)
|
||||
```
|
||||
|
||||
Repeat Step 5 for the next group.
|
||||
|
||||
### Step 7: Summary
|
||||
|
||||
Print a summary of what was done:
|
||||
- Link to the Linear project for this namespace
|
||||
- Number of functions processed
|
||||
- Number of functions skipped (all mutations unkillable)
|
||||
- Number of draft PRs created
|
||||
- Number of mutations killed vs. unkillable
|
||||
- List of test PRs with links
|
||||
- List of mutation rule PRs with links (if any were created)
|
||||
|
||||
## Suggesting New Mutation Rules
|
||||
|
||||
If you notice patterns in surviving mutations that suggest the mutation testing tool should have additional rules (e.g., a common Clojure form that isn't being mutated), open a separate PR proposing additions to `dev.coverage/mutation-rules` with an explanation.
|
||||
|
||||
## Configuration
|
||||
|
||||
The `dev.mutation-testing` namespace reads `LINEAR_API_KEY` from the environment and stores team/project IDs in an atom.
|
||||
|
||||
- **Team ID**: Set once per REPL session via `(mut-test/list-teams!)` and `(mut-test/set-config! {:team-id "..."})` (Step 2)
|
||||
- **Project ID**: Set automatically when `create-project-for-namespace!` is called (Step 3) — one project per namespace
|
||||
- **Team ID**: Set once per REPL session via `(mut-test/list-teams!)` and `(mut-test/set-config! {:team-id "..."})`
|
||||
- **Project ID**: Set automatically when `run!` calls `create-project-for-namespace!`
|
||||
- **Base Branch**: Defaults to `"master"`. Override via `(mut-test/set-config! {:base-branch "release-x.52.x"})` or pass `{:base-branch "..."}` as the second arg to `run!`
|
||||
- **Project ID**: If set (via `set-config!` or `run!` opts), `run!` reuses the existing Linear project instead of creating a new one
|
||||
|
||||
@@ -12,8 +12,9 @@ allowed-tools: Read, Grep, Bash, Glob
|
||||
|
||||
Review pull requests with a focus on:
|
||||
|
||||
- Readability and maintainability
|
||||
- Appropriate test coverage
|
||||
- Compliance with project coding standards and conventions
|
||||
- Code quality and best practices
|
||||
- Clear and correct JSDoc comments
|
||||
- Type safety and proper TypeScript usage
|
||||
- React best practices (when applicable)
|
||||
- React best practices (when applicable)
|
||||
|
||||
@@ -227,7 +227,9 @@
|
||||
;;
|
||||
|
||||
:invalid-arity {:skip-args [metabase.lib.util.match/match
|
||||
metabase.lib.util.match/match-lite]} ; TODO (cam): can we fix these?
|
||||
metabase.lib.util.match/match-lite
|
||||
metabase.lib.util.match/match-many
|
||||
metabase.lib.util.match/replace-lite]} ; TODO (cam): can we fix these?
|
||||
:refer-all {:level :warning, :exclude [clojure.test]}
|
||||
:unused-referred-var {:exclude {compojure.core [GET DELETE POST PUT]}}
|
||||
|
||||
@@ -246,13 +248,17 @@
|
||||
(metabase.lib.util.match/match)
|
||||
(metabase.lib.util.match/match-one)
|
||||
(metabase.lib.util.match/match-lite)
|
||||
(metabase.lib.util.match/match-many)
|
||||
(metabase.lib.util.match/replace)
|
||||
(metabase.lib.util.match/replace-in)
|
||||
(metabase.lib.util.match/replace-lite)
|
||||
(metabase.driver-api.core/match)
|
||||
(metabase.driver-api.core/match-one)
|
||||
(metabase.driver-api.core/match-lite)
|
||||
(metabase.driver-api.core/match-many)
|
||||
(metabase.driver-api.core/replace)
|
||||
(metabase.driver-api.core/replace-in)
|
||||
(metabase.driver-api.core/replace-lite)
|
||||
(metabase.query-processor.middleware.cache-backend.interface/with-cached-results)
|
||||
(metabase.util.regex/rx [opt])
|
||||
(metabase.util/prog1 [<>])
|
||||
@@ -304,6 +310,9 @@
|
||||
metabase.lib.convert/->legacy-MBQL {:message "Conversion to legacy MBQL is discouraged, please use Lib to manipulate MBQL queries"}
|
||||
metabase.lib.core/->legacy-MBQL {:message "Conversion to legacy MBQL is discouraged, please use Lib to manipulate MBQL queries"}
|
||||
metabase.lib.equality/find-column-indexes-for-refs {:message "Use lib.equality/closest-matches-in-metadata or lib.equality/find-closest-matches-for-refs instead of lib.equality/find-column-indexes-for-refs"}
|
||||
metabase.lib.util.match/match {:message "Use lib.util.match/match-many instead of lib.util.match/match."}
|
||||
metabase.lib.util.match/match-one {:message "Use lib.util.match/match-lite instead of lib.util.match/match-one."}
|
||||
metabase.lib.util.match/replace {:message "Use lib.util.match/replace-lite instead of lib.util.match/replace."}
|
||||
metabase-enterprise.transforms.interface/execute! {:message "Use metabase-enterprise.transforms.execute/execute! instead of metabase-enterprise.transforms.interface/execute!"}
|
||||
next.jdbc/active-tx? {:message "Use metabase.app-db.core/in-transaction? to see if we are in a transaction"}
|
||||
toucan2.core/debug {:message "Don't commit code using t2/debug"}
|
||||
|
||||
@@ -547,6 +547,8 @@
|
||||
metabase.driver.common
|
||||
metabase.driver.common.parameters
|
||||
metabase.driver.common.parameters.parse
|
||||
metabase.driver.connection
|
||||
metabase.driver.connection.workspaces
|
||||
metabase.driver.ddl.interface
|
||||
metabase.driver.h2
|
||||
metabase.driver.impl
|
||||
@@ -569,7 +571,8 @@
|
||||
metabase.driver.sql.util
|
||||
metabase.driver.sync
|
||||
metabase.driver.util}
|
||||
:uses #{app-db
|
||||
:uses #{analytics
|
||||
app-db
|
||||
appearance
|
||||
auth-provider
|
||||
classloader
|
||||
@@ -733,21 +736,12 @@
|
||||
|
||||
lib
|
||||
{:team "Querying"
|
||||
:api #{metabase.lib.binning
|
||||
metabase.lib.convert ; Only so [[metabase.queries.models.card]] can set dynamic vars.
|
||||
metabase.lib.core
|
||||
:api #{metabase.lib.core
|
||||
metabase.lib.equality
|
||||
metabase.lib.field
|
||||
metabase.lib.init
|
||||
metabase.lib.metadata
|
||||
metabase.lib.metadata.cached-provider
|
||||
metabase.lib.metadata.column
|
||||
metabase.lib.metadata.invocation-tracker
|
||||
metabase.lib.metadata.protocols
|
||||
metabase.lib.metadata.result-metadata
|
||||
metabase.lib.normalize
|
||||
metabase.lib.options
|
||||
metabase.lib.query
|
||||
metabase.lib.schema
|
||||
metabase.lib.schema.actions
|
||||
metabase.lib.schema.aggregation
|
||||
@@ -769,17 +763,19 @@
|
||||
metabase.lib.schema.test-spec
|
||||
metabase.lib.schema.util
|
||||
metabase.lib.schema.validate
|
||||
metabase.lib.temporal-bucket
|
||||
metabase.lib.types.isa
|
||||
metabase.lib.util
|
||||
metabase.lib.util.match
|
||||
metabase.lib.walk}
|
||||
metabase.lib.util.match}
|
||||
:uses #{config
|
||||
graph
|
||||
legacy-mbql
|
||||
types
|
||||
util}
|
||||
:friends #{query-processor legacy-mbql}}
|
||||
:friends #{legacy-mbql ;; Reuses internal schemas and a few helper functions. Deeply coupled to MBQL.
|
||||
lib-be ;; BE-specific extension of lib; tight coupling is acceptable.
|
||||
query-processor ;; Necessarily manipulates MBQL; better to pull most logic into a new module.
|
||||
enterprise/sandbox ;; Really part of the QP, though its lives in the enterprise/ tree.
|
||||
}}
|
||||
|
||||
lib-be
|
||||
{:team "Querying"
|
||||
@@ -1181,6 +1177,7 @@
|
||||
metabase.query-processor.middleware.add-remaps
|
||||
metabase.query-processor.middleware.annotate
|
||||
metabase.query-processor.middleware.cache-backend.db
|
||||
metabase.query-processor.middleware.catch-exceptions
|
||||
metabase.query-processor.middleware.constraints
|
||||
metabase.query-processor.middleware.limit
|
||||
metabase.query-processor.middleware.permissions
|
||||
@@ -1574,12 +1571,10 @@
|
||||
|
||||
transforms
|
||||
{:team "Querying"
|
||||
:api #{metabase.transforms.api.transform
|
||||
metabase.transforms.api.transform-job
|
||||
metabase.transforms.api.transform-tag
|
||||
metabase.transforms.core
|
||||
:api #{metabase.transforms.core
|
||||
metabase.transforms.feature-gating
|
||||
metabase.transforms.init
|
||||
metabase.transforms.interface
|
||||
metabase.transforms.schema
|
||||
metabase.transforms.util}
|
||||
:uses #{analytics
|
||||
@@ -1594,10 +1589,8 @@
|
||||
premium-features
|
||||
queries
|
||||
query-processor
|
||||
request
|
||||
revisions
|
||||
settings
|
||||
sql-tools
|
||||
sync
|
||||
task
|
||||
task-history
|
||||
@@ -1606,6 +1599,35 @@
|
||||
enterprise/transforms-python
|
||||
enterprise/workspaces}}
|
||||
|
||||
transforms-inspector
|
||||
{:team "Gadget"
|
||||
:api #{metabase.transforms-inspector.core
|
||||
metabase.transforms-inspector.schema}
|
||||
:uses #{driver
|
||||
lib
|
||||
lib-be
|
||||
query-processor
|
||||
sql-tools
|
||||
transforms
|
||||
util}}
|
||||
|
||||
transforms-rest
|
||||
{:team "Gadget"
|
||||
:api #{metabase.transforms-rest.api.transform
|
||||
metabase.transforms-rest.api.transform-job
|
||||
metabase.transforms-rest.api.transform-tag}
|
||||
:uses #{api
|
||||
driver
|
||||
events
|
||||
models
|
||||
query-processor
|
||||
request
|
||||
server
|
||||
sql-tools
|
||||
transforms
|
||||
transforms-inspector
|
||||
util}}
|
||||
|
||||
types
|
||||
{:team "Querying"
|
||||
:api #{metabase.types.core
|
||||
@@ -2433,7 +2455,7 @@
|
||||
enterprise/transforms
|
||||
{:team "Querying"
|
||||
:api #{}
|
||||
:uses #{}}
|
||||
:uses #{premium-features}}
|
||||
|
||||
enterprise/transforms-python
|
||||
{:team "Querying"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
;; TODO -- seems silly to maintain different blacklists and whitelists here than we use for the deftest `^:parallel`
|
||||
;; checker...
|
||||
{:explicitly-safe
|
||||
#{ ;; these toucan methods might actually set global values if it's used outside of a transaction,
|
||||
#{;; these toucan methods might actually set global values if it's used outside of a transaction,
|
||||
;; but since mt/with-temp runs in a transaction, so we'll ignore them in this case.
|
||||
toucan2.core/delete!
|
||||
toucan2.core/update!
|
||||
|
||||
@@ -121,7 +121,9 @@
|
||||
metabase.driver-api.core/match [[:inner 0]]
|
||||
metabase.driver-api.core/match-one [[:inner 0]]
|
||||
metabase.driver-api.core/match-lite [[:inner 0]]
|
||||
metabase.driver-api.core/match-many [[:inner 0]]
|
||||
metabase.driver-api.core/replace [[:inner 0]]
|
||||
metabase.driver-api.core/replace-lite [[:inner 0]]
|
||||
metabase.driver-api.core/replace-in [[:inner 0]]
|
||||
metabase.driver.bigquery-cloud-sdk.query-processor/with-temporal-type [[:default]]
|
||||
metabase.driver.druid.client/DELETE [[:default]]
|
||||
@@ -157,7 +159,9 @@
|
||||
metabase.lib.util.match/match [[:inner 0]]
|
||||
metabase.lib.util.match/match-one [[:inner 0]]
|
||||
metabase.lib.util.match/match-lite [[:inner 0]]
|
||||
metabase.lib.util.match/match-many [[:inner 0]]
|
||||
metabase.lib.util.match/replace [[:inner 0]]
|
||||
metabase.lib.util.match/replace-lite [[:inner 0]]
|
||||
metabase.lib.util.match/replace-in [[:inner 0]]
|
||||
metabase.models.interface/define-batched-hydration-method [[:inner 0]]
|
||||
metabase.models.interface/define-simple-hydration-method [[:inner 0]]
|
||||
|
||||
3
.github/actions/fetch-artifact/action.yml
vendored
3
.github/actions/fetch-artifact/action.yml
vendored
@@ -45,7 +45,8 @@ runs:
|
||||
});
|
||||
|
||||
if (!artifacts.data?.artifacts?.[0]?.id) {
|
||||
throw new Error(`No artifacts found for ${artifactName}`);
|
||||
core.setFailed(`No artifacts found for ${artifactName}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const artifact_id = artifacts.data.artifacts[0].id;
|
||||
|
||||
11
.github/actions/prepare-backend/action.yml
vendored
11
.github/actions/prepare-backend/action.yml
vendored
@@ -1,14 +1,13 @@
|
||||
name: Prepare back-end environment
|
||||
description: |
|
||||
Prepare the backend. Installs jdk, Clojure, and fetches the cache for m2.
|
||||
The CI config fetches `ci-test-config.json` from master and lets us ignore tests at the var level if they are flaking.
|
||||
inputs:
|
||||
java-version:
|
||||
required: true
|
||||
default: '21'
|
||||
default: "21"
|
||||
clojure-version:
|
||||
required: true
|
||||
default: '1.12.0.1488'
|
||||
default: "1.12.0.1488"
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
@@ -17,7 +16,7 @@ runs:
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: ${{ inputs.java-version }}
|
||||
distribution: 'temurin'
|
||||
distribution: "temurin"
|
||||
- name: Install Clojure CLI
|
||||
shell: bash
|
||||
continue-on-error: true
|
||||
@@ -31,6 +30,10 @@ runs:
|
||||
|
||||
- name: Install uv (for Python package installation)
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
enable-cache: false
|
||||
version: "0.10.2" # keep in sync with mise.toml
|
||||
github-token: ${{ github.token }}
|
||||
|
||||
- name: Restore M2 cache
|
||||
uses: actions/cache/restore@v4
|
||||
|
||||
5
.github/actions/prepare-cypress/action.yml
vendored
5
.github/actions/prepare-cypress/action.yml
vendored
@@ -15,11 +15,10 @@ outputs:
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Install Chrome v111
|
||||
- name: Install Chrome v144
|
||||
uses: browser-actions/setup-chrome@v2
|
||||
with:
|
||||
# https://chromium.cypress.io/linux/stable/111.0.5563.146
|
||||
chrome-version: 1097615
|
||||
chrome-version: 144
|
||||
id: setup-chrome
|
||||
|
||||
- name: Get cache keys
|
||||
|
||||
77
.github/actions/sdk-bundle-size-check/action.yml
vendored
Normal file
77
.github/actions/sdk-bundle-size-check/action.yml
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
name: Embedding SDK Bundle Size Check
|
||||
description: |
|
||||
Downloads the current branch's uberjar and builds the base ref's
|
||||
embedding-sdk.js from source, compares their sizes,
|
||||
and sets output if the size change exceeds the threshold.
|
||||
|
||||
inputs:
|
||||
edition:
|
||||
description: 'Metabase edition (ee or oss)'
|
||||
required: true
|
||||
base-ref-commit:
|
||||
description: 'Commit SHA of the base ref (e.g. merge base).'
|
||||
required: true
|
||||
threshold-percent:
|
||||
description: 'Percent threshold for bundle size change'
|
||||
required: true
|
||||
|
||||
outputs:
|
||||
status:
|
||||
description: "Bundle size status: increased, decreased, or stable"
|
||||
value: ${{ steps.compare.outputs.status }}
|
||||
size_change_percent:
|
||||
description: "size change percent (positive = increase, negative = decrease)"
|
||||
value: ${{ steps.compare.outputs.size_change_percent }}
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Prepare backend
|
||||
uses: ./.github/actions/prepare-backend
|
||||
- name: Prepare frontend
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
- name: Download current uberjar
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: metabase-${{ inputs.edition }}-${{ github.event.pull_request.head.sha || github.sha }}-uberjar
|
||||
path: temp/current-uberjar
|
||||
- name: Extract embedding-sdk.js from current uberjar
|
||||
shell: bash
|
||||
run: unzip -j temp/current-uberjar/target/uberjar/metabase.jar frontend_client/app/embedding-sdk.js -d temp/current-bundle
|
||||
- name: Checkout base ref
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.base-ref-commit }}
|
||||
path: temp/base-ref-src
|
||||
- name: Build base-ref embedding-sdk.js
|
||||
shell: bash
|
||||
run: |
|
||||
cd temp/base-ref-src
|
||||
bun install
|
||||
MB_EDITION=${{ inputs.edition }} bun run build-release
|
||||
- name: Compare sizes and set output
|
||||
id: compare
|
||||
shell: bash
|
||||
run: |
|
||||
current_size=$(wc -c < temp/current-bundle/embedding-sdk.js) || { echo "::error::Failed to read current bundle"; exit 1; }
|
||||
base_ref_size=$(wc -c < temp/base-ref-src/resources/frontend_client/app/embedding-sdk.js) || { echo "::error::Failed to read base-ref bundle"; exit 1; }
|
||||
[ "$current_size" -gt 0 ] && [ "$base_ref_size" -gt 0 ] || { echo "::error::Bundle file is empty"; exit 1; }
|
||||
|
||||
diff=$((current_size - base_ref_size))
|
||||
percent=$(( diff * 100 / base_ref_size ))
|
||||
|
||||
echo "Current size: $current_size bytes"
|
||||
echo "Base ref size: $base_ref_size bytes"
|
||||
echo "Difference: $diff bytes ($percent%)"
|
||||
|
||||
threshold="${{ inputs.threshold-percent }}"
|
||||
if [ "$percent" -gt "$threshold" ]; then
|
||||
status="increased"
|
||||
elif [ "$percent" -lt "$(( -threshold ))" ]; then
|
||||
status="decreased"
|
||||
else
|
||||
status="stable"
|
||||
fi
|
||||
|
||||
echo "status=$status" >> $GITHUB_OUTPUT
|
||||
echo "size_change_percent=$percent" >> $GITHUB_OUTPUT
|
||||
75
.github/copilot-instructions.md
vendored
75
.github/copilot-instructions.md
vendored
@@ -1,52 +1,55 @@
|
||||
# Metabase Developer Assistant Instructions
|
||||
|
||||
## Frontend Development
|
||||
For detailed coding standards and conventions, see `CLAUDE.md` and the skills in `.claude/skills/`.
|
||||
|
||||
### Repository Structure
|
||||
## Code Review Standards
|
||||
|
||||
```
|
||||
frontend/src/
|
||||
├── metabase/ # Main application components and pages
|
||||
├── metabase-lib/ # Query building and data modeling utilities
|
||||
├── metabase-types/ # TypeScript type definitions
|
||||
└── metabase/ui/ # Design system components
|
||||
Review focus areas: Security → Performance → Testing → Documentation (in priority order)
|
||||
|
||||
e2e/ # Cypress end-to-end tests
|
||||
frontend/test/ # Jest unit tests
|
||||
```
|
||||
When reviewing code, use these emoji prefixes to categorize feedback:
|
||||
- 🔒 Security concerns
|
||||
- ⚡ Performance opportunities
|
||||
- 🧹 Cleanup needs
|
||||
- 📚 Documentation gaps
|
||||
- 🚨 Blocking issues
|
||||
- 💭 Clarification questions
|
||||
|
||||
### Technology Stack
|
||||
### Security Critical Issues
|
||||
|
||||
- **Framework**: React 18 with TypeScript
|
||||
- **State**: Redux Toolkit
|
||||
- **Styling**: CSS Modules (preferred) > Emotion styled-components
|
||||
- **UI**: `metabase/ui` components built with Mantine v8
|
||||
- **Build**: Rspack (primary), Webpack (legacy)
|
||||
- **Testing**: Jest + React Testing Library, Cypress
|
||||
- Check for hardcoded secrets, API keys, or credentials
|
||||
- Look for SQL injection and XSS vulnerabilities
|
||||
- Verify proper input validation and sanitization
|
||||
- Review authentication and authorization logic
|
||||
- Ensure enterprise features use the plugin system (no enterprise code in OSS)
|
||||
|
||||
### Coding Standards
|
||||
### Performance Red Flags
|
||||
|
||||
#### Component Preferences
|
||||
- Identify N+1 database query problems
|
||||
- Spot inefficient loops and algorithmic issues
|
||||
- Check for memory leaks and resource cleanup
|
||||
- Review caching opportunities for expensive operations
|
||||
|
||||
- Prefer `metabase/ui`components over `metabase/common/components`
|
||||
- Use `.tsx` for components, `.ts` for utilities
|
||||
### Code Quality Essentials
|
||||
|
||||
#### Styling
|
||||
- Functions should be focused and appropriately sized
|
||||
- Use clear, descriptive naming conventions
|
||||
- Ensure proper error handling throughout
|
||||
- Remove dead code and unused imports
|
||||
|
||||
ALWAYS prefer Mantine style props,then CSS modules. DO NOT suggest styled components, they are deprecated.
|
||||
### Review Style
|
||||
|
||||
#### TypeScript Migration
|
||||
- Only flag actual issues worth mentioning
|
||||
- Do not post "looks good" comments or congratulate following conventions
|
||||
- Be specific and actionable in feedback
|
||||
- Explain the "why" behind recommendations
|
||||
- Ask clarifying questions when code intent is unclear
|
||||
|
||||
When heavily editing `.js`/`.jsx` files, create a separate PR to convert to TypeScript first, then implement changes.
|
||||
### Review Format
|
||||
|
||||
#### Enterprise Features
|
||||
- Condense feedback into as few comments as possible
|
||||
- Prefer a single summary comment over dozens of inline comments
|
||||
- Only use inline comments for critical issues that require immediate attention
|
||||
- For non-critical issues, link to the specific line from the summary instead
|
||||
- Delete all stale comments and summaries on each new push or CI run
|
||||
|
||||
Enterprise functionality MUST use the plugin system. It is very important to not expose enterprise code in the OSS version.
|
||||
|
||||
#### Testing Requirements
|
||||
|
||||
All PRs should include tests. Prefer Unit tests over E2E tests.
|
||||
|
||||
#### Localization
|
||||
|
||||
All user-facing strings MUST be localized using the ttag library. Localized strings should be complete phrases, do not concatenate a few separately localized strings. You should add context to strings where the meaning of the string might not be obvious in isolation: e.g. "Home" might have different words in some languages depending on whether you're talking about a dwelling or the landing page for a website.
|
||||
Language-specific rules are in `.github/instructions/*.instructions.md`.
|
||||
|
||||
12
.github/file-paths.yaml
vendored
12
.github/file-paths.yaml
vendored
@@ -199,3 +199,15 @@ cljs:
|
||||
- *shared_specs
|
||||
- ".github/workflows/cljs.yml"
|
||||
- "shadow-cljs.edn"
|
||||
|
||||
openapi:
|
||||
- ".github/workflows/openapi.yml"
|
||||
- "src/metabase/api/**"
|
||||
- "src/metabase/api_routes/**"
|
||||
- "src/metabase/*_rest/api.clj"
|
||||
- "src/metabase/*/api.clj"
|
||||
- "src/metabase/*/api/*.clj"
|
||||
- "enterprise/backend/src/metabase_enterprise/**/api.clj"
|
||||
- "enterprise/backend/src/metabase_enterprise/**/api/*.clj"
|
||||
- "resources/openapi/**"
|
||||
- "package.json"
|
||||
|
||||
9
.github/instructions/clojure.instructions.md
vendored
Normal file
9
.github/instructions/clojure.instructions.md
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
applyTo: "**/*.{clj,cljs,cljc}"
|
||||
---
|
||||
|
||||
# Clojure/ClojureScript Code Review
|
||||
|
||||
When reviewing Clojure or ClojureScript code, apply the guidelines in these files:
|
||||
- `.claude/skills/_shared/clojure-style-guide.md`
|
||||
- `.claude/skills/clojure-review/SKILL.md`
|
||||
9
.github/instructions/docs.instructions.md
vendored
Normal file
9
.github/instructions/docs.instructions.md
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
applyTo: "docs/**/*.md"
|
||||
---
|
||||
|
||||
# Documentation Review
|
||||
|
||||
When reviewing documentation, apply the guidelines in these files:
|
||||
- `.claude/skills/_shared/metabase-style-guide.md`
|
||||
- `.claude/skills/docs-review/SKILL.md`
|
||||
11
.github/instructions/typescript.instructions.md
vendored
Normal file
11
.github/instructions/typescript.instructions.md
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
applyTo: "**/*.{ts,tsx,js,jsx}"
|
||||
---
|
||||
|
||||
# TypeScript/JavaScript Code Review
|
||||
|
||||
When reviewing TypeScript or JavaScript code, apply the guidelines in these files:
|
||||
|
||||
- `.claude/skills/typescript-review/SKILL.md`
|
||||
- `docs/developers-guide/frontend.md`
|
||||
- `frontend/CLAUDE.md`
|
||||
3
.github/workflows/drivers.yml
vendored
3
.github/workflows/drivers.yml
vendored
@@ -77,6 +77,9 @@ jobs:
|
||||
--pr-labels="${{ join(github.event.pull_request.labels.*.name, ',') }}"
|
||||
--skip="${{ inputs.skip }}"
|
||||
)
|
||||
echo "Running:"
|
||||
echo "./bin/mage -driver-decisions ${MAGE_ARGS[@]}"
|
||||
echo ""
|
||||
# First call: show human-readable output for debugging
|
||||
./bin/mage -driver-decisions "${MAGE_ARGS[@]}"
|
||||
# Second call: output only key=value lines for GITHUB_OUTPUT
|
||||
|
||||
5
.github/workflows/e2e-test.yml
vendored
5
.github/workflows/e2e-test.yml
vendored
@@ -45,8 +45,9 @@ jobs:
|
||||
name: e2e-tests-${{ inputs.name }}-${{ inputs.edition }}
|
||||
env:
|
||||
MB_EDITION: ${{ inputs.edition }}
|
||||
# Any env starting with `CYPRESS_` will be available to all Cypress tests via `Cypress.env()`
|
||||
# Example: you can get `CYPRESS_FOO` with `Cypress.env("FOO")`
|
||||
# Sensitive env vars (tokens, secrets) starting with `CYPRESS_` are accessed via async `cy.env()`
|
||||
# Example: `cy.env(["FOO"]).then(({ FOO }) => ...)`
|
||||
# For non-sensitive config, use `Cypress.expose()` in cypress config instead (synchronous)
|
||||
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ secrets.STAGING_MB_ALL_FEATURES_TOKEN }}
|
||||
CYPRESS_MB_STARTER_CLOUD_TOKEN: ${{ secrets.STAGING_MB_STARTER_CLOUD_TOKEN }}
|
||||
CYPRESS_MB_PRO_CLOUD_TOKEN: ${{ secrets.STAGING_MB_PRO_CLOUD_TOKEN }}
|
||||
|
||||
@@ -19,8 +19,9 @@ jobs:
|
||||
react-version: [18, 19]
|
||||
fail-fast: false
|
||||
env:
|
||||
# Any env starting with `CYPRESS_` will be available to all Cypress tests via `Cypress.env()`
|
||||
# Example: you can get `CYPRESS_FOO` with `Cypress.env("FOO")`
|
||||
# Sensitive env vars (tokens, secrets) starting with `CYPRESS_` are accessed via async `cy.env()`
|
||||
# Example: `cy.env(["FOO"]).then(({ FOO }) => ...)`
|
||||
# For non-sensitive config, use `Cypress.expose()` in cypress config instead (synchronous)
|
||||
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ secrets.STAGING_MB_ALL_FEATURES_TOKEN }}
|
||||
CYPRESS_MB_STARTER_CLOUD_TOKEN: ${{ secrets.STAGING_MB_STARTER_CLOUD_TOKEN }}
|
||||
CYPRESS_MB_PRO_CLOUD_TOKEN: ${{ secrets.STAGING_MB_PRO_CLOUD_TOKEN }}
|
||||
@@ -155,8 +156,9 @@ jobs:
|
||||
env:
|
||||
MB_RUN_MODE: "e2e"
|
||||
METASTORE_DEV_SERVER_URL: ${{ secrets.METASTORE_DEV_SERVER_URL }}
|
||||
# Any env starting with `CYPRESS_` will be available to all Cypress tests via `Cypress.env()`
|
||||
# Example: you can get `CYPRESS_FOO` with `Cypress.env("FOO")`
|
||||
# Sensitive env vars (tokens, secrets) starting with `CYPRESS_` are accessed via async `cy.env()`
|
||||
# Example: `cy.env(["FOO"]).then(({ FOO }) => ...)`
|
||||
# For non-sensitive config, use `Cypress.expose()` in cypress config instead (synchronous)
|
||||
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ secrets.STAGING_MB_ALL_FEATURES_TOKEN }}
|
||||
CYPRESS_MB_STARTER_CLOUD_TOKEN: ${{ secrets.STAGING_MB_STARTER_CLOUD_TOKEN }}
|
||||
CYPRESS_MB_PRO_CLOUD_TOKEN: ${{ secrets.STAGING_MB_PRO_CLOUD_TOKEN }}
|
||||
|
||||
48
.github/workflows/embedding-sdk.yml
vendored
48
.github/workflows/embedding-sdk.yml
vendored
@@ -23,6 +23,10 @@ on:
|
||||
description: "Whether to run host and sample apps compatibility tests."
|
||||
required: true
|
||||
type: boolean
|
||||
bundle-size-check:
|
||||
description: "Whether to run bundle size check."
|
||||
required: true
|
||||
type: boolean
|
||||
skip:
|
||||
description: Whether to skip running tests due to one of the artifacts failing to build.
|
||||
required: true
|
||||
@@ -124,8 +128,52 @@ jobs:
|
||||
uses: ./.github/workflows/embedding-sdk-host-sample-apps-tests.yml
|
||||
secrets: inherit
|
||||
|
||||
embedding-sdk-bundle-size-check:
|
||||
if: inputs.bundle-size-check
|
||||
continue-on-error: true
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 20
|
||||
env:
|
||||
BUNDLE_SIZE_THRESHOLD_PERCENT: 2
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/sdk-bundle-size-check
|
||||
id: compare
|
||||
with:
|
||||
edition: ee
|
||||
base-ref-commit: ${{ github.event.pull_request.base.sha }}
|
||||
threshold-percent: ${{ env.BUNDLE_SIZE_THRESHOLD_PERCENT }}
|
||||
|
||||
- name: Comment on PR if bundle size has increased
|
||||
uses: ./.github/actions/notify-pull-request
|
||||
if: success() && steps.compare.outputs.status == 'increased'
|
||||
with:
|
||||
remove-previous-comment: true
|
||||
add-new-comment: true
|
||||
message: "⚠️ Embedding SDK bundle size increased by ${{ steps.compare.outputs.size_change_percent }}% compared to the ${{ github.base_ref }} branch (threshold: ${{ env.BUNDLE_SIZE_THRESHOLD_PERCENT }}%). Please contact Embedding team."
|
||||
include-log: false
|
||||
|
||||
- name: Send notification to Slack via Zapier
|
||||
if: success() && steps.compare.outputs.status == 'increased'
|
||||
shell: bash
|
||||
run: |
|
||||
curl -s -X POST "${{ secrets.EMBEDDING_ZAPIER_WEBHOOK_URL }}" \
|
||||
-d '⚠️ Embedding SDK bundle size increased by ${{ steps.compare.outputs.size_change_percent }}% (threshold: ${{ env.BUNDLE_SIZE_THRESHOLD_PERCENT }}%)
|
||||
|
||||
PR #${{ github.event.pull_request.number }} ${{ github.event.pull_request.title }} (${{ github.event.pull_request.html_url }})'
|
||||
|
||||
- name: Comment on PR if bundle size has decreased
|
||||
uses: ./.github/actions/notify-pull-request
|
||||
if: success() && steps.compare.outputs.status == 'decreased'
|
||||
with:
|
||||
remove-previous-comment: true
|
||||
add-new-comment: true
|
||||
message: "🎉 Embedding SDK bundle size decreased by ${{ steps.compare.outputs.size_change_percent }}% compared to the ${{ github.base_ref }} branch. Nice work!"
|
||||
include-log: false
|
||||
|
||||
embedding-sdk-tests-result:
|
||||
needs:
|
||||
- embedding-sdk-bundle-size-check
|
||||
- embedding-sdk-cli-snippets-type-check
|
||||
- embedding-sdk-docs-snippets-type-check
|
||||
- embedding-sdk-api-documentation-generation-validation
|
||||
|
||||
46
.github/workflows/openapi.yml
vendored
Normal file
46
.github/workflows/openapi.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
name: Validate OpenAPI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "master"
|
||||
- "release-**"
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
files-changed:
|
||||
name: Check which files changed
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 3
|
||||
outputs:
|
||||
openapi: ${{ steps.changes.outputs.openapi }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test which files changed
|
||||
uses: dorny/paths-filter@v3.0.0
|
||||
id: changes
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
filters: .github/file-paths.yaml
|
||||
|
||||
validate-openapi:
|
||||
needs: files-changed
|
||||
if: needs.files-changed.outputs.openapi == 'true'
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
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: Generate OpenAPI spec
|
||||
run: bun run openapi:generate
|
||||
- name: Validate OpenAPI spec
|
||||
run: bun run openapi:lint
|
||||
- name: Split OpenAPI spec
|
||||
run: bun run openapi:split
|
||||
4
.github/workflows/pr-env-close-unlabel.yml
vendored
4
.github/workflows/pr-env-close-unlabel.yml
vendored
@@ -5,8 +5,8 @@ on:
|
||||
types: [ closed, unlabeled ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
pr-env:
|
||||
|
||||
29
.github/workflows/pr-env-destroy-manual.yml
vendored
Normal file
29
.github/workflows/pr-env-destroy-manual.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
name: PR Env Destroy (Manual)
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr_number:
|
||||
description: 'PR number to destroy environment for'
|
||||
required: true
|
||||
type: string
|
||||
self_hosting:
|
||||
description: 'Self-hosting stack'
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
approve:
|
||||
runs-on: ubuntu-latest
|
||||
environment: pr-env-admin
|
||||
steps:
|
||||
- name: Approved
|
||||
run: echo "Destroying PR environment for PR #${{ inputs.pr_number }}"
|
||||
|
||||
destroy:
|
||||
needs: approve
|
||||
uses: ./.github/workflows/pr-env-destroy.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
self_hosting: ${{ inputs.self_hosting }}
|
||||
pr_number: ${{ inputs.pr_number }}
|
||||
17
.github/workflows/pr-env-destroy.yml
vendored
17
.github/workflows/pr-env-destroy.yml
vendored
@@ -9,6 +9,13 @@ on:
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
pr_number:
|
||||
description: 'PR number (required for manual dispatch)'
|
||||
required: false
|
||||
type: string
|
||||
|
||||
env:
|
||||
PR_NUMBER: ${{ inputs.pr_number || github.event.number }}
|
||||
|
||||
jobs:
|
||||
destroy_pr:
|
||||
@@ -62,8 +69,8 @@ jobs:
|
||||
- name: Destroy PR Review ENV
|
||||
if: ${{ !inputs.self_hosting }}
|
||||
run: |
|
||||
kubectl delete metabase -n hosting-pr${{ github.event.number }} hosting-pr${{ github.event.number }}
|
||||
kubectl delete ns hosting-pr${{ github.event.number }}
|
||||
kubectl delete metabase -n hosting-pr${{ env.PR_NUMBER }} hosting-pr${{ env.PR_NUMBER }}
|
||||
kubectl delete ns hosting-pr${{ env.PR_NUMBER }}
|
||||
- name: Setup Helm
|
||||
if: ${{ inputs.self_hosting }}
|
||||
uses: azure/setup-helm@v4
|
||||
@@ -72,8 +79,8 @@ jobs:
|
||||
- name: Destroy PR Review ENV (self-hosting)
|
||||
if: ${{ inputs.self_hosting }}
|
||||
run: |
|
||||
helm uninstall hosting-pr${{ github.event.number }} --namespace hosting-pr${{ github.event.number }} || true
|
||||
kubectl delete ns hosting-pr${{ github.event.number }} || true
|
||||
helm uninstall hosting-pr${{ env.PR_NUMBER }} --namespace hosting-pr${{ env.PR_NUMBER }} || true
|
||||
kubectl delete ns hosting-pr${{ env.PR_NUMBER }} || true
|
||||
- name: Setup psql client
|
||||
run: |
|
||||
sudo apt-get update
|
||||
@@ -85,4 +92,4 @@ jobs:
|
||||
-h ${{ secrets.PR_ENV_DB_HOST }} \
|
||||
-U ${{ secrets.PR_ENV_DB_USER }} \
|
||||
-d ${{ secrets.PR_ENV_DB_NAME }} \
|
||||
-c "DROP DATABASE IF EXISTS hosting_pr${{ github.event.number }};"
|
||||
-c "DROP DATABASE IF EXISTS hosting_pr${{ env.PR_NUMBER }};"
|
||||
|
||||
4
.github/workflows/release-patch.yml
vendored
4
.github/workflows/release-patch.yml
vendored
@@ -13,8 +13,8 @@ on:
|
||||
type: string
|
||||
required: false
|
||||
default: ''
|
||||
# schedule:
|
||||
# - cron: '0 21 * * 1-5' # every weekday at 4 pm EST/5 pm EDT
|
||||
schedule:
|
||||
- cron: '0 21 * * 1-5' # every weekday at 4 pm EST/5 pm EDT
|
||||
|
||||
jobs:
|
||||
auto-patch-trigger:
|
||||
|
||||
4
.github/workflows/run-tests.yml
vendored
4
.github/workflows/run-tests.yml
vendored
@@ -103,7 +103,8 @@ jobs:
|
||||
(needs.files-changed.outputs.embedding_sdk_components == 'true' ||
|
||||
needs.files-changed.outputs.frontend_sources == 'true' ||
|
||||
needs.files-changed.outputs.embedding_documentation == 'true' ||
|
||||
needs.files-changed.outputs.embedding_sdk_ci == 'true')
|
||||
needs.files-changed.outputs.embedding_sdk_ci == 'true' ||
|
||||
needs.files-changed.outputs.embedding_sdk_host_sample_apps == 'true')
|
||||
uses: ./.github/workflows/embedding-sdk-package-build.yml
|
||||
secrets: inherit
|
||||
|
||||
@@ -113,6 +114,7 @@ jobs:
|
||||
uses: ./.github/workflows/embedding-sdk.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
bundle-size-check: ${{ github.event_name == 'pull_request' && needs.uberjar.result == 'success' && needs.files-changed.outputs.frontend_sources == 'true' }}
|
||||
cli-snippets-type-check: ${{ needs.files-changed.outputs.frontend_sources == 'true' }}
|
||||
docs-snippets-type-check: ${{ needs.files-changed.outputs.embedding_documentation == 'true' }}
|
||||
documentation-validation: ${{ needs.files-changed.outputs.frontend_sources == 'true' }}
|
||||
|
||||
50
.github/workflows/translation-context.yml
vendored
Normal file
50
.github/workflows/translation-context.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
name: Crowdin Context Harvester
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "30 6 * * 2" # Tuesday at 06:30 UTC, one hour after translation-update.yml
|
||||
|
||||
jobs:
|
||||
harvest:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
- name: Harvest and write AI context to Crowdin
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_TOKEN }}
|
||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||
OPENAI_KEY: ${{ secrets.CROWDIN_OPENAI_API_KEY }}
|
||||
run: |
|
||||
npx --yes crowdin-context-harvester@latest harvest \
|
||||
--project="$CROWDIN_PROJECT_ID" \
|
||||
--ai="openai" \
|
||||
--model="gpt-5.1" \
|
||||
--croql='not (context contains "✨ AI Context")' \
|
||||
--output="crowdin" \
|
||||
--concurrency=10
|
||||
|
||||
# We only want to trigger pre-translations, not wait for them to finish running upstream.
|
||||
# Weirdly, Crowdin keeps this step running as long as it runs pre-translations on their servers.
|
||||
# Hence, the three minute timeout and continue-on-error.
|
||||
- name: Trigger pre-translations
|
||||
timeout-minutes: 3
|
||||
uses: crowdin/github-action@v2
|
||||
# https://crowdin.github.io/crowdin-cli/commands/crowdin-pre-translate
|
||||
with:
|
||||
command: "pre-translate"
|
||||
command_args: >-
|
||||
--method ai
|
||||
--ai-prompt ${{ secrets.CROWDIN_AI_PROMPT_ID }}
|
||||
--translate-untranslated-only
|
||||
--token ${{ secrets.CROWDIN_TOKEN }}
|
||||
--project-id ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||
--config=locales/crowdin.yml
|
||||
--no-progress
|
||||
continue-on-error: true
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -16,6 +16,7 @@
|
||||
.nrepl-port
|
||||
.portal
|
||||
.vscode
|
||||
/.zed
|
||||
/*.h2.db
|
||||
/*.lock.db
|
||||
/*.mv.db
|
||||
@@ -43,6 +44,7 @@ cypress.env.json
|
||||
/docs/**/package.json
|
||||
/docs/**/yarn.lock
|
||||
/docs/embedding/sdk/api/
|
||||
/docs/embedding/eajs/snippets/
|
||||
/frontend/test/snapshots/*
|
||||
/e2e/snapshots/*
|
||||
/e2e/tmp/*
|
||||
@@ -170,4 +172,3 @@ CLAUDE.local.md
|
||||
/resources/python-sources/sqlglot-*.dist-info/
|
||||
/resources/python-sources/.lock
|
||||
__pycache__/
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 19 KiB |
151
.typedoc/typedoc-plugin-props-doc-transform.js
Normal file
151
.typedoc/typedoc-plugin-props-doc-transform.js
Normal file
@@ -0,0 +1,151 @@
|
||||
import {
|
||||
ArrayType,
|
||||
Comment,
|
||||
Converter,
|
||||
IntrinsicType,
|
||||
LiteralType,
|
||||
ReflectionFlag,
|
||||
ReflectionKind,
|
||||
UnionType,
|
||||
} from "typedoc";
|
||||
|
||||
/**
|
||||
* Check if a UnionType has ALL members as string LiteralTypes.
|
||||
*/
|
||||
function isAllStringLiteralUnion(type) {
|
||||
if (!(type instanceof UnionType)) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
type.types.length > 0 &&
|
||||
type.types.every(
|
||||
(t) => t instanceof LiteralType && typeof t.value === "string",
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the text content from a Comment tag's content array.
|
||||
*/
|
||||
function getTagText(tag) {
|
||||
if (!tag.content || tag.content.length === 0) {
|
||||
return "";
|
||||
}
|
||||
return tag.content.map((part) => part.text).join("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform @remarks text into a human-readable sentence.
|
||||
*
|
||||
* "Guest embed" → "Available in Guest embed."
|
||||
* "Pro/Enterprise" → "Available in Pro/Enterprise."
|
||||
* "Pro/Enterprise, Guest embed" → "Available in Pro/Enterprise and Guest embed."
|
||||
*/
|
||||
function transformRemarks(text) {
|
||||
const trimmed = text.trim();
|
||||
|
||||
if (trimmed.includes(",")) {
|
||||
const parts = trimmed.split(",").map((s) => s.trim());
|
||||
const last = parts.pop();
|
||||
return `Available in ${parts.join(", ")} and ${last}.`;
|
||||
}
|
||||
|
||||
return `Available in ${trimmed}.`;
|
||||
}
|
||||
|
||||
/**
|
||||
* A TypeDoc plugin that transforms property reflections before markdown
|
||||
* rendering to produce cleaner props documentation tables.
|
||||
*
|
||||
* For each property on interfaces/type aliases:
|
||||
* 1. Moves optional `?` markers into the description as "Optional."
|
||||
* 2. Extracts @defaultValue tags into the description as "Default: `value`."
|
||||
* 3. Replaces all-string-literal union types with `string` and lists possible values
|
||||
* 4. Transforms @remarks tags into human-readable availability notes
|
||||
*/
|
||||
export function load(app) {
|
||||
app.converter.on(Converter.EVENT_RESOLVE_END, (context) => {
|
||||
const project = context.project;
|
||||
|
||||
for (const reflection of project.getReflectionsByKind(
|
||||
ReflectionKind.Property,
|
||||
)) {
|
||||
const prefix = [];
|
||||
|
||||
// 1. Optional: move ? into description
|
||||
if (reflection.flags.isOptional) {
|
||||
prefix.push("Optional");
|
||||
reflection.setFlag(ReflectionFlag.Optional, false);
|
||||
}
|
||||
|
||||
// 2. Default value: extract @defaultValue tag
|
||||
if (reflection.comment) {
|
||||
const defaultTag = reflection.comment.getTag("@defaultValue");
|
||||
if (defaultTag) {
|
||||
const defaultText = getTagText(defaultTag).trim();
|
||||
|
||||
// If the value already contains backticks, use as-is
|
||||
if (defaultText.includes("`")) {
|
||||
prefix.push(`Default: ${defaultText}`);
|
||||
} else {
|
||||
prefix.push(`Default: \`${defaultText}\``);
|
||||
}
|
||||
|
||||
reflection.comment.removeTags("@defaultValue");
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Complex union types: simplify all-string-literal unions
|
||||
let unionType = null;
|
||||
|
||||
if (isAllStringLiteralUnion(reflection.type)) {
|
||||
unionType = reflection.type;
|
||||
reflection.type = new IntrinsicType("string");
|
||||
} else if (
|
||||
reflection.type instanceof ArrayType &&
|
||||
isAllStringLiteralUnion(reflection.type.elementType)
|
||||
) {
|
||||
unionType = reflection.type.elementType;
|
||||
reflection.type = new ArrayType(new IntrinsicType("string"));
|
||||
}
|
||||
|
||||
if (unionType) {
|
||||
const values = unionType.types
|
||||
.map((t) => `\`"${t.value}"\``)
|
||||
.join(", ");
|
||||
prefix.push(`Possible values: ${values}`);
|
||||
}
|
||||
|
||||
// 4. Remarks: transform into availability notes
|
||||
if (reflection.comment) {
|
||||
const remarksTag = reflection.comment.getTag("@remarks");
|
||||
if (remarksTag) {
|
||||
const remarksText = getTagText(remarksTag);
|
||||
if (remarksText.trim()) {
|
||||
prefix.push(transformRemarks(remarksText));
|
||||
}
|
||||
reflection.comment.removeTags("@remarks");
|
||||
}
|
||||
}
|
||||
|
||||
// Assemble prefix and prepend to comment summary
|
||||
if (prefix.length > 0) {
|
||||
if (!reflection.comment) {
|
||||
reflection.comment = new Comment();
|
||||
}
|
||||
|
||||
const prefixText = ["---", ...prefix].join("<br>");
|
||||
const existingSummary = reflection.comment.summary || [];
|
||||
|
||||
if (existingSummary.length > 0) {
|
||||
reflection.comment.summary = [
|
||||
...existingSummary,
|
||||
{ kind: "text", text: "<br>" + prefixText },
|
||||
];
|
||||
} else {
|
||||
reflection.comment.summary = [{ kind: "text", text: prefixText }];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -5,6 +5,7 @@ const eajsMarkdownConfig = {
|
||||
plugin: [
|
||||
"typedoc-plugin-mdn-links",
|
||||
"typedoc-plugin-markdown",
|
||||
"./typedoc-plugin-props-doc-transform.js",
|
||||
"./typedoc-plugin-markdown-prepare-for-embedding.js",
|
||||
],
|
||||
entryPoints: [
|
||||
@@ -32,7 +33,7 @@ const eajsMarkdownConfig = {
|
||||
typeAliasPropertiesFormat: "table",
|
||||
typeDeclarationFormat: "table",
|
||||
tableColumnSettings: {
|
||||
hideDefaults: false,
|
||||
hideDefaults: true,
|
||||
hideInherited: true,
|
||||
hideModifiers: true,
|
||||
hideOverrides: true,
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"Build step to install Python dependencies (sqlglot) for GraalVM Python."
|
||||
(:require
|
||||
[clojure.java.io :as io]
|
||||
[clojure.string :as str]
|
||||
[environ.core :as env]
|
||||
[metabuild-common.core :as u]))
|
||||
|
||||
@@ -14,14 +13,11 @@
|
||||
|
||||
(def ^:private sqlglot-version
|
||||
"Pinned sqlglot version for reproducible builds.
|
||||
Read from .sqlglot-version file (single source of truth for both dev and build)."
|
||||
(->> (io/file u/project-root-directory "resources/python-sources/.sqlglot-version")
|
||||
Read from pyproject.toml (single source of truth for both dev and build)."
|
||||
(->> (io/file u/project-root-directory "resources/python-sources/pyproject.toml")
|
||||
slurp
|
||||
;; handle # comments in the version file
|
||||
str/split-lines
|
||||
(remove #(str/starts-with? % "#"))
|
||||
(str/join "\n")
|
||||
str/trim))
|
||||
(re-find #"\"sqlglot==([^\"]+)\"")
|
||||
second))
|
||||
|
||||
(defn build-python-deps!
|
||||
"Install sqlglot to resources/python-sources for bundling into uberjar.
|
||||
@@ -36,7 +32,7 @@
|
||||
(u/step "Install Python dependencies (sqlglot)"
|
||||
(u/create-directory-unless-exists! python-sources-dir)
|
||||
(u/sh {:dir u/project-root-directory}
|
||||
"uv" "pip" "install" (str "sqlglot==" sqlglot-version)
|
||||
"uv" "pip" "install" "-r" (u/filename python-sources-dir "pyproject.toml")
|
||||
"--target" python-sources-dir
|
||||
"--no-compile")
|
||||
(u/announce "sqlglot %s installed to %s" sqlglot-version python-sources-dir))))
|
||||
|
||||
@@ -14,7 +14,7 @@ fi
|
||||
# Generate the current OpenAPI spec from Malli schemas using write-openapi-spec-to-file!
|
||||
# This creates $OPENAPI_SPEC directly
|
||||
# Using the generate-openapi-spec command from metabase.cmd.core
|
||||
bun run generate-openapi
|
||||
bun run openapi:generate
|
||||
ret=$?
|
||||
|
||||
if [[ $ret -ne 0 ]]; then
|
||||
@@ -82,7 +82,7 @@ if ! git diff --exit-code "$OPENAPI_SPEC" >/dev/null 2>&1; then
|
||||
echo ""
|
||||
echo "To fix this:"
|
||||
echo " 1. Pull the latest changes from master: git pull origin master"
|
||||
echo " 2. Run 'bun run generate-openapi' to update $OPENAPI_SPEC"
|
||||
echo " 2. Run 'bun run openapi:generate' to update $OPENAPI_SPEC"
|
||||
echo " 3. Commit the updated openapi.json file"
|
||||
echo ""
|
||||
echo "Differences:"
|
||||
|
||||
5
deps.edn
5
deps.edn
@@ -84,7 +84,7 @@
|
||||
io.github.eerohele/pp {#_#_:git/tag "2024-11-13.77" ; super fast pretty-printing library
|
||||
:git/sha "ffc4edc482c057cd9412c62f018a41e9140c618b"
|
||||
:git/url "https://github.com/eerohele/pp"}
|
||||
io.github.metabase/macaw {:mvn/version "0.2.33"} ; Parse native SQL queries
|
||||
io.github.metabase/macaw {:mvn/version "0.2.36"} ; Parse native SQL queries
|
||||
io.netty/netty-codec-http {:mvn/version "4.2.9.Final"} ; override of transitive dep from aws s3
|
||||
io.prometheus/simpleclient_hotspot {:mvn/version "0.16.0"} ; prometheus jvm collector
|
||||
junegunn/grouper {:mvn/version "0.1.1"} ; Batch processing helper
|
||||
@@ -617,6 +617,9 @@
|
||||
:implicit-dependencies
|
||||
;; too many false positives for now
|
||||
:unused-ret-vals
|
||||
;; hard to clear on for lib.util.match macros, and disabling the linter just there
|
||||
;; doesn't seem to work
|
||||
:unused-meta-on-macro
|
||||
;; Kondo lints this for us anyway, and this isn't as easy to configure.
|
||||
:wrong-arity
|
||||
;; Kondo lints this for us anyway, and this isn't as easy to configure.
|
||||
|
||||
113
dev/src/dev/api/preview.clj
Normal file
113
dev/src/dev/api/preview.clj
Normal file
@@ -0,0 +1,113 @@
|
||||
(ns dev.api.preview
|
||||
"Dev-only endpoints for previewing rendered pulse cards and dashboards.
|
||||
These were previously at `/api/pulse/preview_*` but have been moved here
|
||||
since they are only useful for development and testing."
|
||||
(:require
|
||||
[hiccup.core :refer [html]]
|
||||
[hiccup.page :refer [html5]]
|
||||
[metabase.api.common :as api]
|
||||
[metabase.api.macros :as api.macros]
|
||||
[metabase.api.routes.common]
|
||||
[metabase.channel.render.core :as channel.render]
|
||||
[metabase.channel.urls :as urls]
|
||||
[metabase.query-processor :as qp]
|
||||
[metabase.query-processor.middleware.permissions :as qp.perms]
|
||||
[metabase.util.malli.schema :as ms])
|
||||
(:import
|
||||
(java.io ByteArrayInputStream)))
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
|
||||
(defn- pulse-card-query-results
|
||||
{:arglists '([card])}
|
||||
[{query :dataset_query, card-id :id}]
|
||||
(binding [qp.perms/*card-id* card-id]
|
||||
(qp/process-query
|
||||
(qp/userland-query
|
||||
(assoc query
|
||||
:middleware {:process-viz-settings? true
|
||||
:js-int-to-string? false})
|
||||
{:executed-by api/*current-user-id*
|
||||
:context :pulse
|
||||
:card-id card-id}))))
|
||||
|
||||
(api.macros/defendpoint :get "/preview-card/:id"
|
||||
"Get HTML rendering of a Card with `id`."
|
||||
[{:keys [id]} :- [:map
|
||||
[:id ms/PositiveInt]]]
|
||||
(let [card (api/read-check :model/Card id)
|
||||
result (pulse-card-query-results card)]
|
||||
{:status 200
|
||||
:body (html5
|
||||
[:html
|
||||
[:body {:style "margin: 0;"}
|
||||
(channel.render/render-pulse-card-for-display (channel.render/defaulted-timezone card)
|
||||
card
|
||||
result
|
||||
{:channel.render/include-title? true, :channel.render/include-buttons? true})]])}))
|
||||
|
||||
(api.macros/defendpoint :get "/preview-dashboard/:id"
|
||||
"Get HTML rendering of a Dashboard with `id`.
|
||||
|
||||
This endpoint relies on a custom middleware defined in `metabase.channel.render.core/style-tag-nonce-middleware` to
|
||||
allow the style tag to render properly, given our Content Security Policy setup."
|
||||
[{:keys [id]} :- [:map
|
||||
[:id ms/PositiveInt]]]
|
||||
(api/read-check :model/Dashboard id)
|
||||
{:status 200
|
||||
:headers {"Content-Type" "text/html"}
|
||||
:body (channel.render/style-tag-from-inline-styles
|
||||
(html5
|
||||
[:head
|
||||
[:meta {:charset "utf-8"}]
|
||||
[:link {:nonce "%NONCE%" ;; this will be str/replaced by 'style-tag-nonce-middleware
|
||||
:rel "stylesheet"
|
||||
:href "https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,100;0,300;0,400;0,700;0,900;1,100;1,300;1,400;1,700;1,900&display=swap"}]]
|
||||
[:body [:h2 (format "Backend Artifacts Preview for Dashboard %s" id)]
|
||||
(channel.render/render-dashboard-to-html id)]))})
|
||||
|
||||
(api.macros/defendpoint :get "/preview-card-info/:id"
|
||||
"Get JSON object containing HTML rendering of a Card with `id` and other information."
|
||||
[{:keys [id]} :- [:map
|
||||
[:id ms/PositiveInt]]]
|
||||
(let [card (api/read-check :model/Card id)
|
||||
result (pulse-card-query-results card)
|
||||
data (:data result)
|
||||
card-type (channel.render/detect-pulse-chart-type card nil data)
|
||||
card-html (html (channel.render/render-pulse-card-for-display (channel.render/defaulted-timezone card)
|
||||
card
|
||||
result
|
||||
{:channel.render/include-title? true}))]
|
||||
{:id id
|
||||
:pulse_card_type card-type
|
||||
:pulse_card_html card-html
|
||||
:pulse_card_name (:name card)
|
||||
:pulse_card_url (urls/card-url (:id card))
|
||||
:row_count (:row_count result)
|
||||
:col_count (count (:cols (:data result)))}))
|
||||
|
||||
(def ^:private preview-card-width 400)
|
||||
|
||||
(api.macros/defendpoint :get "/preview-card-png/:id"
|
||||
"Get PNG rendering of a Card with `id`. Optionally specify `width` as a query parameter."
|
||||
[{:keys [id]} :- [:map
|
||||
[:id ms/PositiveInt]]
|
||||
{:keys [width]} :- [:map
|
||||
[:width {:optional true} [:maybe ms/PositiveInt]]]]
|
||||
(let [card (api/read-check :model/Card id)
|
||||
result (pulse-card-query-results card)
|
||||
width (or width preview-card-width)
|
||||
ba (channel.render/render-pulse-card-to-png (channel.render/defaulted-timezone card)
|
||||
card
|
||||
result
|
||||
width
|
||||
{:channel.render/include-title? true})]
|
||||
{:status 200, :headers {"Content-Type" "image/png"}, :body (ByteArrayInputStream. ba)}))
|
||||
|
||||
(def ^:private ^{:arglists '([handler])} style-nonce-middleware
|
||||
(metabase.api.routes.common/wrap-middleware-for-open-api-spec-generation
|
||||
(partial channel.render/style-tag-nonce-middleware "/dev/preview/preview-dashboard")))
|
||||
|
||||
(def ^{:arglists '([request respond raise])} routes
|
||||
"`/dev/preview` endpoints."
|
||||
(api.macros/ns-handler *ns* style-nonce-middleware))
|
||||
@@ -1,12 +1,15 @@
|
||||
(ns dev.api.routes
|
||||
(:require [dev.api.prototype]
|
||||
(:require [dev.api.preview]
|
||||
[dev.api.prototype]
|
||||
[metabase.api.util.handlers :as handlers]))
|
||||
|
||||
(comment
|
||||
dev.api.preview/keep-me
|
||||
dev.api.prototype/keep-me)
|
||||
|
||||
(def ^:private dev-routes-map
|
||||
{"/prototype" 'dev.api.prototype})
|
||||
{"/preview" 'dev.api.preview
|
||||
"/prototype" 'dev.api.prototype})
|
||||
|
||||
(def ^{:arglists '([request respond raise])} routes
|
||||
;; This map will be merged at the top level, so /dev/prototype is available.
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
;; run a query with debugging enabled
|
||||
(binding [metabase.query-processor.debug/*debug* true]
|
||||
(metabase.query-processor/process-query query))"
|
||||
{:clj-kondo/config '{:linters
|
||||
{:discouraged-var {metabase.lib.util.match/replace {:level :off}}}}}
|
||||
(:require
|
||||
[clojure.java.io :as io]
|
||||
[clojure.string :as str]
|
||||
@@ -123,25 +125,25 @@
|
||||
(symbol (format "#_\"%s.%s\"" field-name table-name)))))
|
||||
(field-id->name-form [field-id]
|
||||
(list 'do (add-name-to-field-id field-id) field-id))]
|
||||
(lib.util.match/replace form
|
||||
(lib.util.match/replace-lite form
|
||||
[:field (id :guard pos-int?) opts]
|
||||
[:field id (add-name-to-field-id id) (cond-> opts
|
||||
(pos-int? (:source-field opts))
|
||||
(update :source-field field-id->name-form))]
|
||||
|
||||
(m :guard (every-pred map? (comp pos-int? :source-table)))
|
||||
(m :guard (and (map? m) (pos-int? (:source-table m))))
|
||||
(add-names* (update m :source-table add-table-id-name))
|
||||
|
||||
(m :guard (every-pred map? (comp pos-int? :metabase.query-processor.util.add-alias-info/source-table)))
|
||||
(m :guard (and (map? m) (pos-int? (:metabase.query-processor.util.add-alias-info/source-table m))))
|
||||
(add-names* (update m :metabase.query-processor.util.add-alias-info/source-table add-table-id-name))
|
||||
|
||||
(m :guard (every-pred map? (comp pos-int? :fk-field-id)))
|
||||
(m :guard (and (map? m) (pos-int? (:fk-field-id m))))
|
||||
(-> m
|
||||
(update :fk-field-id field-id->name-form)
|
||||
add-names*)
|
||||
|
||||
;; don't recursively replace the `do` lists above, other we'll get vectors.
|
||||
(_ :guard (every-pred list? #(= (first %) 'do)))
|
||||
(l :guard (and (seq? l) (= (first l) 'do)))
|
||||
&match)))
|
||||
x)
|
||||
->sorted-mbql-query-map))
|
||||
@@ -343,10 +345,10 @@
|
||||
e)))))
|
||||
|
||||
(defn- no-$ [x]
|
||||
(lib.util.match/replace x [::$ & args] (into [::no-$] args)))
|
||||
(lib.util.match/replace-lite x [::$ & args] (into [::no-$] args)))
|
||||
|
||||
(defn- symbolize [form]
|
||||
(lib.util.match/replace form
|
||||
(lib.util.match/replace-lite form
|
||||
[::-> x y]
|
||||
(symbol (format "%s->%s" (symbolize x) (str/replace (symbolize y) #"^\$" "")))
|
||||
|
||||
|
||||
157
dev/src/dev/debug_qp/analyze_user_query.clj
Normal file
157
dev/src/dev/debug_qp/analyze_user_query.clj
Normal file
@@ -0,0 +1,157 @@
|
||||
(ns dev.debug-qp.analyze-user-query
|
||||
"Given access to e.g. a Cloud instance with a querying issue, this script can use a login token borrowed from your
|
||||
browser to make API calls to the instance, and assemble a local mock `MetadataProvider` which contains the tables,
|
||||
fields and cards necessary to run QP preprocessing and compilation locally against the same metadata the user
|
||||
instance is seeing.
|
||||
|
||||
Of course the queries can't be executed without a connection to the user's DWH, which is usually impossible."
|
||||
(:require
|
||||
[clj-http.client :as http]
|
||||
[medley.core :as m]
|
||||
[metabase.lib.js.metadata :as lib.js.metadata]
|
||||
[metabase.lib.metadata :as lib.metadata]
|
||||
[metabase.query-processor.compile :as qp.compile]
|
||||
[metabase.query-processor.preprocess :as qp.preprocess]
|
||||
[metabase.util.json :as json]
|
||||
[metabase.util.malli :as mu]))
|
||||
|
||||
;; XXX: INSTRUCTIONS:
|
||||
;; - Log into the instance, open DevTools, and navigate to the problem question.
|
||||
;; - Open the Network tab, find a `/api/card/1234` or similar request, right click and "Copy as cURL".
|
||||
;; - Paste that somewhere, probably into your shell to test that the `curl` call is working.
|
||||
;; - Grab its `cookie` and edit it into `my-ctx` below. (It's the `-b` arg to curl.)
|
||||
;; - Edit the `instance-url` of `my-ctx` below likewise.
|
||||
;; - Edit the `database-id` of `my-ctx` below to match the problem database on the user instance.
|
||||
;; - That can be seen in eg. `/api/card` requests and similar.
|
||||
;; - Now you can run the analysis functions below, like compile-card.
|
||||
;; - See the examples at the bottom of this file.
|
||||
(def ^:private my-ctx
|
||||
"Contains instance details, for passing to the various functions below."
|
||||
{:instance-url "https://acustomer.metabaseapp.com" ; No trailing slash.
|
||||
:cookie-from-browser "COPY ME FROM YOUR curl COMMAND"
|
||||
:database-id 123})
|
||||
|
||||
(defn- mk-headers [{:keys [cookie-from-browser instance-url] :as _ctx}]
|
||||
{"accept" "application/json"
|
||||
"accept-language" "en-US,en;q=0.9"
|
||||
"cache-control" "no-cache"
|
||||
"content-type" "application/json"
|
||||
"cookie" cookie-from-browser
|
||||
"pragma" "no-cache"
|
||||
"priority" "u=1, i"
|
||||
"referer" instance-url
|
||||
"sec-ch-ua" "\"Google Chrome\";v=\"141\", \"Not?A_Brand\";v=\"8\", \"Chromium\";v=\"141\""
|
||||
"sec-ch-ua-mobile" "?0"
|
||||
"sec-ch-ua-platform" "\"macOS\""
|
||||
"sec-fetch-dest" "empty"
|
||||
"sec-fetch-mode" "cors"
|
||||
"sec-fetch-site" "same-origin"
|
||||
"user-agent" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36"})
|
||||
|
||||
(defn- instance-fetch [{:keys [instance-url] :as ctx} path]
|
||||
(let [headers (mk-headers ctx)
|
||||
request {:method :get
|
||||
:url (str instance-url path)
|
||||
:accept :json
|
||||
:content-type :json
|
||||
:throw-exceptions false
|
||||
:headers headers}
|
||||
response (-> (http/request request)
|
||||
(select-keys [:body :headers :status])
|
||||
(update :body json/decode))]
|
||||
(if (<= 200 (:status response) 299)
|
||||
(:body response)
|
||||
(throw (ex-info "Non-200 response" response)))))
|
||||
|
||||
(defn- qm-index [objs]
|
||||
(m/index-by (comp str #(get % "id")) objs))
|
||||
|
||||
(defn- qm->metadata [raw]
|
||||
;; /query_metadata requests return a map in a different shape from what lib.js.metadata expects.
|
||||
;; We get {"databases" [{...}], "tables" [{..., "fields": [{...}]}]} but we need
|
||||
;; {"databases" {"12" {...}}
|
||||
;; "tables" {"333" {...}}
|
||||
;; "fields" {"4343" {...}}
|
||||
;; "questions" {"12" {...}}}
|
||||
(let [fields (for [table (get raw "tables")
|
||||
:when (number? (get table "id"))
|
||||
field (get table "fields")
|
||||
:when (number? (get field "id"))]
|
||||
field)]
|
||||
{"databases" (qm-index (get raw "databases"))
|
||||
"tables" (qm-index (get raw "tables"))
|
||||
"fields" (qm-index fields)}))
|
||||
|
||||
(defn- recursive-card-metadata
|
||||
"Helper for [[metadata-for]]. Prefer calling that function instead."
|
||||
[ctx main-card-id]
|
||||
(loop [metadata {}
|
||||
cards-seen #{main-card-id}
|
||||
[card-id & cards] [main-card-id]]
|
||||
(if-not card-id
|
||||
metadata
|
||||
(let [card (instance-fetch ctx (str "/api/card/" card-id))
|
||||
raw (instance-fetch ctx (str "/api/card/" card-id "/query_metadata"))
|
||||
nested-cards (->> (get raw "tables")
|
||||
(map #(get % "id"))
|
||||
(filter string?)
|
||||
(map (comp Long/parseLong #(subs % 6)))
|
||||
(remove cards-seen))]
|
||||
(recur (m/deep-merge metadata
|
||||
{"questions" {(str card-id) card}}
|
||||
(qm->metadata raw))
|
||||
(into cards-seen nested-cards)
|
||||
(concat cards nested-cards))))))
|
||||
|
||||
(defn- table-metadata
|
||||
"Helper for [[metadata-for]]. Prefer calling that function instead."
|
||||
[ctx table-id]
|
||||
(qm->metadata {"tables" [(-> (instance-fetch ctx (str "/api/table/" table-id "/query_metadata"))
|
||||
(dissoc "db"))]}))
|
||||
|
||||
;; =============================================== External API ===================================================
|
||||
(defn metadata-for
|
||||
"Given a map like `{:card [123 456], :table [789]}` returns a `MetadataProvider` with metadata for all the tables,
|
||||
cards and fields transitively required by the specified roots.
|
||||
|
||||
Generally you only need a single `{:card [123]}` for the problem card you're investigating.
|
||||
|
||||
Occasionally a broken card might reference fields by ID which are not really part of their query - then they're
|
||||
missing from the `/query_metadata` on that card and will be missed by this function unless you explicitly add
|
||||
`{:table [789]}` to the map of roots.
|
||||
|
||||
Returns a complete `MetadataProvider` ready to use in a QP call."
|
||||
[{:keys [database-id] :as ctx} kind->ids]
|
||||
(->> (for [[kind ids] kind->ids
|
||||
id ids]
|
||||
(case kind
|
||||
:card (recursive-card-metadata ctx id)
|
||||
:table (table-metadata ctx id)))
|
||||
(apply m/deep-merge)
|
||||
(lib.js.metadata/metadata-provider database-id)))
|
||||
|
||||
(defn preprocess-card
|
||||
"Given a `MetadataProvider` built by [[metadata-for]] and a card ID, tries to preprocess that card."
|
||||
[metadata-provider card-id]
|
||||
(mu/disable-enforcement
|
||||
(-> (lib.metadata/card metadata-provider card-id)
|
||||
:dataset-query
|
||||
qp.preprocess/preprocess)))
|
||||
|
||||
(defn compile-card
|
||||
"Given a `MetadataProvider` built by [[metadata-for]] and a card ID, tries to compile that card to e.g. SQL."
|
||||
[metadata-provider card-id]
|
||||
(mu/disable-enforcement
|
||||
(-> (lib.metadata/card metadata-provider card-id)
|
||||
:dataset-query
|
||||
qp.compile/compile)))
|
||||
|
||||
(comment
|
||||
;; Example calls
|
||||
(let [card-id 456
|
||||
mp (metadata-for my-ctx {:card [card-id]})]
|
||||
(compile-card mp card-id))
|
||||
|
||||
(let [card-id 456
|
||||
mp (metadata-for my-ctx {:card [card-id]})]
|
||||
(preprocess-card mp card-id)))
|
||||
@@ -7,11 +7,15 @@
|
||||
Configuration:
|
||||
- LINEAR_API_KEY env var must be set
|
||||
- Call (set-config! {:team-id \"...\" :project-id \"...\"}) before creating issues"
|
||||
(:refer-clojure :exclude [run!])
|
||||
(:require
|
||||
[cheshire.core :as json]
|
||||
[clj-http.client :as http]
|
||||
[clojure.java.io :as io]
|
||||
[clojure.java.shell :as shell]
|
||||
[clojure.string :as str]))
|
||||
[clojure.set :as set]
|
||||
[clojure.string :as str]
|
||||
[dev.coverage :as coverage]))
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
|
||||
@@ -20,14 +24,21 @@
|
||||
(defonce ^:private config (atom {}))
|
||||
|
||||
(defn set-config!
|
||||
"Set configuration for Linear API calls.
|
||||
"Set configuration for mutation testing.
|
||||
|
||||
Required keys:
|
||||
- :team-id — Linear team ID
|
||||
- :project-id — Linear project ID (can be set after creating a project)"
|
||||
- :project-id — Linear project ID (can be set after creating a project)
|
||||
|
||||
Optional keys:
|
||||
- :base-branch — git branch to create feature branches from (default: \"master\")
|
||||
- :project-id — Linear project ID to add issues to (skips project creation in run!)"
|
||||
[m]
|
||||
(swap! config merge m))
|
||||
|
||||
(defn- base-branch []
|
||||
(get @config :base-branch "master"))
|
||||
|
||||
(defn- api-key []
|
||||
(or (System/getenv "LINEAR_API_KEY")
|
||||
(throw (ex-info "LINEAR_API_KEY environment variable is not set" {}))))
|
||||
@@ -215,14 +226,14 @@
|
||||
"Generate a branch name for mutation testing a function.
|
||||
Example: mutation-testing-lib-order-by-orderable-columns"
|
||||
[target-ns fn-name]
|
||||
(let [short-ns (last (clojure.string/split (str target-ns) #"\."))]
|
||||
(let [short-ns (last (str/split (str target-ns) #"\."))]
|
||||
(str "mutation-testing-lib-" short-ns "-" fn-name)))
|
||||
|
||||
(defn create-branch!
|
||||
"Create and checkout a new branch from master for mutation testing a function."
|
||||
"Create and checkout a new branch from the base branch for mutation testing a function."
|
||||
[target-ns fn-name]
|
||||
(let [branch (branch-name target-ns fn-name)]
|
||||
(sh "git" "checkout" "master")
|
||||
(sh "git" "checkout" (base-branch))
|
||||
(sh "git" "pull")
|
||||
(sh "git" "checkout" "-b" branch)
|
||||
branch))
|
||||
@@ -255,10 +266,11 @@
|
||||
(let [title (pr-title target-ns fn-name)
|
||||
body (pr-description opts)
|
||||
result (sh "gh" "pr" "create" "--draft"
|
||||
"--base" (base-branch)
|
||||
"--title" title
|
||||
"--body" body
|
||||
"--label" "no-backport")]
|
||||
(clojure.string/trim (:out result))))
|
||||
(str/trim (:out result))))
|
||||
|
||||
(defn add-pr-comment!
|
||||
"Add a general comment to a PR. `pr-url-or-number` can be a PR URL or number."
|
||||
@@ -308,12 +320,17 @@
|
||||
"-f" "start_side=RIGHT"]))]
|
||||
(apply sh args)))
|
||||
|
||||
(defn return-to-master!
|
||||
"Checkout master branch."
|
||||
(defn return-to-base!
|
||||
"Checkout the configured base branch."
|
||||
[]
|
||||
(sh "git" "checkout" "master")
|
||||
(sh "git" "checkout" (base-branch))
|
||||
nil)
|
||||
|
||||
(defn return-to-master!
|
||||
"Checkout the configured base branch. Deprecated: use return-to-base! instead."
|
||||
[]
|
||||
(return-to-base!))
|
||||
|
||||
;;; --- Report helpers ---
|
||||
|
||||
(defn report-stats
|
||||
@@ -409,6 +426,358 @@
|
||||
(create-issue! {:title (linear-issue-title target-ns fn-name)
|
||||
:description (linear-issue-description target-ns fn-name)}))
|
||||
|
||||
;;; --- Orchestration ---
|
||||
|
||||
(defn parse-namespace
|
||||
"Parse a namespace symbol into all derived paths and names.
|
||||
Auto-detects .cljc vs .clj by checking which file exists."
|
||||
[target-ns-sym]
|
||||
(let [target-ns (str target-ns-sym)
|
||||
test-ns (str target-ns "-test")
|
||||
short-name (last (str/split target-ns #"\."))
|
||||
ns-path (-> target-ns
|
||||
(str/replace "." "/")
|
||||
(str/replace "-" "_"))
|
||||
test-path-base (-> test-ns
|
||||
(str/replace "." "/")
|
||||
(str/replace "-" "_"))
|
||||
source-ext (cond
|
||||
(.exists (io/file (str "src/" ns-path ".cljc"))) ".cljc"
|
||||
(.exists (io/file (str "src/" ns-path ".clj"))) ".clj"
|
||||
:else ".cljc")
|
||||
test-ext (cond
|
||||
(.exists (io/file (str "test/" test-path-base ".cljc"))) ".cljc"
|
||||
(.exists (io/file (str "test/" test-path-base ".clj"))) ".clj"
|
||||
:else ".cljc")]
|
||||
{:target-ns (symbol target-ns)
|
||||
:test-ns (symbol test-ns)
|
||||
:short-name short-name
|
||||
:source-path (str "src/" ns-path source-ext)
|
||||
:test-path (str "test/" test-path-base test-ext)
|
||||
:report-path (str "mutation-testing-report." target-ns ".before.md")}))
|
||||
|
||||
(defn group-functions
|
||||
"Group functions from coverage results for batch processing.
|
||||
Takes the output of coverage/test-namespace.
|
||||
|
||||
Logic:
|
||||
- Public functions with surviving mutations each become a group
|
||||
- Private functions get assigned to the public function with the most test overlap
|
||||
- Functions with zero surviving mutations are skipped
|
||||
- Uncovered or unassignable private functions get their own group"
|
||||
[coverage-results]
|
||||
(let [with-survivors (into {} (filter (fn [[_ v]] (seq (:survived v))) coverage-results))
|
||||
public? (fn [fn-sym]
|
||||
(let [v (find-var fn-sym)]
|
||||
(and v (not (:private (meta v))))))
|
||||
public-fns (into {} (filter (fn [[k _]] (public? k)) with-survivors))
|
||||
private-fns (into {} (remove (fn [[k _]] (public? k)) with-survivors))
|
||||
assignments (into {}
|
||||
(for [[priv-fn priv-data] private-fns
|
||||
:let [priv-tests (set (:tests priv-data))
|
||||
best (when (seq priv-tests)
|
||||
(->> public-fns
|
||||
(map (fn [[pub-fn pub-data]]
|
||||
[pub-fn (count (set/intersection
|
||||
priv-tests
|
||||
(set (:tests pub-data))))]))
|
||||
(filter (fn [[_ n]] (pos? n)))
|
||||
(sort-by second >)
|
||||
first))]
|
||||
:when best]
|
||||
[priv-fn (first best)]))
|
||||
unassigned-private (into {} (remove (fn [[k _]] (contains? assignments k)) private-fns))
|
||||
public-groups (for [[pub-fn pub-data] public-fns
|
||||
:let [attached (keep (fn [[priv-fn assigned-to]]
|
||||
(when (= assigned-to pub-fn) priv-fn))
|
||||
assignments)
|
||||
all-fn-names (vec (cons pub-fn attached))
|
||||
all-mutations (vec (concat (:survived pub-data)
|
||||
(mapcat #(:survived (get coverage-results %))
|
||||
attached)))
|
||||
all-tests (apply set/union
|
||||
(keep #(:tests (get coverage-results %))
|
||||
all-fn-names))]]
|
||||
{:primary-fn pub-fn
|
||||
:fn-names all-fn-names
|
||||
:mutations all-mutations
|
||||
:tests (or all-tests #{})})
|
||||
private-groups (for [[priv-fn priv-data] unassigned-private]
|
||||
{:primary-fn priv-fn
|
||||
:fn-names [priv-fn]
|
||||
:mutations (vec (:survived priv-data))
|
||||
:tests (or (:tests priv-data) #{})})]
|
||||
(vec (concat public-groups private-groups))))
|
||||
|
||||
(defn build-test-prompt
|
||||
"Build a prompt for Claude to write tests that kill surviving mutations."
|
||||
[{:keys [target-ns test-ns source-path test-path fn-names mutations]}]
|
||||
(let [fn-sources (str/join "\n\n"
|
||||
(for [fn-name fn-names
|
||||
:let [fn-info (coverage/find-function-source fn-name)
|
||||
source (when fn-info (coverage/read-function-from-file fn-info))]
|
||||
:when source]
|
||||
(str ";;; " fn-name "\n" source)))
|
||||
mutation-list (str/join "\n\n"
|
||||
(map-indexed
|
||||
(fn [i {:keys [description mutation]}]
|
||||
(str "### Mutation " (inc i) ": " description "\n```clojure\n" mutation "\n```"))
|
||||
mutations))
|
||||
test-content (slurp test-path)]
|
||||
(str "You are writing mutation tests for functions in `" target-ns "`.\n"
|
||||
"\n"
|
||||
"## Source Code of Functions Under Test\n"
|
||||
"\n"
|
||||
"```clojure\n"
|
||||
fn-sources
|
||||
"\n```\n"
|
||||
"\n"
|
||||
"## Existing Test File (" test-path ")\n"
|
||||
"\n"
|
||||
"```clojure\n"
|
||||
test-content
|
||||
"\n```\n"
|
||||
"\n"
|
||||
"## Surviving Mutations to Kill\n"
|
||||
"\n"
|
||||
"Each mutation below represents a change to the source code that no existing test catches.\n"
|
||||
"Write the simplest tests that would fail if these mutations were applied.\n"
|
||||
"\n"
|
||||
mutation-list
|
||||
"\n"
|
||||
"\n"
|
||||
"## Instructions\n"
|
||||
"\n"
|
||||
"- Write the simplest tests that kill these surviving mutations\n"
|
||||
"- Follow the patterns in the existing test file (same helpers, same assertion style)\n"
|
||||
"- Never call private functions directly — test through public API\n"
|
||||
"- Insert tests near related existing tests, not at the end of the file\n"
|
||||
"- Use the Edit tool to modify the test file at: " test-path "\n"
|
||||
"- After writing tests, verify they compile by running: clj-nrepl-eval -p $(clj-nrepl-eval --discover-ports | head -1) \"(require '" test-ns " :reload)\"\n"
|
||||
"- Each test should verify one meaningful behavior — don't over-engineer\n")))
|
||||
|
||||
(defn invoke-claude!
|
||||
"Shell out to claude CLI to write tests. Returns the output string."
|
||||
[prompt]
|
||||
(let [result (shell/sh "claude" "-p"
|
||||
"--allowedTools" "Edit,Read,Bash(clj-nrepl-eval*)"
|
||||
:in prompt)]
|
||||
(when-not (zero? (:exit result))
|
||||
(throw (ex-info "Claude invocation failed" {:exit (:exit result)
|
||||
:err (:err result)})))
|
||||
(:out result)))
|
||||
|
||||
(defn- all-test-names
|
||||
"Get all test var names from a test namespace."
|
||||
[test-ns-sym]
|
||||
(require test-ns-sym :reload)
|
||||
(let [ns-obj (find-ns test-ns-sym)]
|
||||
(->> (ns-interns ns-obj)
|
||||
vals
|
||||
(filter #(:test (meta %)))
|
||||
(map #(symbol (str test-ns-sym) (name (.sym ^clojure.lang.Var %))))
|
||||
set)))
|
||||
|
||||
(defn- count-tests
|
||||
"Count test vars in a namespace."
|
||||
[test-ns-sym]
|
||||
(count (filter #(:test (meta %)) (vals (ns-interns (find-ns test-ns-sym))))))
|
||||
|
||||
(defn verify-and-retry!
|
||||
"Verify mutations are killed. Retry with Claude if needed.
|
||||
Returns {:status :success|:partial, :killed [...], :survived [...]}."
|
||||
[{:keys [fn-names test-ns test-path max-retries]
|
||||
:or {max-retries 2}}]
|
||||
(loop [retries-left max-retries]
|
||||
(let [test-names (all-test-names test-ns)
|
||||
fn-results (into {}
|
||||
(for [fn-name fn-names
|
||||
:let [result (coverage/test-mutations fn-name test-names)]
|
||||
:when result]
|
||||
[fn-name result]))
|
||||
all-killed (vec (mapcat :killed (vals fn-results)))
|
||||
all-survived (vec (mapcat :survived (vals fn-results)))]
|
||||
(if (empty? all-survived)
|
||||
{:status :success :killed all-killed :survived []}
|
||||
(if (pos? retries-left)
|
||||
(do
|
||||
(println " " (count all-survived) "mutations still survive, retrying..."
|
||||
"(" retries-left "retries left)")
|
||||
(let [retry-prompt (str "Some mutations still survive after your previous tests. "
|
||||
"Please write additional tests to kill them.\n\n"
|
||||
"## Still-Surviving Mutations\n\n"
|
||||
(str/join "\n\n"
|
||||
(for [{:keys [description mutation]} all-survived]
|
||||
(str "### " description "\n```clojure\n" mutation "\n```")))
|
||||
"\n\n## Instructions\n"
|
||||
"- The test file is at: " test-path "\n"
|
||||
"- Use the Edit tool to add more tests\n"
|
||||
"- Focus specifically on the mutations listed above\n"
|
||||
"- Do NOT duplicate existing tests\n")]
|
||||
(invoke-claude! retry-prompt))
|
||||
(recur (dec retries-left)))
|
||||
{:status :partial :killed all-killed :survived all-survived})))))
|
||||
|
||||
(defn process-group!
|
||||
"Process one group of functions: branch → tests → verify → commit → PR.
|
||||
Returns {:pr-url ... :issue ... :killed ... :not-killed ...}."
|
||||
[session group]
|
||||
(let [{:keys [target-ns test-ns test-path]} session
|
||||
{:keys [primary-fn fn-names mutations]} group
|
||||
primary-fn-name (name primary-fn)]
|
||||
;; 1. Create branch
|
||||
(println " Creating branch...")
|
||||
(create-branch! (str target-ns) primary-fn-name)
|
||||
|
||||
;; 2. Count tests before
|
||||
(require test-ns :reload)
|
||||
(let [tests-before (count-tests test-ns)]
|
||||
|
||||
;; 3. Build prompt and invoke Claude
|
||||
(println " Invoking Claude to write tests...")
|
||||
(let [prompt (build-test-prompt {:target-ns target-ns
|
||||
:test-ns test-ns
|
||||
:source-path (:source-path session)
|
||||
:test-path test-path
|
||||
:fn-names fn-names
|
||||
:mutations mutations})]
|
||||
(invoke-claude! prompt))
|
||||
|
||||
;; 4. Verify and retry
|
||||
(println " Verifying mutations killed...")
|
||||
(let [verify-result (verify-and-retry! {:fn-names fn-names
|
||||
:test-ns test-ns
|
||||
:test-path test-path
|
||||
:max-retries 2})
|
||||
killed (:killed verify-result)
|
||||
survived (:survived verify-result)
|
||||
tests-after (count-tests test-ns)
|
||||
tests-added (- tests-after tests-before)]
|
||||
|
||||
;; 5. Commit and push
|
||||
(println " Committing and pushing...")
|
||||
(commit-and-push! (str target-ns) primary-fn-name [test-path])
|
||||
|
||||
;; 6. Create Linear issue
|
||||
(println " Creating Linear issue...")
|
||||
(let [issue (create-issue-for-function! (str target-ns) primary-fn-name)
|
||||
|
||||
;; 7. Create draft PR
|
||||
_ (println " Creating draft PR...")
|
||||
pr-url (create-draft-pr!
|
||||
{:target-ns (str target-ns)
|
||||
:fn-name primary-fn-name
|
||||
:fn-names (mapv name fn-names)
|
||||
:linear-identifier (:identifier issue)
|
||||
:mutations-before (count mutations)
|
||||
:tests-added tests-added
|
||||
:killed (mapv :description killed)
|
||||
:not-killed (mapv (fn [m] {:description (:description m)
|
||||
:rationale "Could not be killed automatically"})
|
||||
survived)
|
||||
:suggested-changes []})]
|
||||
|
||||
;; 8. Return to base branch
|
||||
(return-to-base!)
|
||||
|
||||
(println " Done! PR:" pr-url)
|
||||
{:pr-url pr-url
|
||||
:issue issue
|
||||
:killed killed
|
||||
:not-killed survived
|
||||
:tests-added tests-added})))))
|
||||
|
||||
(defn print-summary!
|
||||
"Print a summary of the mutation testing run."
|
||||
[{:keys [project groups results]}]
|
||||
(println "\n========================================")
|
||||
(println " Mutation Testing Summary")
|
||||
(println "========================================")
|
||||
(println "Project:" (:name project))
|
||||
(println "Groups:" (count groups))
|
||||
(let [successful (filter :pr-url results)
|
||||
failed (filter :error results)
|
||||
total-killed (count (mapcat :killed successful))
|
||||
total-unkilled (count (mapcat :not-killed successful))
|
||||
total-tests (reduce + 0 (keep :tests-added successful))]
|
||||
(println "PRs created:" (count successful))
|
||||
(when (seq failed)
|
||||
(println "Errors:" (count failed)))
|
||||
(println "Tests added:" total-tests)
|
||||
(println "Mutations killed:" total-killed)
|
||||
(println "Mutations not killed:" total-unkilled)
|
||||
(println)
|
||||
(when (seq successful)
|
||||
(println "PRs:")
|
||||
(doseq [r successful]
|
||||
(println " " (:pr-url r))))
|
||||
(when (seq failed)
|
||||
(println "\nFailed:")
|
||||
(doseq [r failed]
|
||||
(println " " (:primary-fn r) "-" (:error r))))
|
||||
(println "========================================")))
|
||||
|
||||
(defn run!
|
||||
"Main entry point. Runs the full mutation testing workflow for a namespace.
|
||||
|
||||
Prerequisites:
|
||||
- nREPL running
|
||||
- LINEAR_API_KEY env var set
|
||||
- gh CLI authenticated
|
||||
- team-id configured via (set-config! {:team-id \"...\"})
|
||||
|
||||
Options (optional second arg):
|
||||
- :base-branch — git branch to create feature branches from (default: from config, or \"master\")
|
||||
- :project-id — existing Linear project ID to use (skips creating a new project)"
|
||||
([target-ns-sym] (run! target-ns-sym {}))
|
||||
([target-ns-sym opts]
|
||||
(when-let [bb (:base-branch opts)]
|
||||
(set-config! {:base-branch bb}))
|
||||
(when-let [pid (:project-id opts)]
|
||||
(set-config! {:project-id pid}))
|
||||
(let [parsed (parse-namespace target-ns-sym)
|
||||
{:keys [target-ns test-ns report-path]} parsed]
|
||||
;; 1. Generate report
|
||||
(println "Generating mutation testing report for" (str target-ns) "...")
|
||||
(coverage/generate-report target-ns [test-ns] report-path)
|
||||
(println "Report written to" report-path)
|
||||
|
||||
;; 2. Create or reuse Linear project
|
||||
(let [project (if (:project-id @config)
|
||||
(do (println "Using existing Linear project:" (:project-id @config))
|
||||
{:project-id (:project-id @config)
|
||||
:name (str "Existing project " (:project-id @config))})
|
||||
(do (println "Creating Linear project...")
|
||||
(let [p (create-project-for-namespace! (str target-ns) report-path)]
|
||||
(println "Created project:" (:name p))
|
||||
p)))]
|
||||
|
||||
;; 3. Run coverage and group functions
|
||||
(println "Running coverage analysis and grouping...")
|
||||
(let [coverage-results (coverage/test-namespace target-ns [test-ns])
|
||||
groups (group-functions coverage-results)]
|
||||
(println "Found" (count groups) "function groups to process")
|
||||
|
||||
;; 4. Process each group
|
||||
(let [results (vec
|
||||
(for [group groups]
|
||||
(do
|
||||
(println "\n--- Processing:" (:primary-fn group)
|
||||
"(" (count (:mutations group)) "surviving mutations) ---")
|
||||
(try
|
||||
(process-group! parsed group)
|
||||
(catch Exception e
|
||||
(println "ERROR:" (.getMessage e))
|
||||
(try (return-to-base!) (catch Exception _))
|
||||
{:error (.getMessage e)
|
||||
:primary-fn (:primary-fn group)})))))]
|
||||
|
||||
;; 5. Print summary
|
||||
(print-summary! {:project project
|
||||
:groups groups
|
||||
:results results})
|
||||
results))))))
|
||||
|
||||
(comment
|
||||
;; Setup:
|
||||
;; 1. Set LINEAR_API_KEY env var before starting the REPL
|
||||
|
||||
@@ -87,6 +87,7 @@ Metabase's reference documentation.
|
||||
|
||||
- [The SQL editor](./questions/native-editor/writing-sql.md)
|
||||
- [SQL parameters](./questions/native-editor/sql-parameters.md)
|
||||
- [Table variables](./questions/native-editor/table-variables.md)
|
||||
- [Referencing models and saved questions](./questions/native-editor/referencing-saved-questions-in-queries.md)
|
||||
- [Snippets](./questions/native-editor/snippets.md)
|
||||
- [Snippet folder permissions](./permissions/snippets.md)
|
||||
@@ -150,6 +151,7 @@ Metabase's reference documentation.
|
||||
- [AI overview](./ai/start.md)
|
||||
- [Metabot](./ai/metabot.md)
|
||||
- [Setting up Metabot](./ai/settings.md)
|
||||
- [SQL generation](./ai/sql-generation.md)
|
||||
|
||||
### Exploration and organization
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ If you're an admin who just wants to edit data in your tables, check out [Editab
|
||||
|
||||
For actions to work, you'll first need to do the following two things:
|
||||
|
||||
1. **Enable model actions for the database connection**. To enable actions for a database connection, admins should click on the **gear** icon in the upper right and navigate to **Admin settings** > **Databases**, then click on the database you want to create actions for. On the right side of the connection settings form, toggle the **Model actions** option. For actions to work, the database user account (the account you're using to connect to the database) must have [write permissions](../databases/users-roles-privileges.md#privileges-to-enable-actions-and-editable-table-data). And for now, actions are only supported on PostgreSQL and MySQL databases.
|
||||
1. **Enable model actions for the database connection**. To enable actions for a database connection, admins should click on the **grid** icon in the upper right and navigate to **Admin** > **Databases**, then click on the database you want to create actions for. On the right side of the connection settings form, toggle the **Model actions** option. For actions to work, the database user account (the account you're using to connect to the database) must have [write permissions](../databases/users-roles-privileges.md#privileges-to-enable-actions-and-editable-table-data). And for now, actions are only supported on PostgreSQL and MySQL databases.
|
||||
2. **Create at least one model from that database.** Actions are associated with models, so you'll need to have created (or have access to) at least one model before you can start creating actions.
|
||||
|
||||
## Who can use actions
|
||||
|
||||
BIN
docs/ai/images/accept-or-reject.png
Normal file
BIN
docs/ai/images/accept-or-reject.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 55 KiB |
BIN
docs/ai/images/sql-generation-admin.png
Normal file
BIN
docs/ai/images/sql-generation-admin.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
BIN
docs/ai/images/sql-generation.png
Normal file
BIN
docs/ai/images/sql-generation.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 75 KiB |
@@ -124,7 +124,6 @@ Open it from **+ New → AI exploration**, then ask a question or pick one of th
|
||||
|
||||
When answering questions in AI exploration, Metabot searches a limited set of content instead of your entire Metabase. Admins can set a **Collection for natural language querying** to scope AI exploration to a specific collection and its subcollections. You can still refer to content outside that scope by explicitly @-mentioning it in your prompt. See [Metabot AI settings](../ai/settings.md) for more details.
|
||||
|
||||
|
||||
## Giving feedback on Metabot responses
|
||||
|
||||
When you hover over Metabot's responses, you'll see options to give feedback.
|
||||
|
||||
@@ -7,7 +7,7 @@ summary: Configure Metabot settings, including which collections Metabot can acc
|
||||
|
||||
> Metabot is only available as an add-on on Metabase Cloud.
|
||||
|
||||
_Settings > Admin settings > AI_
|
||||
_Admin > AI_
|
||||
|
||||
This page covers admin settings for Metabase's AI assistant, [Metabot](./metabot.md).
|
||||
|
||||
@@ -28,7 +28,7 @@ For now, Metabot is only available for Metabase Cloud. Before you can set up Met
|
||||
|
||||
5. Read through the [terms of service](https://www.metabase.com/license/hosting) and click **Add Metabot AI**.
|
||||
|
||||
Once you've added Metabot AI in the Metabase store, you can log into your Metabase and configure it in _Admin settings > AI_.
|
||||
Once you've added Metabot AI in the Metabase store, you can log into your Metabase and configure it in _Admin > AI_.
|
||||
|
||||
## Verified content
|
||||
|
||||
@@ -105,7 +105,7 @@ In other words, to restrict what data Metabot can see for each person, simply ap
|
||||
|
||||
## Viewing Metabot usage
|
||||
|
||||
You can see how many Metabot requests people have made this month by going to **Admin settings > Settings > License**.
|
||||
You can see how many Metabot requests people have made this month by going to **Admin > Settings > License**.
|
||||
|
||||
If you aren't logged into the [Metabase Store](../cloud/accounts-and-billing.md).(, you'll need to log in to the store before you can view the usage. Once logged in to the store, go back to your Metabase and view the license page.
|
||||
|
||||
|
||||
55
docs/ai/sql-generation.md
Normal file
55
docs/ai/sql-generation.md
Normal file
@@ -0,0 +1,55 @@
|
||||
---
|
||||
title: SQL generation on self-hosted Metabases
|
||||
summary: Bring your own API key to generate SQL from natural language in your self-hosted Metabase.
|
||||
---
|
||||
|
||||
# SQL generation on self-hosted Metabases
|
||||
|
||||
> One-shot SQL generation with your own API key is only available on self-hosted Metabases. For Metabase Cloud, check out [Metabot](./metabot.md).
|
||||
|
||||
Bring your own API key to generate SQL from natural language in the [native editor](../questions/native-editor/writing-sql.md).
|
||||
|
||||
## Setting up one-shot SQL generation
|
||||
|
||||
To enable SQL generation on a self-hosted Metabase:
|
||||
|
||||
1. Go to **Admin settings > AI**.
|
||||
2. Enter your **Anthropic API key**.
|
||||
3. Select the AI model you'd like to use for SQL generation. (Opus models typically give the best results.)
|
||||
|
||||

|
||||
|
||||
You'll need an API key from [Anthropic](https://www.anthropic.com/learn/build-with-claude). Once you've entered the key, anyone in your Metabase can use SQL generation in the native editor.
|
||||
|
||||
Currently, only Anthropic is supported.
|
||||
|
||||
## Generate SQL from natural language
|
||||
|
||||

|
||||
|
||||
To generate SQL from a natural language prompt:
|
||||
|
||||
1. Open the [native editor](../questions/native-editor/writing-sql.md).
|
||||
|
||||
2. Press **Cmd+Shift+I** (Mac) or **Ctrl+Shift+I** (Windows).
|
||||
|
||||
3. Enter one or more tables.
|
||||
|
||||
4. Type a prompt describing the query you want to write.
|
||||
|
||||
5. Click **Generate** to create the SQL.
|
||||
|
||||
6. Review the generated SQL before running it.
|
||||
|
||||
You can also edit existing SQL. Place your cursor in the query, trigger the prompt, and describe the change you want to make (like "add a filter for orders from this year"). Metabase will suggest edits that you can accept or reject. You must select at least one table for both new queries and edits.
|
||||
|
||||

|
||||
|
||||
Each prompt is independent — the AI doesn't retain context from previous generations.
|
||||
|
||||
Like with all generative AI, always double-check the output.
|
||||
|
||||
## Further reading
|
||||
|
||||
- [The native/SQL editor](../questions/native-editor/writing-sql.md)
|
||||
- [Metabot](./metabot.md)
|
||||
@@ -14,6 +14,10 @@ Metabot is an AI assistant that helps you explore and analyze your data.
|
||||
|
||||
Admins can configure Metabot to focus on specific collections and manage example prompts.
|
||||
|
||||
## [SQL generation](./sql-generation.md)
|
||||
|
||||
Use your own API key to generate SQL from natural language on self-hosted Metabases.
|
||||
|
||||
## [Agent API](./agent-api.md)
|
||||
|
||||
A REST API for building headless, agentic BI applications on top of Metabase's semantic layer.
|
||||
|
||||
5900
docs/api.json
5900
docs/api.json
File diff suppressed because it is too large
Load Diff
@@ -91,8 +91,8 @@ Just like uploaded CSV data, the data from your Google Sheets counts toward your
|
||||
To check how much storage you're using:
|
||||
|
||||
1. Log in to your Metabase.
|
||||
2. Click on the **gear** icon in the upper right.
|
||||
3. Select **Admin settings**.
|
||||
2. Click the **grid** icon in the upper right.
|
||||
3. Select **Admin**.
|
||||
4. In the **Settings** tab, click on **License and billing**.
|
||||
5. Check:
|
||||
- Currently stored rows
|
||||
|
||||
@@ -56,7 +56,7 @@ Generally, annual billing is a great deal; it's cheaper across the board. The on
|
||||
|
||||
## What counts as a user account?
|
||||
|
||||
A user account is any account which has been added to your Metabase instance (manually or via SSO) that has not been deactivated. You can view a list of user accounts in your **Admin settings** -> **People** list. Any user account which has been [deactivated](../people-and-groups/managing.md#deactivating-an-account) doesn't count toward your number of user accounts. That is to say: if an account exists, but has not been deactivated, that account will count toward your bill, _even if no one signs in and uses that account_. If an account is deactivated, that account is charged for the number of days the account was available for use during that billing period, including the day it was deactivated (since it was available for use for part of the day up until it was deactivated).
|
||||
A user account is any account which has been added to your Metabase instance (manually or via SSO) that has not been deactivated. You can view a list of user accounts in your **Admin** -> **People** list. Any user account which has been [deactivated](../people-and-groups/managing.md#deactivating-an-account) doesn't count toward your number of user accounts. That is to say: if an account exists, but has not been deactivated, that account will count toward your bill, _even if no one signs in and uses that account_. If an account is deactivated, that account is charged for the number of days the account was available for use during that billing period, including the day it was deactivated (since it was available for use for part of the day up until it was deactivated).
|
||||
|
||||
## How billing works with embedding
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ To migrate, you'll need to [contact our success team](https://www.metabase.com/h
|
||||
|
||||
The major version of your self-hosted Metabase should match the version of your Metabase Cloud instance. For example, if your Metabase Cloud instance is on version 52, then your self-hosted instance should be on version 52 as well.
|
||||
|
||||
You can find the version of your Metabase by clicking on the **Gear** icon in the top right of your Metabase and clicking on **About Metabase**. For Metabase Cloud, you can also see the version of your instance in the [Metabase Store](https://store.metabase.com).
|
||||
You can find the version of your Metabase by clicking the **grid icon** in the top right, selecting **Help**, then clicking **About Metabase**. For Metabase Cloud, you can also see the version of your instance in the [Metabase Store](https://store.metabase.com).
|
||||
|
||||
### Use PostgreSQL for your application database
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ You'll kick off the migration process in your self-hosted Metabase, and finish u
|
||||
|
||||
### Initiate your migration
|
||||
|
||||
In your Metabase, go to **Admin settings** > **Settings** > **Cloud**.
|
||||
In your Metabase, go to **Admin** > **Settings** > **Cloud**.
|
||||
|
||||
In the **Migrate to Metabase Cloud** section, click on the **Get started** button.
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ If you're looking for date, time, number, or currency formatting, see [Formattin
|
||||
|
||||
Hit cmd/ctrl + k to bring up the command palette and search for "Branding" and click on Settings -> Branding.
|
||||
|
||||
You can also click on the **gear** icon at the bottom of the navigation sidebar and click through **Admin settings** > **Settings** > **Appearance**.
|
||||
You can also click the **grid** icon at the bottom of the navigation sidebar and click through **Admin** > **Settings** > **Appearance**.
|
||||
|
||||
Appearance settings are split across different tabs:
|
||||
|
||||
@@ -123,7 +123,7 @@ This setting affects all links in the product experience that point to Metabase.
|
||||
|
||||
## Help link in the settings menu
|
||||
|
||||
The Settings menu (the "gear" menu in the upper right of your Metabase) includes a **Help** option that links to a [Metabase help page](https://www.metabase.com/help/) by default. You can change this menu Help item by selecting one of the following options:
|
||||
The Settings menu (the "grid" menu in the upper right of your Metabase) includes a **Help** option that links to a [Metabase help page](https://www.metabase.com/help/) by default. You can change this menu Help item by selecting one of the following options:
|
||||
|
||||
- Link to [Metabase help](https://www.metabase.com/help/) (default)
|
||||
- Hide it (the Settings menu won't display the Help option at all).
|
||||
|
||||
@@ -74,10 +74,21 @@ If you select "Don't cache results" for a question, dashboard, or database, Meta
|
||||
|
||||
{% include plans-blockquote.html feature="Refresh cache automatically" %}
|
||||
|
||||
> **Refreshing the cache automatically doesn't apply when [row and column security](../permissions/row-and-column-security.md) or [connection impersonation](../permissions/impersonation.md) permissions are in effect.** In those permissions setups, Metabase ignores the automatically generated cache and runs a fresh query (which it will then cache). So _caching_ still works for row and column security and impersonation, it's just that this preemptive, automatic caching doesn't apply in these cases, as those permissions setups filter data specific to each viewer.
|
||||
If you turn on "Refresh cache automatically" for a question or dashboard, Metabase will rerun the query/queries as soon as the cache is invalidated by whichever caching policy you've set.
|
||||
|
||||
If you turn on refresh cache automatically for a question or dashboard, Metabase will rerun the query/queries as soon as the cache is invalidated by whichever caching policy you've set. Normally, Metabase only refreshes the cache when someone views the item _after_ its cache has expired. So, normally, when the cache has expired, the next person to view the question will be stuck waiting for the query to run and refresh the cache. But by automatically refreshing results to update the cache, the loading times will always be as fast as possible, and people will always get valid, cached results.
|
||||
Normally, Metabase only refreshes the cache when someone views the item _after_ its cache has expired. Which means that the first person to view a question after the cache has expired will be stuck waiting for the query to run and refresh the cache.
|
||||
|
||||
If you turn on "Refresh cache automatically", Metabase will refresh the cache as soon as it expires, so the loading times will always be from a valid cache.
|
||||
|
||||
### Some permissions setups are incompatible with automatic cache refreshing
|
||||
|
||||
Automatic cache refresh is NOT available when certain permission setups are in effect:
|
||||
|
||||
- [Row and column security](../permissions/row-and-column-security.md)
|
||||
- [Connection impersonation](../permissions/impersonation.md)
|
||||
- [Database routing](../permissions/database-routing.md)
|
||||
|
||||
Caching will still work for queries that people run, but Metabase won't automatically refresh the cache, as these setups create many different queries, each returning data specific to each user's permissions.
|
||||
### How Metabase handles parameter values when automatically refreshing the cache
|
||||
|
||||
When automatically refreshing results, Metabase will apply the default parameter values (if any). Metabase will also cache results of up to ten of the most frequently applied parameter values that were applied _during the last caching period_ (as defined by your caching policy). If your question or dashboard has multiple parameters, Metabase will cache up to ten of the most frequently applied _combinations_ of parameter values.
|
||||
@@ -101,7 +112,7 @@ _\* Denotes [Pro](https://www.metabase.com/product/pro) and [Enterprise](https:/
|
||||
|
||||
### Default caching policy
|
||||
|
||||
To set a default caching policy for your Metabase: Hit Cmd/Ctrl + k to bring up the command palette and search for **Performance**. Or, click through **Gear** settings icon > **Admin settings** > **Performance** > **Database caching**.
|
||||
To set a default caching policy for your Metabase: Hit Cmd/Ctrl + k to bring up the command palette and search for **Performance**. Or, click through **grid** icon > **Admin** > **Performance** > **Database caching**.
|
||||
|
||||
Click on the button next to **Default policy**, and select a [cache invalidation policy](#cache-invalidation-policies).
|
||||
|
||||
@@ -157,7 +168,7 @@ A question policy overrides a dashboard policy, which overrides a database polic
|
||||
To clear the cache and refresh the results:
|
||||
|
||||
- **Questions and dashboards**: Visit the item and click through the **Info > Caching policy > Clear cache** (the "Clear cache" button is at the bottom of the sidebar).
|
||||
- **Database**: Click the **Gear** icon and click through **Admin settings** > **Performance** > **Database caching**. Select your database and click the **Clear cache** button (at the bottom of the page).
|
||||
- **Database**: Click the **grid** icon and click through **Admin** > **Performance** > **Database caching**. Select your database and click the **Clear cache** button (at the bottom of the page).
|
||||
|
||||
## Caching location
|
||||
|
||||
|
||||
@@ -214,6 +214,9 @@ config:
|
||||
notification-system-event-thread-pool-size: 5
|
||||
notification-temp-file-size-max-bytes: 10485760
|
||||
notification-thread-pool-size: 3
|
||||
oidc-allowed-networks: allow-all
|
||||
oidc-providers: []
|
||||
oidc-user-provisioning-enabled: true
|
||||
persisted-model-refresh-cron-schedule: 0 0 0/6 * * ? *
|
||||
persisted-models-enabled: false
|
||||
premium-embedding-token: null
|
||||
@@ -281,6 +284,7 @@ config:
|
||||
smtp-override-enabled: false
|
||||
source-address-header: X-Forwarded-For
|
||||
sql-jdbc-fetch-size: 500
|
||||
sql-tools-parser-backend: sqlglot
|
||||
ssh-heartbeat-interval-sec: 180
|
||||
start-of-week: sunday
|
||||
subscription-allowed-domains: null
|
||||
|
||||
@@ -12,7 +12,7 @@ By default, Metabase uses OpenStreetMaps for map visualizations, but there are a
|
||||
|
||||
To find the map settings:
|
||||
|
||||
1. At the top right of the screen, click **gear** icon > **Admin settings** > **Settings**.
|
||||
1. At the top right of the screen, click **grid** icon > **Admin** > **Settings**.
|
||||
2. Select `Maps` from the navigation on the left.
|
||||
|
||||

|
||||
@@ -41,7 +41,7 @@ Your GeoJSON file should:
|
||||
|
||||
To add a custom map:
|
||||
|
||||
1. Go to **Admin Settings > Maps > Custom Maps**.
|
||||
1. Click the **grid icon**, then go to **Admin** > **Settings** > **Maps** > **Custom Maps**.
|
||||
2. Click **Add a Map**.
|
||||
3. Enter a name for your map.
|
||||
4. Provide the URL to your GeoJSON file.
|
||||
|
||||
@@ -13,7 +13,7 @@ Both admins and people with [settings access](../permissions/application.md#sett
|
||||
|
||||
## Email on Metabase Cloud
|
||||
|
||||
_Admin settings > Settings > Email_
|
||||
_Admin > Settings > Email_
|
||||
|
||||
On Metabase Cloud, you can use the default SMTP server, or set up a custom server.
|
||||
|
||||
@@ -32,7 +32,7 @@ When Metabase manages the SMTP server, you can't change the from address. If you
|
||||
|
||||
{% include plans-blockquote.html feature="Custom SMTP server on Metabase Cloud" %}
|
||||
|
||||
_Admin settings > Settings > Email > Set up a custom SMTP server_
|
||||
_Admin > Settings > Email > Set up a custom SMTP server_
|
||||
|
||||
By default, Metabase Cloud manages an SMTP server for you. But if you want to use your own SMTP server, you can bring your own.
|
||||
|
||||
@@ -66,7 +66,7 @@ You can edit these settings at any time. You can also toggle between this custom
|
||||
|
||||
## Configuring your email account
|
||||
|
||||
_Admin settings > Settings > Email_
|
||||
_Admin > Settings > Email_
|
||||
|
||||
For Metabase to send messages to people, you'll need to set up an email account to send emails via **SMTP** (simple mail transfer protocol). SMTP is an email standard that, when combined with SSL/TLS, provides security protection for emails.
|
||||
|
||||
|
||||
@@ -1306,6 +1306,34 @@ If Metabase stops sending notifications like alerts, it may be because long-runn
|
||||
queries are clogging the notification queue. You may be able to unclog the queue by
|
||||
increasing the size of the thread pool dedicated to notifications.
|
||||
|
||||
### `MB_OIDC_ALLOWED_NETWORKS`
|
||||
|
||||
- Type: keyword
|
||||
- Default: `allow-all`
|
||||
- [Configuration file name](./config-file.md): `oidc-allowed-networks`
|
||||
|
||||
What networks are OIDC requests allowed to? Possible values: 'allow-all' (default), 'allow-private', or 'external-only'.
|
||||
|
||||
### `MB_OIDC_PROVIDERS`
|
||||
|
||||
> Only available on Metabase [Pro](https://www.metabase.com/product/pro) and [Enterprise](https://www.metabase.com/product/enterprise) plans.
|
||||
|
||||
- Type: json
|
||||
- Default: `[]`
|
||||
- [Configuration file name](./config-file.md): `oidc-providers`
|
||||
|
||||
JSON containing OIDC provider configurations.
|
||||
|
||||
### `MB_OIDC_USER_PROVISIONING_ENABLED`
|
||||
|
||||
> Only available on Metabase [Pro](https://www.metabase.com/product/pro) and [Enterprise](https://www.metabase.com/product/enterprise) plans.
|
||||
|
||||
- Type: boolean
|
||||
- Default: `true`
|
||||
- [Configuration file name](./config-file.md): `oidc-user-provisioning-enabled`
|
||||
|
||||
When a user logs in via OIDC, create a Metabase account for them automatically if they don't have one.
|
||||
|
||||
### `MB_PERSISTED_MODEL_REFRESH_CRON_SCHEDULE`
|
||||
|
||||
- Type: string
|
||||
@@ -1938,6 +1966,13 @@ Identify the source of HTTP requests by this header's value, instead of its remo
|
||||
Fetch size for result sets. We want to ensure that the jdbc ResultSet objects are not realizing the entire results
|
||||
in memory.
|
||||
|
||||
### `MB_SQL_TOOLS_PARSER_BACKEND`
|
||||
|
||||
- Type: keyword
|
||||
- Default: `sqlglot`
|
||||
|
||||
Parser backend of `sql-tools` module.
|
||||
|
||||
### `MB_SSH_HEARTBEAT_INTERVAL_SEC`
|
||||
|
||||
- Type: integer
|
||||
@@ -2140,6 +2175,8 @@ Default: `null`
|
||||
|
||||
A JDBC-style connection URI that can be used instead of most of `MB_DB_*` like [MB_DB_HOST](#mb_db_host). Also used when certain Connection String parameters are required for the connection. The connection type requirement is the same as [MB_DB_TYPE](#mb_db_type).
|
||||
|
||||
Note that the `currentSchema` JDBC parameter has no effect. [The schema used for PostgreSQL application databases must be `public`](https://github.com/metabase/metabase/issues/37836).
|
||||
|
||||
Examples:
|
||||
|
||||
```
|
||||
|
||||
@@ -13,7 +13,7 @@ On Pro and Enterprise plans, you can customize the font Metabase uses (the defau
|
||||
|
||||
## Included fonts
|
||||
|
||||
To change your Metabase font, click on the **gear** icon in the upper right of the screen and select **Admin settings** > **Settings** > **Appearance**. Under **Font**, select from a list of included fonts.
|
||||
To change your Metabase font, click the **grid** icon in the upper right of the screen and select **Admin** > **Settings** > **Appearance**. Under **Font**, select from a list of included fonts.
|
||||
|
||||
- [Custom font](#custom-fonts)
|
||||
- [Lato](https://fonts.google.com/specimen/Lato)
|
||||
@@ -40,7 +40,7 @@ Comic Sans is currently not included.
|
||||
|
||||
## Custom fonts
|
||||
|
||||
To set a custom font, click on the **gear** icon in the upper right of the screen and select **Admin settings** > **Settings** > **Appearance**.
|
||||
To set a custom font, click the **grid** icon in the upper right of the screen and select **Admin** > **Settings** > **Appearance**.
|
||||
|
||||
For **Font**, select **Custom...** and enter URLs to your font files.
|
||||
|
||||
@@ -93,7 +93,7 @@ For latin text, we'd copy the relevant src URLs for each style, in this case:
|
||||
- For 400: [https://fonts.gstatic.com/s/robotomono/v21/L0xTDF4xlVMF-BfR8bXMIhJHg45mwgGEFl0_3vrtSM1J-gEPT5Ese6hmHSh0me8iUI0.woff2](https://fonts.gstatic.com/s/robotomono/v21/L0xTDF4xlVMF-BfR8bXMIhJHg45mwgGEFl0_3vrtSM1J-gEPT5Ese6hmHSh0me8iUI0.woff2)
|
||||
- For 700: [https://fonts.gstatic.com/s/robotomono/v21/L0xTDF4xlVMF-BfR8bXMIhJHg45mwgGEFl0_3vrtSM1J-gEPT5Ese6hmHSh0me8iUI0.woff2](https://fonts.gstatic.com/s/robotomono/v21/L0xTDF4xlVMF-BfR8bXMIhJHg45mwgGEFl0_3vrtSM1J-gEPT5Ese6hmHSh0me8iUI0.woff2)
|
||||
|
||||
And paste those URLS in the relevant input fields in **Admin settings** > **Settings** > **Appearance** > **Font** section where it says "Tell us where to find the file for each font weight. You don’t need to include all of them, but it’ll look better if you do."
|
||||
And paste those URLS in the relevant input fields in **Admin** > **Settings** > **Appearance** > **Font** section where it says "Tell us where to find the file for each font weight. You don’t need to include all of them, but it’ll look better if you do."
|
||||
|
||||
## Hosting fonts on GitHub
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ redirect_from:
|
||||
|
||||
Admins can update the localization settings for the instance:
|
||||
|
||||
1. Click on the **gear** icon in the upper right.
|
||||
2. Click **Admin settings**.
|
||||
1. Click the **grid** icon in the upper right.
|
||||
2. Click **Admin**.
|
||||
3. In the **Settings** tab, click on **Localization** in the left sidebar.
|
||||
|
||||
These localization settings allow you to set global language and formatting defaults for dates, times, numbers, and currencies.
|
||||
|
||||
@@ -11,7 +11,7 @@ Metabase logs quite a bit of information by default. Metabase uses [Log4j 2](htt
|
||||
|
||||
## View and download Metabase logs
|
||||
|
||||
You can find Metabase logs in **Admin settings** > **Tools** > **Logs**. You can filter the logs by keywords (for example, "sync") and download them as a text file.
|
||||
You can find Metabase logs in **Admin** > **Tools** > **Logs**. You can filter the logs by keywords (for example, "sync") and download them as a text file.
|
||||
|
||||
If you're running self-hosted Metabase, you'll also be able see the logs in the terminal.
|
||||
|
||||
@@ -25,7 +25,7 @@ Metabase uses [log4j](https://logging.apache.org/log4j/2.x/)for logging configur
|
||||
|
||||
### Temporary override logging configuration
|
||||
|
||||
To temporarily adjust the logging configuration, go to **Admin settings** > **Tools** > **Logs** and click on **Customize log levels**.
|
||||
To temporarily adjust the logging configuration, go to **Admin** > **Tools** > **Logs** and click on **Customize log levels**.
|
||||
|
||||
You can select from log level presets for common troubleshooting tasks (for example, troubleshooting sync issues), or provide your own configuration as a JSON. For example, here's an override configuration that increases logging for troubleshooting linked filters:
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ redirect_from:
|
||||
|
||||
This section contains settings for your whole instance, like its URL, the reporting timezone, and toggles for disabling or enabling some of Metabase's optional features.
|
||||
|
||||
You can configure these settings from **Settings** > **Admin Settings** > **General**.
|
||||
You can configure these settings by clicking the **grid icon** in the upper right, then going to **Admin** > **Settings** > **General**.
|
||||
|
||||
## Site name
|
||||
|
||||
@@ -44,11 +44,11 @@ This option turns determines whether or not you allow [anonymous data about your
|
||||
|
||||
## Friendly table and field names
|
||||
|
||||
By default, Metabase attempts to make field and table names more readable by changing things like `somehorriblename` to `Some Horrible Name`. This does not work well for languages other than English, or for fields that have lots of abbreviations or codes in them. If you'd like to turn this setting off, you can do so from the Admin Panel under **Settings** > **Admin settings** > **General**.
|
||||
By default, Metabase attempts to make field and table names more readable by changing things like `somehorriblename` to `Some Horrible Name`. This does not work well for languages other than English, or for fields that have lots of abbreviations or codes in them. If you'd like to turn this setting off, you can do so from the Admin Panel under **Admin** **> Settings** > **General**.
|
||||
|
||||
If you re-enable this setting, Metabase will run a [scan](../databases/sync-scan.md#how-database-scans-work) against your database to review your table and column names again.
|
||||
|
||||
To manually label field or table names in Metabase, check out the [Table Metadata](../data-modeling/metadata-editing.md) section in your admin settings. Metadata in the Table Metadata can be further curated in [models](../data-modeling/models.md).
|
||||
To manually label field or table names in Metabase, check out the [Table Metadata](../data-modeling/metadata-editing.md) section in your admin panel. Metadata in the Table Metadata can be further curated in [models](../data-modeling/models.md).
|
||||
|
||||
## Enable X-rays
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ Both admins and people with [settings access](../permissions/application.md#sett
|
||||
|
||||
For Metabase to post to your Slack channels, you’ll need to create a Slack App and make it available to Metabase.
|
||||
|
||||
From any Metabase page, go to **Admin settings** > **Settings** > **Notification channels** > **Slack**.
|
||||
From any Metabase page, go to **Admin** > **Settings** > **Notification channels** > **Slack**.
|
||||
|
||||
Click on **Open Slack Apps**. Metabase will open a new browser tab and send you over to the Slack website to create the Slack app.
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ For now, webhooks are only available for [alerts](../questions/alerts.md); you c
|
||||
|
||||
Before you can send an alert to a webhook, an admin will need to create a webhook to target.
|
||||
|
||||
Admins and people with [settings access](../permissions/application.md#settings-access) can create a webhook by clicking the **gear icon** > **Admin settings** > **Notification channels**. In **Webhooks for alerts**, click **+ Add another**. Metabase will show you a short form to fill out:
|
||||
Admins and people with [settings access](../permissions/application.md#settings-access) can create a webhook by clicking the **grid icon** > **Admin** > **Notification channels**. In **Webhooks for alerts**, click **+ Add another**. Metabase will show you a short form to fill out:
|
||||
|
||||
- **Webhook URL**. Where you want Metabase to send the results of an alert. Must be a valid URL.
|
||||
- **Give it a name**. Name the webhook so that people can pick the right hook when setting up an alert on a question.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: Editable tables
|
||||
title: Editable tables
|
||||
summary: Admins can create, update, and delete data in tables right in Metabase.
|
||||
---
|
||||
|
||||
@@ -23,8 +23,8 @@ Currently, editable tables are available for the following databases:
|
||||
|
||||
To set up table editing, admins can:
|
||||
|
||||
1. Click the **gear** icon in the top right.
|
||||
2. Select **Admin settings**.
|
||||
1. Click the **grid** icon in the top right.
|
||||
2. Select **Admin**.
|
||||
3. Click **Databases**.
|
||||
4. Select your database.
|
||||
5. Toggle on **Editable table data**.
|
||||
|
||||
@@ -15,7 +15,7 @@ People all around the world use Metabase, and everyone has different preferences
|
||||
|
||||
## Field formatting
|
||||
|
||||
_Admin settings > Table Metadata > Database > Table > Field > Formatting_
|
||||
_Admin > Table Metadata > Database > Table > Field > Formatting_
|
||||
|
||||
You can override the global defaults for a specific field by going to the `Table Metadata` section of the Admin Panel. Select the database and table of the field in question, then click scroll down to the **Formatting**.
|
||||
|
||||
@@ -25,7 +25,7 @@ The options you'll see here will depend on the field's data type and it's [seman
|
||||
|
||||
## Text formatting options
|
||||
|
||||
_Admin settings > Table Metadata > Database > Table > Field > Formatting_
|
||||
_Admin > Table Metadata > Database > Table > Field > Formatting_
|
||||
|
||||
Options depend on the [semantic type](./semantic-types.md) you select for the field.
|
||||
|
||||
@@ -55,7 +55,7 @@ https://www.google.com/search?q=askew
|
||||
|
||||
## Dates and times
|
||||
|
||||
_Admin settings > Table Metadata > Database > Table > Field > Formatting_
|
||||
_Admin > Table Metadata > Database > Table > Field > Formatting_
|
||||
|
||||
Options depend on the [semantic type](./semantic-types.md) you select for the field.
|
||||
|
||||
@@ -99,7 +99,7 @@ Choose between 12-hour and 24-hour time format:
|
||||
|
||||
## Numbers
|
||||
|
||||
_Admin settings > Table Metadata > Database > Table > Field > Formatting_
|
||||
_Admin > Table Metadata > Database > Table > Field > Formatting_
|
||||
|
||||
Options depend on the [semantic type](./semantic-types.md) you select for the field.
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ And here are the values as seen in the table:
|
||||
|
||||
This unfolding allows you to filter for values found in the original JSON object.
|
||||
|
||||
Metabase will prefix the unfolded column names with the name of the original column that contained the JSON. You can change the column names in **Admin settings** > [Table metadata](metadata-editing.md), or by creating a [model](./models.md) and editing the column metadata.
|
||||
Metabase will prefix the unfolded column names with the name of the original column that contained the JSON. You can change the column names in **Admin** > [Table metadata](metadata-editing.md), or by creating a [model](./models.md) and editing the column metadata.
|
||||
|
||||
## Toggling JSON unfolding for a database
|
||||
|
||||
@@ -35,8 +35,8 @@ If you notice a hit to performance from this JSON unfolding, we recommend turnin
|
||||
|
||||
To turn off JSON unfolding for a database:
|
||||
|
||||
1. Click on the **Gear** in the upper right.
|
||||
2. Select **Admin settings**
|
||||
1. Click the **grid** icon in the upper right.
|
||||
2. Select **Admin**
|
||||
3. Visit the **Databases** tab.
|
||||
4. Select the relevant database.
|
||||
5. Click **Show advanced options**.
|
||||
@@ -49,8 +49,8 @@ To turn off JSON unfolding for a database:
|
||||
|
||||
If performance degrades, or you'd rather keep the JSON contained in the original column, you can turn off unfolding for individual fields in their settings.
|
||||
|
||||
1. Click on the **Gear** in the upper right.
|
||||
2. Select **Admin settings**.
|
||||
1. Click the **grid** icon in the upper right.
|
||||
2. Select **Admin**.
|
||||
3. Visit the **Table metadata** tab.
|
||||
4. Select the database that contains the field you want to update.
|
||||
5. Select the table that contains the field.
|
||||
@@ -67,9 +67,9 @@ For example, if you upload a CSV with JSON in it, you might need to update the d
|
||||
## Databases that support JSON unfolding
|
||||
|
||||
- [BigQuery](../databases/connections/postgresql.md): automatically enabled, applies to `STRUCT` types only.
|
||||
|
||||
|
||||
If your data is stored in the [STRUCT data type](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#struct_type) in BigQuery, you can query the table's nested fields. This is enabled by default. However, Metabase won't unfold JSON stored in BigQuery as the `JSON` type. This is because in BigQuery, nested fields are _part of the table definition itself_, so when Metabase syncs with your BigQuery database, it'll be able to get metadata about any of your tables, including tables with nested fields. Querying nested fields, however, doesn't extend to arrays (REPEATED (STRUCT)) in BigQuery.
|
||||
|
||||
|
||||
- [Druid (JDBC)](../databases/connections/druid.md)
|
||||
- [MongoDB](../databases/connections/mysql.md): automatically enabled for all nested fields.
|
||||
- [MySQL](../databases/connections/mysql.md)
|
||||
|
||||
@@ -6,7 +6,7 @@ redirect_from:
|
||||
|
||||
# Table metadata admin settings
|
||||
|
||||
_Admin settings > Table metadata_
|
||||
_Admin > Table metadata_
|
||||
|
||||

|
||||
|
||||
@@ -18,7 +18,7 @@ Admins can grant access to these metadata settings to other groups. See [table m
|
||||
|
||||
## Table settings
|
||||
|
||||
_Admin settings > Table metadata > Database > Table_
|
||||
_Admin > Table metadata > Database > Table_
|
||||
|
||||
You can search for tables, or use the tree navigation to find each of your connected databases and their tables.
|
||||
|
||||
@@ -65,7 +65,7 @@ To hide **all of the tables in a database** (say, if you've migrated to a new da
|
||||
|
||||
## Field settings
|
||||
|
||||
_Admin settings > Table Metadata > Database > Table > Field_
|
||||
_Admin > Table Metadata > Database > Table > Field_
|
||||
|
||||
Fields are also known as Columns (see the [difference between fields and columns](https://www.metabase.com/learn/grow-your-data-skills/data-fundamentals/database-basics#columns-vs-fields)).
|
||||
|
||||
@@ -91,13 +91,13 @@ Click the preview button to see sample data from that field.
|
||||
|
||||
## Scan values for a specific field
|
||||
|
||||
_Admin settings > Table Metadata > Database > Table > Field_
|
||||
_Admin > Table Metadata > Database > Table > Field_
|
||||
|
||||
To scan or discard field values for a specific field, click on the **Field values** button. Metabase uses these values to populate dropdown menus in filter widgets. (Values aren't dropped from your database.)
|
||||
|
||||
## Field data
|
||||
|
||||
_Admin settings > Table Metadata > Database > Table > Field_
|
||||
_Admin > Table Metadata > Database > Table > Field_
|
||||
|
||||
### Field name
|
||||
|
||||
@@ -147,7 +147,7 @@ If Metabase doesn't support the casting option you need, you can [create a SQL q
|
||||
|
||||
## Field metadata
|
||||
|
||||
_Admin settings > Table Metadata > Database > Table > Field_
|
||||
_Admin > Table Metadata > Database > Table > Field_
|
||||
|
||||
### Semantic type
|
||||
|
||||
@@ -159,7 +159,7 @@ See [semantic types](../data-modeling/semantic-types.md).
|
||||
|
||||
## Field behavior
|
||||
|
||||
_Admin settings > Table Metadata > Database > Table > Field_
|
||||
_Admin > Table Metadata > Database > Table > Field_
|
||||
|
||||
### Field visibility
|
||||
|
||||
@@ -208,7 +208,7 @@ Select "Custom mapping" from the dropdown menu. For each value in the column, en
|
||||
|
||||
## Field formatting
|
||||
|
||||
_Admin settings > Table Metadata > Database > Table > Field_
|
||||
_Admin > Table Metadata > Database > Table > Field_
|
||||
|
||||
See [Field formatting](./formatting.md#field-formatting).
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ To persist models for faster loading, you'll need to turn on model persistence f
|
||||
|
||||
### Turn on model persistence for your Metabase
|
||||
|
||||
To turn on model persistence for your Metabase, go to **Admin settings** > **Performance** > **Model persistence**.
|
||||
To turn on model persistence for your Metabase, go to **Admin** > **Performance** > **Model persistence**.
|
||||
|
||||
You can set models to refresh based on one of the default frequencies (every 1 hour, 2 hours, etc.), or select the **Custom** option to use [cron syntax](https://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html) to specify your own update frequency.
|
||||
|
||||
@@ -46,7 +46,7 @@ If someone [changes the query definition of a model](./models.md#edit-a-models-q
|
||||
|
||||
Once you've turned on model persistence for your Metabase, you'll need to set it up for each specific database, as Metabase will need to create a schema in your data warehouse to store the persisted models.
|
||||
|
||||
1. Go to **Admin settings** > **Databases** > [your database] > **Turn model persistence on**. If the credentials you've given Metabase to connect to your database are permissive, Metabase should do all the work for you: Metabase will check if the schema already exists, or otherwise attempt to create it. If the connection's credentials _lack_ the necessary permissions to create the schema in your database, you'll need to create the schema in the database yourself.
|
||||
1. Go to **Admin** > **Databases** > [your database] > **Turn model persistence on**. If the credentials you've given Metabase to connect to your database are permissive, Metabase should do all the work for you: Metabase will check if the schema already exists, or otherwise attempt to create it. If the connection's credentials _lack_ the necessary permissions to create the schema in your database, you'll need to create the schema in the database yourself.
|
||||
|
||||
2. To manually create the schema in your data warehouse, click on the **info icon** to get the schema name.
|
||||
|
||||
@@ -68,7 +68,7 @@ To refresh a model's results, go to the model and click on the three-dot menu (*
|
||||
|
||||
## View model persistence logs
|
||||
|
||||
You can view the logs for model persistence by clicking on the **gear** icon in the upper right and selecting **Admin settings** > **Tools** > **Model caching logs**. See [Admin tools](../usage-and-performance-tools/tools.md).
|
||||
You can view the logs for model persistence by clicking on the **grid** icon in the upper right and selecting **Admin** > **Tools** > **Model caching logs**. See [Admin tools](../usage-and-performance-tools/tools.md).
|
||||
|
||||
## Difference between persisted models and caching
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ There are some limitations to this indexing:
|
||||
|
||||
- The indexed field must be a text/string type.
|
||||
- The record containing the field must have an integer entity key.
|
||||
- To keep your search speedy, Metabase will only index 5000 unique values from that field, so this option isn't the best choice to turn on for tables with a ton of records.
|
||||
- To keep your search speedy, Metabase will only index 25,000 unique values from that field, so this option isn't the best choice to turn on for tables with a ton of records.
|
||||
|
||||
## Edit a model's query
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ Metabase allows admins to create segments so people can quickly and easily refer
|
||||
|
||||
To manage segments:
|
||||
|
||||
1. Click the **gear** icon in the upper right.
|
||||
2. Select **Admin setting**.
|
||||
1. Click the **grid** icon in the upper right.
|
||||
2. Select **Admin**.
|
||||
3. Go to the **Table Metadata** tab.
|
||||
4. Click **Segments** at the bottom left.
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ You can think of semantic types as adding meaning and context to a field to comm
|
||||
|
||||
## Editing data and semantic types
|
||||
|
||||
Admins, and people with [permission to manage table metadata](../permissions/data.md#manage-table-metadata-permissions), can cast data types and edit semantic types in the Admin settings' Table Metadata tab.
|
||||
Admins, and people with [permission to manage table metadata](../permissions/data.md#manage-table-metadata-permissions), can cast data types and edit semantic types in the Admin's Table Metadata tab.
|
||||
|
||||
### Cast data types
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ Segments are saved filters on tables. You can use segments to create an official
|
||||
|
||||
People will see segments as options in the Filter block of the [query builder](../questions/query-builder/editor.md).
|
||||
|
||||
For now, in addition to Data Studio, segments can also be managed through **Admin settings > Table Metadata**, see [Segments in table metadata](../data-modeling/segments.md).
|
||||
For now, in addition to Data Studio, segments can also be managed through **Admin > Table Metadata**, see [Segments in table metadata](../data-modeling/segments.md).
|
||||
|
||||
To see all segments on a table, select the table in **Data Structure** and switch to **Segments** tab.
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ docker run -d \
|
||||
|
||||
### Metabase environment variables
|
||||
|
||||
These settings can also be configured in the Metabase UI at **Admin Settings** > **Settings** > **Python Runner** (`/admin/settings/python-runner`). Note that environment variables take precedence over UI settings.
|
||||
These settings can also be configured in the Metabase UI at **Admin** > **Settings** > **Python Runner** (`/admin/settings/python-runner`). Note that environment variables take precedence over UI settings.
|
||||
|
||||
| Variable | Description |
|
||||
| ----------------------------------------- | ---------------------------------------------------------------------------------- |
|
||||
|
||||
@@ -45,6 +45,44 @@ Currently, you can't convert between different transform types (like converting
|
||||
|
||||
6. Optionally, assign tags to your transforms. Tags are used by [jobs](jobs-and-runs.md) to run transforms on schedule.
|
||||
|
||||
## Variables in SQL transforms
|
||||
|
||||
SQL transforms support [variables](../../questions/native-editor/sql-parameters.md) (`{% raw %}{{my_variable}} {% endraw %}`), which are only useful when combined with [snippets](../../questions/native-editor/snippets.md).
|
||||
|
||||
For (a really simple) example, let's say you want to count rows per week across several tables. You could create a snippet called "rows per week" containing the full query with a `{% raw %}{{table}}{% endraw %}` variable:
|
||||
|
||||
```sql
|
||||
{% raw %}
|
||||
SELECT
|
||||
date_trunc('week', created_at) AS week,
|
||||
count(*) AS row_count
|
||||
FROM {{table}}
|
||||
GROUP BY week
|
||||
ORDER BY week
|
||||
{% endraw %}
|
||||
```
|
||||
|
||||
That way you can use the snippet to create multiple transforms, each sourced from different tables. Each transform's entire SQL is just:
|
||||
|
||||
```sql
|
||||
{% raw %}{{snippet: rows per week}}{% endraw %}
|
||||
```
|
||||
|
||||
In each transform's variable panel, set the default value of `table` to the target table (e.g., `orders`, `returns`, `subscriptions`). See [table variables](../../questions/native-editor/table-variables.md).
|
||||
|
||||
If you ever need to change the query (say, to switch from weekly to daily granularity), you update the snippet once and every transform picks up the change on its next run.
|
||||
|
||||
### Transform variable must be optional, or have a default value
|
||||
|
||||
Parameters in transforms must either:
|
||||
|
||||
- Wrap the variable in optional blocks (`[[ ]]`).
|
||||
- Supply a default value.
|
||||
|
||||
The reason transform variables must have a default value (or be optional) is that transforms run on a schedule, so there's no way to pass a value to the variable when the job runs the transform.
|
||||
|
||||
The incremental `{%raw%}[[WHERE id > {{checkpoint}}]]{% endraw %}` pattern shown in [Incremental query transforms](#incremental-query-transforms) is an example of this an optional variable in practice. See also [optional variables](../../questions/native-editor/optional-variables.md).
|
||||
|
||||
## Run a query transform
|
||||
|
||||
See [Run a transform](transforms-overview.md#run-a-transform). You'll see logs for a transform run on the transform's page.
|
||||
|
||||
@@ -59,7 +59,7 @@ _Data Studio > Transforms_
|
||||
You can see all your Metabase's transforms:
|
||||
|
||||
1. Make sure you have [appropriate permissions to see transforms](#permissions-for-transforms).
|
||||
2. Click on the **grid** icon on top right and go to **Data Studio**.
|
||||
2. Click the **grid** icon on top right and go to **Data Studio**.
|
||||
3. In the left sidebar, select **Transforms**.
|
||||
|
||||
## Create a transform
|
||||
@@ -71,7 +71,7 @@ If you're using remote sync, you won't be able to create transforms if your inst
|
||||
To create a transform:
|
||||
|
||||
1. Make sure you have [appropriate permissions for creating transforms](#permissions-for-transforms).
|
||||
2. Click on the **grid** icon on top right and go to **Data Studio**.
|
||||
2. Click the **grid** icon on top right and go to **Data Studio**.
|
||||
3. In the left sidebar, select **Transforms**.
|
||||
4. Click **+ New** and select a source for your transform.
|
||||
|
||||
@@ -85,6 +85,8 @@ To create a transform:
|
||||
|
||||
See [query-based transforms](query-transforms.md) and [Python transforms](python-transforms.md) for more information. You can reference target tables of other transforms when writing your transform.
|
||||
|
||||
If you're writing a SQL transform, variables _must_ be wrapped in optional blocks (`[[ ]]`), or given a default value. See [variables in SQL transforms](query-transforms.md#variables-in-sql-transforms) for more details.
|
||||
|
||||
If you have the Metabot AI add-on, you can [use Metabot](#use-metabot-to-generate-code-for-transforms) to generate code for your transform.
|
||||
|
||||
6. Click **Save** in the top right corner and fill out the transform information:
|
||||
@@ -191,13 +193,13 @@ Incremental transforms work differently for query-based transforms and Python tr
|
||||
|
||||
## Versioning transforms
|
||||
|
||||
_Admin settings > General > Remote sync_
|
||||
_Admin > General > Remote sync_
|
||||
|
||||
You can check your transforms into git with [Remote Sync](../../installation-and-operation/remote-sync.md). If you enable transform sync, Metabase will serialize transforms as YAML files and push them to your specified GitHub repo branch.
|
||||
|
||||
To enable git sync of transforms:
|
||||
|
||||
1. Go to Admin settings by click on the **grid** icon in top right and select **Admin**.
|
||||
1. Go to Admin settings by click the **grid** icon in top right and select **Admin**.
|
||||
2. On the **General** tab, pick **Remote sync** in the left sidebar.
|
||||
3. Follow the steps to [Set up Remote Sync](../../installation-and-operation/remote-sync.md), and toggle "Transforms" sync on.
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ Connect Metabase to your data sources.
|
||||
|
||||
## Adding a database connection
|
||||
|
||||
To add a database connection, click on the **gear** icon in the top right, and navigate to **Admin settings** > **Databases** > **Add a database**.
|
||||
To add a database connection, click the **grid** icon in the top right, and navigate to **Admin** > **Databases** > **Add a database**.
|
||||
|
||||
Fill out the fields for that database, and click **Save changes** at the bottom.
|
||||
|
||||
@@ -65,11 +65,11 @@ See [Syncing and scanning](./sync-scan.md).
|
||||
|
||||
**Caution: Deleting a database is irreversible! All saved questions and dashboard cards based on the database will be deleted as well!**
|
||||
|
||||
Go to **Admin settings** > **Databases** > your database and click **Remove this database**.
|
||||
Go to **Admin** > **Databases** > your database and click **Remove this database**.
|
||||
|
||||
## Restoring the Sample Database
|
||||
|
||||
If you've deleted the Metabase [Sample Database](https://www.metabase.com/glossary/sample-database), go to **Admin settings** > **Databases** and click **Bring the Sample Database back**.
|
||||
If you've deleted the Metabase [Sample Database](https://www.metabase.com/glossary/sample-database), go to **Admin** > **Databases** and click **Bring the Sample Database back**.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ title: Amazon Athena
|
||||
|
||||
# Amazon Athena
|
||||
|
||||
To add a database connection, click on the **gear** icon in the top right, and navigate to **Admin settings** > **Databases** > **Add a database**.
|
||||
To add a database connection, click the **grid** icon in the top right, and navigate to **Admin** > **Databases** > **Add a database**.
|
||||
|
||||
## Connection and sync
|
||||
|
||||
@@ -209,6 +209,12 @@ If Metabase also needs to create tables, you'll need additional AWS Glue permiss
|
||||
}
|
||||
```
|
||||
|
||||
## Writeable connection
|
||||
|
||||
{% include plans-blockquote.html feature="Writeable connection" is_plural=true%}
|
||||
|
||||
Set up an additional connection used for write operations. See [Writeable connections](../writeable-connection.md).
|
||||
|
||||
## Model features
|
||||
|
||||
There aren't (yet) any model features available for Athena.
|
||||
|
||||
@@ -6,7 +6,7 @@ redirect_from:
|
||||
|
||||
# Google BigQuery
|
||||
|
||||
To add a database connection, click on the **gear** icon in the top right, and navigate to **Admin settings** > **Databases** > **Add a database**.
|
||||
To add a database connection, click the **grid** icon in the top right, and navigate to **Admin** > **Databases** > **Add a database**.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
@@ -161,6 +161,12 @@ FROM [my_dataset.my_table]
|
||||
|
||||
If you're having trouble with your BigQuery connection, you can check out this [troubleshooting guide](../../troubleshooting-guide/bigquery-drive.md) that covers BigQuery issues, [this one](../../troubleshooting-guide/db-connection.md) on data warehouse connections, or visit [Metabase's discussion forum](https://discourse.metabase.com/search?q=bigquery) to see if someone has encountered and resolved a similar issue.
|
||||
|
||||
## Writeable connection
|
||||
|
||||
{% include plans-blockquote.html feature="Writeable connection" is_plural=true%}
|
||||
|
||||
Set up an additional connection used for write operations. See [Writeable connections](../writeable-connection.md).
|
||||
|
||||
## Model features
|
||||
|
||||
There aren't (yet) any model features available for BigQuery.
|
||||
|
||||
@@ -5,7 +5,7 @@ description: Learn how to connect Metabase to your ClickHouse database, includin
|
||||
|
||||
# ClickHouse
|
||||
|
||||
To add a database connection, click on the **gear** icon in the top right, and navigate to **Admin settings** > **Databases** > **Add a database**.
|
||||
To add a database connection, click the **grid** icon in the top right, and navigate to **Admin** > **Databases** > **Add a database**.
|
||||
|
||||
You can edit these settings at any time. Just remember to save your changes.
|
||||
|
||||
@@ -101,6 +101,12 @@ Turn this option **ON** to scan a sample of values every time Metabase runs a [s
|
||||
|
||||
A fingerprinting query examines the first 10,000 rows from each column and uses that data to guesstimate how many unique values each column has, what the minimum and maximum values are for numeric and timestamp columns, and so on. If you leave this option **OFF**, Metabase will only fingerprint your columns once during setup.
|
||||
|
||||
## Writeable connection
|
||||
|
||||
{% include plans-blockquote.html feature="Writeable connection" is_plural=true%}
|
||||
|
||||
Set up an additional connection used for write operations. See [Writeable connections](../writeable-connection.md).
|
||||
|
||||
## Model features
|
||||
|
||||
There aren't (yet) any model features for ClickHouse.
|
||||
|
||||
@@ -4,7 +4,7 @@ title: Databricks
|
||||
|
||||
# Databricks
|
||||
|
||||
To add a database connection, click on the **gear** icon in the top right, and navigate to **Admin settings** > **Databases** > **Add a database**. Then select **Databricks**.
|
||||
To add a database connection, click the **grid** icon in the top right, and navigate to **Admin** > **Databases** > **Add a database**. Then select **Databricks**.
|
||||
|
||||
You can edit these settings at any time. Just remember to save your changes.
|
||||
|
||||
@@ -101,6 +101,12 @@ Turn this option **ON** to scan a sample of values every time Metabase runs a [s
|
||||
|
||||
A fingerprinting query examines the first 10,000 rows from each column and uses that data to guesstimate how many unique values each column has, what the minimum and maximum values are for numeric and timestamp columns, and so on. If you leave this option **OFF**, Metabase will only fingerprint your columns once during setup.
|
||||
|
||||
## Writeable connection
|
||||
|
||||
{% include plans-blockquote.html feature="Writeable connection" is_plural=true%}
|
||||
|
||||
Set up an additional connection used for write operations. See [Writeable connections](../writeable-connection.md).
|
||||
|
||||
## Model features
|
||||
|
||||
There aren't (yet) any model features available for Databricks.
|
||||
|
||||
@@ -6,7 +6,7 @@ title: Druid
|
||||
|
||||
> Prefer the Druid JDBC connection.
|
||||
|
||||
To add a database connection, click on the **gear** icon in the top right, and navigate to **Admin settings** > **Databases** > **Add a database**.
|
||||
To add a database connection, click the **grid** icon in the top right, and navigate to **Admin** > **Databases** > **Add a database**.
|
||||
|
||||
## Edit connection details
|
||||
|
||||
@@ -64,6 +64,12 @@ Turn this option **ON** to scan a sample of values every time Metabase runs a [s
|
||||
|
||||
A fingerprinting query examines the first 10,000 rows from each column and uses that data to guesstimate how many unique values each column has, what the minimum and maximum values are for numeric and timestamp columns, and so on. If you leave this option **OFF**, Metabase will only fingerprint your columns once during setup.
|
||||
|
||||
## Writeable connection
|
||||
|
||||
{% include plans-blockquote.html feature="Writeable connection" is_plural=true%}
|
||||
|
||||
Set up an additional connection used for write operations. See [Writeable connections](../writeable-connection.md).
|
||||
|
||||
## Model features
|
||||
|
||||
There aren't (yet) any model features available for Druid.
|
||||
|
||||
@@ -6,7 +6,7 @@ title: MariaDB
|
||||
|
||||
> This page covers connecting to MariaDB as a _data warehouse_. For using MariaDB as Metabase's _application database_, see [Configuring the Metabase application database](../../installation-and-operation/configuring-application-database.md).
|
||||
|
||||
To add a database connection, click on the **gear** icon in the top right, and navigate to **Admin settings** > **Databases** > **Add a database**.
|
||||
To add a database connection, click the **grid** icon in the top right, and navigate to **Admin** > **Databases** > **Add a database**.
|
||||
|
||||
MariaDB shares a driver with MySQL, so select the **MySQL** driver.
|
||||
|
||||
@@ -101,6 +101,12 @@ JSON schema inference doesn't work with MariaDB, due to implementation differenc
|
||||
|
||||
If your password contains characters that aren't UTF-8, then you might need to add an additional variable to the connection string `passwordCharacterEncoding=<your_encoding_here>`. This ensures that MariaDB understands the special characters in the password during authentication.
|
||||
|
||||
## Writeable connection
|
||||
|
||||
{% include plans-blockquote.html feature="Writeable connection" is_plural=true%}
|
||||
|
||||
Set up an additional connection used for write operations. See [Writeable connections](../writeable-connection.md).
|
||||
|
||||
## Database routing
|
||||
|
||||
With database routing, an admin can build a question once using one database, and the question will run its query against a different database with the same schema depending on who is viewing the question.
|
||||
|
||||
@@ -6,7 +6,7 @@ redirect_from:
|
||||
|
||||
# MongoDB
|
||||
|
||||
To add a database connection, click on the **gear** icon in the top right, and navigate to **Admin settings** > **Databases** > **Add a database**.
|
||||
To add a database connection, click the **grid** icon in the top right, and navigate to **Admin** > **Databases** > **Add a database**.
|
||||
|
||||
## Supported versions
|
||||
|
||||
@@ -130,6 +130,12 @@ Instead, Metabase gets a sample of the fields in a collection by scanning a samp
|
||||
|
||||
If you're not seeing all of the fields show up for a collection in Metabase, one workaround is to include all possible keys in the first document of the collection, and give those keys null values. That way, Metabase will be able to recognize the correct schema for the entire collection.
|
||||
|
||||
## Writeable connection
|
||||
|
||||
{% include plans-blockquote.html feature="Writeable connection" is_plural=true%}
|
||||
|
||||
Set up an additional connection used for write operations. See [Writeable connections](../writeable-connection.md).
|
||||
|
||||
## Database routing
|
||||
|
||||
With database routing, an admin can build a question once using one database, and the question will run its query against a different database with the same schema depending on who is viewing the question.
|
||||
|
||||
@@ -8,7 +8,7 @@ redirect_from:
|
||||
|
||||
> This page covers connecting to MySQL as a _data warehouse_. For using MySQL as Metabase's _application database_, see [Configuring the Metabase application database](../../installation-and-operation/configuring-application-database.md).
|
||||
|
||||
To add a database connection, click on the **gear** icon in the top right, and navigate to **Admin settings** > **Databases** > **Add a database**.
|
||||
To add a database connection, click the **grid** icon in the top right, and navigate to **Admin** > **Databases** > **Add a database**.
|
||||
|
||||
## Supported versions
|
||||
|
||||
@@ -187,6 +187,12 @@ mysql:
|
||||
|
||||
If your password contains characters that aren't UTF-8, then you might need to add an additional variable to the connection string `passwordCharacterEncoding=<your_encoding_here>`. This ensures that MySQL understands the special characters in the password during authentication.
|
||||
|
||||
## Writeable connection
|
||||
|
||||
{% include plans-blockquote.html feature="Writeable connection" is_plural=true%}
|
||||
|
||||
Set up an additional connection used for write operations. See [Writeable connections](../writeable-connection.md).
|
||||
|
||||
## Model features
|
||||
|
||||
Choose whether to enable features related to [Metabase models](../../data-modeling/models.md). These features will often require that the database user account, the one you use to connect to your database, has both read and write privileges.
|
||||
|
||||
@@ -6,7 +6,7 @@ redirect_from:
|
||||
|
||||
# Oracle
|
||||
|
||||
To add a database connection, click on the **gear** icon in the top right, and navigate to **Admin settings** > **Databases** > **Add a database**.
|
||||
To add a database connection, click the **grid** icon in the top right, and navigate to **Admin** > **Databases** > **Add a database**.
|
||||
|
||||
## Supported versions
|
||||
|
||||
@@ -131,6 +131,12 @@ For example, if you're running Metabase from a directory called `/app/`, you sho
|
||||
|
||||
The process for adding plugins when running via Docker is similar, but you'll need to mount the `plugins` directory. Refer to instructions [here](../../installation-and-operation/running-metabase-on-docker.md#adding-external-dependencies-or-plugins) for more details.
|
||||
|
||||
## Writeable connection
|
||||
|
||||
{% include plans-blockquote.html feature="Writeable connection" is_plural=true%}
|
||||
|
||||
Set up an additional connection used for write operations. See [Writeable connections](../writeable-connection.md).
|
||||
|
||||
## Danger zone
|
||||
|
||||
See [Danger zone](../danger-zone.md).
|
||||
|
||||
@@ -8,7 +8,7 @@ redirect_from:
|
||||
|
||||
> This page covers connecting to PostgreSQL as a _data warehouse_. For using PostgreSQL as Metabase's _application database_, see [Configuring the Metabase application database](../../installation-and-operation/configuring-application-database.md).
|
||||
|
||||
To add a database connection, click on the **gear** icon in the top right, and navigate to **Admin settings** > **Databases** > **Add a database**.
|
||||
To add a database connection, click the **grid** icon in the top right, and navigate to **Admin** > **Databases** > **Add a database**.
|
||||
|
||||
Fill out the fields for that database, and click **Save changes** at the bottom.
|
||||
|
||||
@@ -186,6 +186,12 @@ Turn this option **ON** to scan a sample of values every time Metabase runs a [s
|
||||
|
||||
A fingerprinting query examines the first 10,000 rows from each column and uses that data to guesstimate how many unique values each column has, what the minimum and maximum values are for numeric and timestamp columns, and so on. If you leave this option **OFF**, Metabase will only fingerprint your columns once during setup.
|
||||
|
||||
## Writeable connection
|
||||
|
||||
{% include plans-blockquote.html feature="Writeable connection" is_plural=true%}
|
||||
|
||||
Set up an additional connection used for write operations. See [Writeable connections](../writeable-connection.md).
|
||||
|
||||
## Model features
|
||||
|
||||
Choose whether to enable features related to [Metabase models](../../data-modeling/models.md). These features will often require that the database user account, the one you use to connect to your database, has both read and write privileges.
|
||||
|
||||
@@ -4,7 +4,7 @@ title: Presto
|
||||
|
||||
# Presto
|
||||
|
||||
To add a database connection, click on the **gear** icon in the top right, and navigate to **Admin settings** > **Databases** > **Add a database**.
|
||||
To add a database connection, click the **grid** icon in the top right, and navigate to **Admin** > **Databases** > **Add a database**.
|
||||
|
||||
Fill out the fields for that database, and click **Save changes** at the bottom.
|
||||
|
||||
@@ -94,6 +94,12 @@ Turn this option **ON** to scan a sample of values every time Metabase runs a [s
|
||||
|
||||
A fingerprinting query examines the first 10,000 rows from each column and uses that data to guesstimate how many unique values each column has, what the minimum and maximum values are for numeric and timestamp columns, and so on. If you leave this option **OFF**, Metabase will only fingerprint your columns once during setup.
|
||||
|
||||
## Writeable connection
|
||||
|
||||
{% include plans-blockquote.html feature="Writeable connection" is_plural=true%}
|
||||
|
||||
Set up an additional connection used for write operations. See [Writeable connections](../writeable-connection.md).
|
||||
|
||||
## Database routing
|
||||
|
||||
With database routing, an admin can build a question once using one database (data catalog), and the question will run its query against a different data catalog with the same schema depending on who is viewing the question.
|
||||
|
||||
@@ -7,7 +7,7 @@ redirect_from:
|
||||
|
||||
# Amazon Redshift
|
||||
|
||||
To add a database connection, click on the **gear** icon in the top right, and navigate to **Admin settings** > **Databases** > **Add a database**.
|
||||
To add a database connection, click the **grid** icon in the top right, and navigate to **Admin** > **Databases** > **Add a database**.
|
||||
|
||||
## Connection and sync
|
||||
|
||||
@@ -98,6 +98,12 @@ Turn this option **ON** to scan a sample of values every time Metabase runs a [s
|
||||
|
||||
A fingerprinting query examines the first 10,000 rows from each column and uses that data to guesstimate how many unique values each column has, what the minimum and maximum values are for numeric and timestamp columns, and so on. If you leave this option **OFF**, Metabase will only fingerprint your columns once during setup.
|
||||
|
||||
## Writeable connection
|
||||
|
||||
{% include plans-blockquote.html feature="Writeable connection" is_plural=true%}
|
||||
|
||||
Set up an additional connection used for write operations. See [Writeable connections](../writeable-connection.md).
|
||||
|
||||
## Model features
|
||||
|
||||
Choose whether to enable features related to Metabase models. These will often require a write connection.
|
||||
|
||||
@@ -6,7 +6,7 @@ redirect_from:
|
||||
|
||||
# Snowflake
|
||||
|
||||
To add a database connection, click on the **gear** icon in the top right, and navigate to **Admin settings** > **Databases** > **Add a database**.
|
||||
To add a database connection, click the **grid** icon in the top right, and navigate to **Admin** > **Databases** > **Add a database**.
|
||||
|
||||
You can edit these settings at any time. Just remember to save your changes.
|
||||
|
||||
@@ -14,17 +14,17 @@ You can edit these settings at any time. Just remember to save your changes.
|
||||
|
||||
Paste a connection string here to pre-fill the remaining fields below.
|
||||
|
||||
## Display name
|
||||
### Display name
|
||||
|
||||
The display name for the database in the Metabase interface.
|
||||
|
||||
## Account or hostname
|
||||
### Account or hostname
|
||||
|
||||
You have the option of using an account name or hostname. Your hostname is your database's IP address or its domain name (e.g., database.example.com).
|
||||
|
||||
If your URL includes your database's region, you can connect using an account name, otherwise connect using a hostname.
|
||||
|
||||
### AWS
|
||||
#### AWS
|
||||
|
||||
Connect with an account name.
|
||||
|
||||
@@ -37,7 +37,7 @@ You'd enter `az12345.ca-central-1.aws` as the account name in Metabase.
|
||||
|
||||
Not all regions require the cloud platform identifier. If you're in `us-west-2`, you would enter `az12345` as the account name. For the requirements per region, see [the official Snowflake's documentation](https://docs.snowflake.com/en/user-guide/admin-account-identifier.html#non-vps-account-locator-formats-by-cloud-platform-and-region).
|
||||
|
||||
### app.snowflake.com
|
||||
#### app.snowflake.com
|
||||
|
||||
Connect with a host name.
|
||||
|
||||
@@ -47,31 +47,31 @@ You can get your host name by going to Admin > Accounts. The Account name will b
|
||||
|
||||
Learn more about [Account Identifiers in Snowflake](https://docs.snowflake.com/en/user-guide/admin-account-identifier).
|
||||
|
||||
## Username
|
||||
### Username
|
||||
|
||||
The database username for the account that you want to use to connect to your database. You can set up multiple connections to the same database using different user accounts to connect to the same database, each with different sets of [privileges](../users-roles-privileges.md).
|
||||
|
||||
On your app.snowflake.com account page, you can find Users and roles under Admin > Users & Roles.
|
||||
|
||||
## Password
|
||||
### Password
|
||||
|
||||
The password for the username that you use to connect to the database.
|
||||
|
||||
## RSA private key (PEM)
|
||||
### RSA private key (PEM)
|
||||
|
||||
Not required. You have the option of using a **Local file path** or an **Uploaded file path**. If you're on Metabase Cloud, you'll need to select **Uploaded file path** and upload your certificate.
|
||||
|
||||
## Warehouse
|
||||
### Warehouse
|
||||
|
||||
Snowflake warehouse. If the user lacks a default warehouse, you'll need to enter the warehouse to connect to.
|
||||
|
||||
On app.snowflake.com, you can find warehouses under Admin > Warehouses.
|
||||
|
||||
## Database name (case sensitive)
|
||||
### Database name (case sensitive)
|
||||
|
||||
The name of the database you want to connect to in Snowflake. On app.snowflake.com you can find databases under Data > Databases.
|
||||
|
||||
## Schemas (optional)
|
||||
### Schemas (optional)
|
||||
|
||||
You can specify which schemas you want to sync and scan. If no schema is passed, then all schema available to that user and role will be listed as folders in Metabase.
|
||||
|
||||
@@ -96,7 +96,7 @@ Let's say you have three schemas: FOO, BAR, and BAZ.
|
||||
|
||||
Note that only the `*` wildcard is supported; you can't use other special characters or regexes.
|
||||
|
||||
## Role (optional)
|
||||
### Role (optional)
|
||||
|
||||
> **WARNING:** Metabase gets _all the permissions_ combined from _every_ role you grant the user in Snowflake. Setting the 'Role' here just adds the role to the connection string to tell Snowflake which role to default to – it **won't restrict** access on its own. So **be careful which roles you assign to the connection user**: if down the line you grant that user broader roles in Snowflake, Metabase will automatically gets those new permissions, potentially showing data you didn't mean for it to see. If instead you want Metabase to issue `SET ROLE` commands before querying anything, check out [connection impersonation](../../permissions/impersonation.md).
|
||||
|
||||
@@ -107,25 +107,25 @@ Specify a role to override the database user's default role. For example, if the
|
||||
|
||||
You can enter `ANALYTICS` in the Role field to ensure that the `METABASE` user connects to Snowflake using the `ANALYTICS` role by default.
|
||||
|
||||
## Use an SSH tunnel
|
||||
### Use an SSH tunnel
|
||||
|
||||
See our [guide to SSH tunneling](../ssh-tunnel.md).
|
||||
|
||||
## Additional JDBC connection string options
|
||||
### Additional JDBC connection string options
|
||||
|
||||
Some databases allow you to append options to the connection string that Metabase uses to connect to your database.
|
||||
|
||||
## Re-run queries for simple explorations
|
||||
### Re-run queries for simple explorations
|
||||
|
||||
Turn this option **OFF** if people want to click **Run** (the play button) before applying any [Summarize](../../questions/query-builder/summarizing-and-grouping.md) or filter selections.
|
||||
|
||||
By default, Metabase will execute a query as soon as you choose an grouping option from the **Summarize** menu or a filter condition from the [drill-through menu](https://www.metabase.com/learn/metabase-basics/querying-and-dashboards/questions/drill-through). If your database is slow, you may want to disable re-running to avoid loading data on each click.
|
||||
|
||||
## Choose when syncs and scans happen
|
||||
### Choose when syncs and scans happen
|
||||
|
||||
See [syncs and scans](../sync-scan.md#choose-when-syncs-and-scans-happen).
|
||||
|
||||
### Periodically refingerprint tables
|
||||
#### Periodically refingerprint tables
|
||||
|
||||
> Periodic refingerprinting will increase the load on your database.
|
||||
|
||||
@@ -133,6 +133,12 @@ Turn this option **ON** to scan a sample of values every time Metabase runs a [s
|
||||
|
||||
A fingerprinting query examines the first 10,000 rows from each column and uses that data to guesstimate how many unique values each column has, what the minimum and maximum values are for numeric and timestamp columns, and so on. If you leave this option **OFF**, Metabase will only fingerprint your columns once during setup.
|
||||
|
||||
## Writeable connection
|
||||
|
||||
{% include plans-blockquote.html feature="Writeable connection" is_plural=true%}
|
||||
|
||||
Set up an additional connection used for write operations. See [Writeable connections](../writeable-connection.md).
|
||||
|
||||
## Model features
|
||||
|
||||
There aren't (yet) any model features available for Snowflake.
|
||||
|
||||
@@ -4,7 +4,7 @@ title: SparkSQL
|
||||
|
||||
# SparkSQL
|
||||
|
||||
To add a database connection, click on the **gear** icon in the top right, and navigate to **Admin settings** > **Databases** > **Add a database**.
|
||||
To add a database connection, click the **grid** icon in the top right, and navigate to **Admin** > **Databases** > **Add a database**.
|
||||
|
||||
## Settings
|
||||
|
||||
@@ -77,6 +77,12 @@ Turn this option **ON** to scan a sample of values every time Metabase runs a [s
|
||||
|
||||
A fingerprinting query examines the first 10,000 rows from each column and uses that data to guesstimate how many unique values each column has, what the minimum and maximum values are for numeric and timestamp columns, and so on. If you leave this option **OFF**, Metabase will only fingerprint your columns once during setup.
|
||||
|
||||
## Writeable connection
|
||||
|
||||
{% include plans-blockquote.html feature="Writeable connection" is_plural=true%}
|
||||
|
||||
Set up an additional connection used for write operations. See [Writeable connections](../writeable-connection.md).
|
||||
|
||||
## Danger zone
|
||||
|
||||
See [Danger zone](../danger-zone.md).
|
||||
|
||||
@@ -4,7 +4,7 @@ title: SQL Server
|
||||
|
||||
# SQL Server
|
||||
|
||||
To add a database connection, click on the **gear** icon in the top right, and navigate to **Admin settings** > **Databases** > **Add a database**.
|
||||
To add a database connection, click the **grid** icon in the top right, and navigate to **Admin** > **Databases** > **Add a database**.
|
||||
|
||||
## Supported versions
|
||||
|
||||
@@ -97,6 +97,12 @@ A fingerprinting query examines the first 10,000 rows from each column and uses
|
||||
|
||||
To connect to Azure SQL, you'll need to set the port to 1433.
|
||||
|
||||
## Writeable connection
|
||||
|
||||
{% include plans-blockquote.html feature="Writeable connection" is_plural=true%}
|
||||
|
||||
Set up an additional connection used for write operations. See [Writeable connections](../writeable-connection.md).
|
||||
|
||||
## Database routing
|
||||
|
||||
With database routing, an admin can build a question once using one database, and the question will run its query against a different database with the same schema depending on who is viewing the question.
|
||||
|
||||
@@ -6,7 +6,7 @@ title: SQLite
|
||||
|
||||
> SQLite isn't available for [Metabase Cloud](https://www.metabase.com/cloud/).
|
||||
|
||||
To add a database connection, click on the **gear** icon in the top right, and navigate to **Admin settings** > **Databases** > **Add a database**.
|
||||
To add a database connection, click the **grid** icon in the top right, and navigate to **Admin** > **Databases** > **Add a database**.
|
||||
|
||||
Fill out the fields for that database, and click **Save changes** at the bottom.
|
||||
|
||||
@@ -61,6 +61,12 @@ Turn this option **ON** to scan a sample of values every time Metabase runs a [s
|
||||
|
||||
A fingerprinting query examines the first 10,000 rows from each column and uses that data to guesstimate how many unique values each column has, what the minimum and maximum values are for numeric and timestamp columns, and so on. If you leave this option **OFF**, Metabase will only fingerprint your columns once during setup.
|
||||
|
||||
## Writeable connection
|
||||
|
||||
{% include plans-blockquote.html feature="Writeable connection" is_plural=true%}
|
||||
|
||||
Set up an additional connection used for write operations. See [Writeable connections](../writeable-connection.md).
|
||||
|
||||
## Database routing
|
||||
|
||||
With database routing, an admin can build a question once using one database, and the question will run its query against a different database with the same schema depending on who is viewing the question.
|
||||
|
||||
@@ -7,7 +7,7 @@ description: Learn how to connect Metabase to your Starburst or Trino database,
|
||||
|
||||
> This driver also works for connections to a Trino database.
|
||||
|
||||
To add a database connection, click on the **gear** icon in the top right, and navigate to **Admin settings** > **Databases** > **Add a database**.
|
||||
To add a database connection, click the **grid** icon in the top right, and navigate to **Admin** > **Databases** > **Add a database**.
|
||||
|
||||
You can edit these settings at any time. Just remember to save your changes.
|
||||
|
||||
@@ -91,6 +91,12 @@ Turn this option **ON** to scan a sample of values every time Metabase runs a [s
|
||||
|
||||
A fingerprinting query examines the first 10,000 rows from each column and uses that data to guesstimate how many unique values each column has, what the minimum and maximum values are for numeric and timestamp columns, and so on. If you leave this option **OFF**, Metabase will only fingerprint your columns once during setup.
|
||||
|
||||
## Writeable connection
|
||||
|
||||
{% include plans-blockquote.html feature="Writeable connection" is_plural=true%}
|
||||
|
||||
Set up an additional connection used for write operations. See [Writeable connections](../writeable-connection.md).
|
||||
|
||||
## Model features
|
||||
|
||||
There aren't (yet) any model features for Starburst.
|
||||
|
||||
@@ -41,6 +41,12 @@ For example, if you're running Metabase from a directory called `/app/`, you sho
|
||||
|
||||
The process for adding plugins when running via Docker is similar, but you'll need to mount the `plugins` directory. Refer to instructions [here](../../installation-and-operation/running-metabase-on-docker.md#adding-external-dependencies-or-plugins) for more details.
|
||||
|
||||
## Writeable connection
|
||||
|
||||
{% include plans-blockquote.html feature="Writeable connection" is_plural=true%}
|
||||
|
||||
Set up an additional connection used for write operations. See [Writeable connections](../writeable-connection.md).
|
||||
|
||||
## Model features
|
||||
|
||||
There aren't (yet) any model features for Vertica.
|
||||
|
||||
@@ -11,8 +11,8 @@ But since of course you're making [backups of your Metabase application database
|
||||
|
||||
To access the Danger Zone section for a database connection:
|
||||
|
||||
1. Click on the **gear** icon in the top right of Metabase.
|
||||
2. Go to **Admin settings** > **Databases**.
|
||||
1. Click the **grid** icon in the top right of Metabase.
|
||||
2. Go to **Admin** > **Databases**.
|
||||
3. Find the database you want to modify and click on it.
|
||||
4. Scroll down to the bottom of the database's settings page to find the "Danger Zone" section.
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ Once you set the `MB_ENCRYPTION_SECRET_KEY` value, Metabase will automatically e
|
||||
|
||||
## Encrypting an existing connection
|
||||
|
||||
If you added databases before setting the `MB_ENCRYPTION_SECRET_KEY` value, you can encrypt the connection details by going to each one of those databases in **Admin settings** > **Databases** and clicking on the **Save** button. Existing databases with unencrypted details will continue to work normally.
|
||||
If you added databases before setting the `MB_ENCRYPTION_SECRET_KEY` value, you can encrypt the connection details by going to each one of those databases in **Admin** > **Databases** and clicking on the **Save** button. Existing databases with unencrypted details will continue to work normally.
|
||||
|
||||
## Rotating an encryption key
|
||||
|
||||
|
||||
BIN
docs/databases/images/writeable-connection.png
Normal file
BIN
docs/databases/images/writeable-connection.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 100 KiB |
@@ -15,16 +15,16 @@ Metabase periodically runs different types of queries on your data warehouse to
|
||||
|
||||
When Metabase first connects to your database, it performs a [sync](#how-database-syncs-work) to determine the metadata of the columns in your tables and automatically assign each column a [semantic type](../data-modeling/semantic-types.md).
|
||||
|
||||
You can follow the progress of these queries from **Gear icon** >**Admin settings** > **Tools** > **Tasks** and filtering by the various sync tasks.
|
||||
You can follow the progress of these queries from **grid icon** >**Admin** > **Tools** > **Tasks** and filtering by the various sync tasks.
|
||||
|
||||
Once the queries are done running, you can view and edit the synced metadata from **Admin settings** > **Table Metadata**. For more info, see [editing metadata](../data-modeling/metadata-editing.md).
|
||||
Once the queries are done running, you can view and edit the synced metadata from **Admin** > **Table Metadata**. For more info, see [editing metadata](../data-modeling/metadata-editing.md).
|
||||
|
||||
## Choose when syncs and scans happen
|
||||
|
||||
By default, Metabase does a lightweight hourly sync and an intensive daily scan of field values. If you have a large database, you might want to choose when syncs and scans happen.
|
||||
|
||||
1. Click on the **Gear icon**.
|
||||
2. Select **Admin settings**.
|
||||
1. Click on the **grid icon**.
|
||||
2. Select **Admin**.
|
||||
3. Go to **Databases**.
|
||||
4. Select your database.
|
||||
5. In the **Connection and sync** section, click on **Edit connection details**.
|
||||
@@ -54,21 +54,21 @@ Regardless of which option you pick, if you [set a field to use a dropdown list
|
||||
|
||||
## Manually syncing tables and columns
|
||||
|
||||
1. Go to **Admin settings** > **Databases** > your database.
|
||||
1. Go to **Admin** > **Databases** > your database.
|
||||
2. Click **Sync database schema**.
|
||||
|
||||
## Manually scanning column values
|
||||
|
||||
To scan values from all the columns in a table:
|
||||
|
||||
1. Go to **Admin settings** > **Table Metadata** > your database.
|
||||
1. Go to **Admin** > **Table Metadata** > your database.
|
||||
2. Select the table that you want to bring up to date with your database.
|
||||
3. Click the **gear icon** at the top of the page.
|
||||
4. Click **Re-scan this table**.
|
||||
|
||||
To scan values from a specific column:
|
||||
|
||||
1. Go to **Admin settings** > **Table Metadata** > your database.
|
||||
1. Go to **Admin** > **Table Metadata** > your database.
|
||||
2. Select the table.
|
||||
3. Find the column you want to bring up to date with your database.
|
||||
4. Click the **gear icon** in the panel for that column.
|
||||
@@ -78,7 +78,7 @@ To scan values from a specific column:
|
||||
|
||||
To clear the [scanned field values for a table](#syncing-and-scanning-databases):
|
||||
|
||||
1. Go to **Admin settings** > **Table Metadata**.
|
||||
1. Go to **Admin** > **Table Metadata**.
|
||||
2. Select the database and table.
|
||||
3. Click the **gear icon** in the upper right.
|
||||
4. Click **Discard cached field values**.
|
||||
@@ -89,7 +89,7 @@ You can also tell Metabase to forget the cached values for individual fields by
|
||||
|
||||
To prevent Metabase from running syncs and scans against a specific table, change the [table visibility](../data-modeling/metadata-editing.md#table-visibility) to **Hidden**:
|
||||
|
||||
1. Go to **Admin settings** > **Table Metadata** > your database.
|
||||
1. Go to **Admin** > **Table Metadata** > your database.
|
||||
2. Hover over the table name in the sidebar.
|
||||
3. Click the **eye** icon.
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user